Netwrix 1Secure delivers unified visibility across data and identity — free for 14 days with full access. Start a free trial

Resource centerBlog
Powerful LDAP extended controls: Anti-remediation and invisible recon in AD

Powerful LDAP extended controls: Anti-remediation and invisible recon in AD

Jul 3, 2026

I ran an audit against every MS-ADTS LDAP extended control. Most behave exactly as documented; two stood out for potential offensive use. Both abusing legitimate controls, but neither a privilege escalation:

  1. FORCE_UPDATE → win replication conflicts (anti-remediation). A no-op LDAP MODIFY carrying LDAP_SERVER_FORCE_UPDATE (.1974) inflates an attribute's per-attribute replication version without changing the attribute value. AD breaks conflicts by version first (timestamp only as a tiebreak), so an attacker who can write to at least one attribute can make their value beat a defender's later correction of that attribute on another DC. The fix silently reverts. Needs only WriteProperty; no DA, no replication rights, no rogue DC.
  2. OBJECT_SECURITY DirSync → invisible bulk enumeration. DirSync (.841) with the OBJECT_SECURITY flag is the unprivileged path: any Domain User can bulk-read everything they're already allowed to read, including security descriptors, and it logs nothing (no Event 1644, no Event 4662). A low-noise collection primitive.

The unifying theme: a documented LDAP control, used as intended at the mechanism level, produces an effect Microsoft's telemetry and most defenders don't expect.

Demonstrated against a two-DC cloud.lab (Windows Server 2022, forest functional level 2016). Lab / authorized-research context only.

Finding 1 - FORCE_UPDATE: Winning replication conflicts from a single LDAP write

The idea in plain language

If you already know AD replication cold, skip ahead. Otherwise, here’s an analogy.

Picture a company with two identical filing cabinets in two different offices. These are the two domain controllers (DCs). To stay in sync, every change made to one gets copied to the other. That copying is replication.

What if two people edit the same file at the same time in different offices, before the copying catches up? The cabinets disagree, and AD needs a rule for who wins:

  1. Whoever's version number is higher wins. Every field remembers how many times it has been changed and that count is its "version."
  2. Only if the versions tie does AD look at who edited most recently (timestamp).
  3. If that ties too, it breaks the tie by the cabinet's ID.

The trick: normally, if you “edit” a field by writing back the exact value it already had, AD shrugs, says “nothing changed,” and the version stays put. FORCE_UPDATE is a flag that says “count this as a real change anyway.” So you can write Bob over a field that already says Bob six times, and the version climbs to 6 even though the value never changed.

So an attacker makes one malicious change to a field they can edit, hits it with FORCE_UPDATE a few times to crank the version to 6, and waits. When a defender fixes it on the other cabinet (any other DC), it’s a new, later edit, but the field's first change there, so version 1. The cabinets sync, AD compares 6 vs 1, and 6 wins: the attacker's value comes back and the defender's fix silently disappears. The attacker forced a change that beats a newer, legitimate one.

Two honest catches: it only works on fields you could already edit (it's persistence, not escalation), and it isn't invisible. It leaves a field whose version jumped with no value change. The genuinely new part is the “price of admission: the famous DCShadow attack does the same "my version wins" move but needs near-domain-god powers (pretending to be a DC); this needs one ordinary write and permission on one field.

Background: How AD decides who wins

Active Directory is multi-master, meaning every writable DC accepts changes that replicate outward. Two DCs can be told to change the same attribute of the same object before replication reconciles. For a per-attribute conflict the tiebreak order is:

  1. Version: A per-attribute counter incremented on each originating write.
  2. Timestamp: Used only if versions are equal.
  3. Server (DSA) GUID: Used only if version and timestamp tie.

This metadata is per attribute, visible via repadmin /showobjmeta or msDS-replAttributeMetaData. The crucial property: a lower version always loses to a higher version, no matter how much newer it is. And a MODIFY that writes an attribute's existing value is normally a no-op (AD does not bump the version). You cannot normally run the version up by rewriting the same value.

The control: LDAP_SERVER_FORCE_UPDATE (1.2.840.113556.1.4.1974)

FORCE_UPDATE is a documented, benign-by-framing control: per MS-ADTS, it tells the DC to process the modification even when it would otherwise be a no-op (“update even if the new data is identical”). The Java library ldaptive ships a ForceUpdateControl doing exactly this, with no attack framing. The side effect that matters: because the forced write counts as a real originating write, it increments the per-attribute version with no value change. That is the bridge. The version stamp is conventionally considered unreachable by an LDAP client (which is why DCShadow manipulates it over the replication protocol as a rogue DC); FORCE_UPDATE moves it into reach from an ordinary authenticated LDAP MODIFY.

The technique

  1. Pick a target attribute the principal can already write (one WriteProperty ACE).
  2. Set the malicious value on DC-A.
  3. Inflate the version: Send N additional same-value MODIFYs, each with FORCE_UPDATE, each bumping the version with no visible change.
  4. Wait: A defender corrects the value, naturally on whichever DC they're connected to (DC-B). Their single correction increments the version by one (to current+1). As long as the attacker inflated above that, the attacker still outranks it. (In the lab, the defender landed at version 1 only because the attribute was previously unset; in general, the attacker just needs to out-version the correction, which is free.)
  5. Convergence: AD compares versions; the attacker's inflated version outranks the defender's later-but-lower correction. The attacker's value wins on both DCs.

The defender sees their change “not stick”: they fix it, it looks fixed, and minutes later it reverts.

Privilege model vs DCShadow: The novel part

DCShadow

This technique (FORCE_UPDATE conflict-win)

Manipulates per-attribute version

Yes

Yes

Mechanism

Register a rogue DC, push via DRSUAPI DrsReplicaAdd / GetNCChanges

One authenticated LDAP MODIFY + a request control

Privilege required

DA/EA (or DS-Install-Replica + topology rights) + SYSTEM

WriteProperty on the single target attribute

Server-side footprint

Config-partition objects, a transient rogue DC, cleanup

A single MODIFY to a live DC

Tooling

DCShadow-class tooling

Any LDAP client that can attach a control

The non-obvious insight: LDAP can reach the per-attribute version stamp at all. FORCE_UPDATE quietly breaks the "you must speak the replication protocol as a DC" assumption from the client surface.

Demonstration (two-DC lab)

Attacker = svc-research, a plain domain user delegated only WP;description (no replication rights). Attacker actions run under its own NTLM bind; admin context is used purely for lab orchestration (delegation, replication pause/resume, simulating the defender).

      Step 1 — low-priv writer bumps the version:
  baseline:                      description  version 1
  attacker no-op FORCE_UPDATE -> description  version 2   (no value change)

Step 2 — stage and win the conflict (replication paused):
  DC01:  description='ATTACKER-OWNED'            version 6  @ 19:58:34  (attacker, FORCE_UPDATE x3)
  DC-02: description='defender-remediation-LATER' version 1 @ 19:58:36  (defender, LATER, lower version)
  => CONVERGED: DC01 = DC-02 = 'ATTACKER-OWNED'   (defender's later fix reverted)
      

The entire offensive ingredient is one MODIFY with the control attached:

      var m = new ModifyRequest(dn, DirectoryAttributeOperation.Replace, "description", value);
m.Controls.Add(new DirectoryControl("1.2.840.113556.1.4.1974", null, true, true)); // FORCE_UPDATE, critical
connection.SendRequest(m);   // same-value write now bumps the per-attribute version
      

Impact, scope, and limitations

An anti-remediation / persistence primitive: make an attacker value stubborn against cleanup for any attribute the attacker can already write, e.g. msDS-AllowedToActOnBehalfOfOtherIdentity (RBCD backdoor), servicePrincipalName (re-assert a Kerberoast target), scriptPath / gPLink (sticky foothold).

  • Scope boundary (tested): Linked attributes (member / memberOf) are not affected. They replicate via Linked-Value Replication with per-value metadata; a no-op FORCE_UPDATE re-add of an existing member succeeds but does not increment the link value's version. So group membership can't be pinned this way, only single-valued and multi-valued non-linked attributes.
  • Requires existing write access (not escalation), and the defender must correct on a different DC (or pre-convergence) for the conflict to exist.
  • Not stealthy: It leaves an abnormal version jump with no value change, and a value that “comes back” after remediation (harmj0y, Hunting With AD Replication Metadata, 2017).

Novelty: A multi-sweep prior-art review plus an authenticated GitHub-native code search (~1,000+ OID hits) found no publication of this specific primitive. Almost every hit is inert (SDK headers, language bindings, dissectors, CTF supportedControl dumps). The closest neighbors are DCShadow (same effect, DA/EA via a rogue DC), LDAPAngel/RIFM (a forest-recovery tool that sends FORCE_UPDATE, but non-critically for its operational FSMO/GC purpose, not version inflation), ldaptive's benign ForceUpdateControl, and Tenable's object-level "Conflicting Objects" race. None of them use this technique. Absence of evidence isn't proof, but the conflict-win / anti-remediation application appears unpublished.

Finding 2 - OBJECT_SECURITY DirSync: Enumerating Active Directory with no log trail

The idea in plain language

A regular user can already look up most things in Active Directory: names, group memberships, even the permission lists (ACLs) on objects they can read. Normally, those lookups can be recorded by the DC's query log. DirSync is a sync feature meant for tools like Entra Connect to pull changes. One of its options, OBJECT_SECURITY, lets an ordinary user run it to read everything from Active Directory they're already allowed to read with their permissions, but because it rides the replication plumbing instead of the normal search path, the DC records nothing. It’s the same data a user could already collect, but with no trail. This method also provides a "bookmark" (cookie) to come back later for only what changed in AD.

The mechanism

DirSync (LDAP_SERVER_DIRSYNC_OID, .841) has two modes:

  • flags=0 uses full replication semantics and requires the Get-Changes replication right. This is the privileged, DCSync-adjacent path, and it is logged via Event 4662.
  • OBJECT_SECURITY (flag 0x1) is the unprivileged path documented for ordinary callers: it requires no replication right and no permission change, just Domain Users, and scopes results to data the caller can already read. (This is the path Simon Décosse, simondotsh, documented in 2022. My contribution is the validated detection consequence.)

Because OBJECT_SECURITY exercises no Get-Changes right, it produces no Event 4662; and because DirSync uses the replication code path rather than the search path, it produces no Event 1644. It falls between the two host-log sources entirely.

The DirSync cookie it returns also lets you reconnect later and pull only the changes since last time, so it doubles as a low-noise change monitor.

Why it's a sneaky opportunity

It's a genuine opportunity for the recon / enumeration phase:

  • No special access: Any compromised domain account works. There's no replication right to request, no schema/searchFlags change, nothing that itself looks suspicious.
  • No footprint: Bulk object and membership collection with no-cost log trail, plus a built-in delta-sync for ongoing monitoring.

This is stealth, not new access. It does not hand an attacker data they couldn't otherwise read. It scopes to the caller's effective read access, returning exactly what an ordinary LDAP search would. Crucially, it does not bypass the confidential-attribute gate (searchFlags 0x80): a confidential attribute is returned only if the caller is genuinely entitled to read it (holds the CONTROL_ACCESS right). That bypass belongs to the other DirSync mode, flags=0, which uses replication semantics to leak confidential attributes to anyone with the Get-Changes right (the subject of the companion detection blind spots post). OBJECT_SECURITY also cannot read secrets (it is not DCSync, so no password material). What's different is purely the evasion: an ordinary SD_FLAGS sweep would leave Event 1644 records wherever LDAP query logging is on; the OBJECT_SECURITY DirSync version leaves none in either log. The value to an attacker is a low-noise collection that defeats the LDAP-search telemetry defenders rely on, which is why it's worth knowing about even though it grants no new privilege.

Detection and defense

Both findings share a theme: the obvious detection idea doesn't work, and host logs are blinder than defenders assume. Everything below was validated by firing the techniques in the lab.

FORCE_UPDATE (Finding 1)

  • Primary - replication-metadata hunt: Snapshot msDS-replAttributeMetaData (or repadmin /showobjmeta) for sensitive attributes and alert when a per-attribute version rises while the value hash is unchanged. In the lab this flagged the attack cleanly (description version 28 → 29, value unchanged). A second symptom: a value that reappears after remediation.
  • Event 1644 is blind to it: FORCE_UPDATE rides a MODIFY; Event 1644 logs searches only, so the modify produces no 1644 event. So, a rule keyed on the .1974 OID in query logs never fires. Wire-detection needs PCAP/ETW, and the control requires a signed bind (a simple/Basic bind is rejected), limiting cleartext-389 visibility.
  • Hardening: Minimize WriteProperty on sensitive attributes (the entire prerequisite); remediate on the same DC (when possible) and verify the version afterward.

OBJECT_SECURITY DirSync (Finding 2)

  • Host logs won't see it: No 4662 (no Get-Changes right) and no 1644 (replication path), so there is no event rule to write; this is the blind spot.
  • What actually works is limited: The realistic catch is network / ETW capture of the DirSync control on the wire (subject to LDAPS) plus baselining DirSync usage and alerting on it from any non-sync source. A SACL read-canary is not a reliable backstop here: in testing, a ReadProperty audit ACE did not raise Event 4662 for an ordinary read of the attribute. AD's host read-auditing is unreliable, and DirSync's replication-path read does not change that. (SACL canaries are reliable against the FORCE_UPDATE write and against replication-rights DirSync/DCSync, just not against this stealthy read.)
  • Watch the ADWS angle too: The PowerShell AD cmdlets (Get-ADUser and friends) don't speak LDAP directly. Instead, they go through Active Directory Web Services (ADWS, TCP 9389), which relays the query to the DC locally. So, Event 1644 records the client as 127.0.0.1 (the DC itself), not the operator's real address.
    • Why that matters: Every 1644 rule has to exclude 127.0.0.1. Because the DC's own internal searches generate constant loopback noise, that same exclusion also drops any activity an attacker proxies through ADWS. Only network/ETW visibility sees the true source.

SACL canaries: What they are, and where they help

Several recommendations above lean on SACL canaries, so let’s talk about them. Every AD object's security descriptor carries two access-control lists:

  • DACL: Decides who can do what (permissions)
  • SACL: Decides what gets audited

A SACL audit ACE says “when this object or attribute is accessed, emit Windows Security Event 4662.” A canary is a deliberately placed “honeytoken-style” tripwire on a high-value object that legitimate activity rarely touches, so any access lights it up. Because it's evaluated at the object-access layer (not the control or log layer), it fires regardless of which LDAP control or transport was used.

To deploy:

  • Enable Audit Directory Service Access (Success) and add an audit ACE for the access you care about on your crown jewels (e.g. the RBCD attribute msDS-AllowedToActOnBehalfOfOtherIdentity, privileged-group member, AdminSDHolder).

The Catch: Strong for writes, weak for reads (validated).

These two findings fail different defenses, so the canary helps unevenly:

  • FORCE_UPDATE is a write: A SACL write-canary on the target attribute reliably trips 4662 on the malicious modify and the replication-metadata hunt catches it regardless. (A SACL audit on a control-access right likewise reliably catches replication-rights DirSync/DCSync via the Get-Changes 4662.)
  • OBJECT_SECURITY DirSync is a read: AD's host read auditing is unreliable. In my tests, a ReadProperty audit ACE did not raise 4662 even for an ordinary read of the attribute. So a SACL canary does not dependably catch this stealthy read. It falls to network/ETW capture and DirSync baselining instead.

The blunt version: audit canaries are your best tripwire for the write side and replication-rights abuse, but the stealthy read needs eyes on the wire.

Tools

I built two proof-of-concept tools for Red Team and Blue Team to explore and defend LDAP extended controls: LDAP Extended Controls Toolkit.

Folder

Tool

Language

Use it to

red/

offensive CLI (ldapctl)

Python 3 / ldap3

Collect the directory invisibly, make a change survive remediation, and probe existence without logging

blue/

defensive module (AdLdapDefense)

PowerShell

Audit what your DC actually logs, hunt replication-metadata tampering, deploy and self-test SACL canaries, and catch replication (DirSync/DCSync) abuse

What each tool does

red/ldapctl (offensive)

Three subcommands, each operationalizing one validated extended-control technique. Full flags, privileges, and output shapes are in red/README.md.

Subcommand

What it actually does

Control / finding

Footprint

collect

Bulk-reads objects, attributes, and group member lists over the replication path as an ordinary Domain User, paging on the DirSync cookie for incremental delta runs. Scopes to what the account can already read: it does not bypass confidential attributes and does not return security descriptors (that path stays empty).

OBJECT_SECURITY DirSync

None in host logs: 0× Event 1644, 0× Event 4662. Caught only by a SACL read canary.

pin

Writes a value, then inflates that attribute's per-attribute replication version so it wins AD conflict resolution (version beats timestamp) against a defender's later correction on another DC. Refuses linked attributes (LVR, unaffected). Needs only WriteProperty on the attribute.

FORCE_UPDATE conflict-win

Not stealthy: writes the value and bumps its version. Persistence/anti-remediation, not privesc.

recon

Tests whether a specific DN exists at base scope without reading its attributes and without appearing in Event 1644. Base-DN oracle only (AD evaluates the control as 0 under subtree scope).

EXPECTED_ENTRY_COUNT oracle

Invisible to Event 1644: the control isn't recorded in the 1644 controls field.

blue/AdLdapDefense (defensive)

A PowerShell module of five exported functions covering four detection/hardening capabilities. Parameters and sample output are in blue/README.md. Requires PowerShell 5.1+ and RSAT (ActiveDirectory), run from an admin workstation that can reach the DC.

Function

What it actually does

Catches

Invoke-LdapLoggingAudit

Reports whether Event 1644 is effective: it's silently useless when the search thresholds are 0 (disabled), a common misconfig. Also reports which controls actually land in the 1644 controls field (with -Probe) and whether 4662 auditing is on. -Fix corrects the threshold trap.

LDAP logging blind spots

Invoke-ReplMetadataHunt

Baselines each sensitive attribute's replication Version plus a value hash, then on later runs flags any object where the version rose while the value did not change. That mismatch is the tamper signature, and it's the reliable catch because FORCE_UPDATE rides a modify and leaves no 1644.

FORCE_UPDATE / DCShadow-class version tampering

Deploy-SaclCanary / Test-SaclCanary

Plants a SACL audit ACE on a high-value object so access raises Event 4662, then self-tests that it fires. A read canary is the reliable catch for the OBJECT_SECURITY DirSync collection that is otherwise invisible (matched by objectGUID, not CN).

Reads (including invisible DirSync) and writes

Get-ReplicationAbuse

Hunts Event 4662 carrying a Get-Changes replication GUID from a non-DC, non-approved-sync account, and recovers the source IP by joining to the matching 4624 logon on LogonId. Flags DirSync (Get-Changes) as well as DCSync (Get-Changes-All). Rules that watch only Get-Changes-All miss the lower-privileged DirSync path.

DirSync / DCSync Get-Changes abuse


References

Share on

Learn More

About the author

Darryl baker headshot

Darryl Baker

Senior Staff Security Researcher

Darryl G. Baker is a Senior Staff Security Researcher at Netwrix and a recognized authority in Identity and Active Directory security. With over a decade of identity systems experience, he has led enterprise security assessments, identity security trainings, and threat emulations focused on Active Directory, Entra ID, and Azure environments. Darryl has delivered highly rated trainings and demos at BlueTeamCon, BSidesCT, The Experts Conference, and Wild Wild West Hackin’ Fest. He’s the architect behind numerous hands on attack emulation labs—leveraging current red team and blue team tools to help defenders master everything from attack path analysis to threat hunting. In his sessions, Darryl blends deep technical insight with real world case studies, empowering blue team professionals to strengthen their identity security posture and defend against evolving adversary techniques.