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:
- FORCE_UPDATE → win replication conflicts (anti-remediation). A no-op LDAP
MODIFYcarryingLDAP_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 onlyWriteProperty; no DA, no replication rights, no rogue DC. - OBJECT_SECURITY DirSync → invisible bulk enumeration. DirSync (
.841) with theOBJECT_SECURITYflag 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:
- Whoever's version number is higher wins. Every field remembers how many times it has been changed and that count is its "version."
- Only if the versions tie does AD look at who edited most recently (timestamp).
- 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:
- Version: A per-attribute counter incremented on each originating write.
- Timestamp: Used only if versions are equal.
- 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
- Pick a target attribute the principal can already write (one
WritePropertyACE). - Set the malicious value on DC-A.
- Inflate the version: Send N additional same-value
MODIFYs, each with FORCE_UPDATE, each bumping the version with no visible change. - 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.)
- 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 |
One authenticated |
|
Privilege required |
DA/EA (or |
|
|
Server-side footprint |
Config-partition objects, a transient rogue DC, cleanup |
A single |
|
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=0uses 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(flag0x1) 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(orrepadmin /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 (descriptionversion 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.1974OID 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
WritePropertyon 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-groupmember,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 |
|---|---|---|---|
|
|
offensive CLI ( |
Python 3 / ldap3 |
Collect the directory invisibly, make a change survive remediation, and probe existence without logging |
|
|
defensive module ( |
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 |
|---|---|---|---|
|
|
Bulk-reads objects, attributes, and group |
OBJECT_SECURITY DirSync |
None in host logs: 0× Event 1644, 0× Event 4662. Caught only by a SACL read canary. |
|
|
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. |
|
|
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 |
|---|---|---|
|
|
Reports whether Event 1644 is effective: it's silently useless when the search thresholds are |
LDAP logging blind spots |
|
|
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 |
|
|
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 |
|
|
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 ( |
DirSync / DCSync Get-Changes abuse |
References
- MS-ADTS,
LDAP_SERVER_FORCE_UPDATE_OID. https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/3c5e87db-4728-4f29-b164-01dd7d7391ea - MS-ADTS / MS-DRSR. Replication conflict resolution (version → timestamp → DSA GUID); DirSync (
LDAP_SERVER_DIRSYNC_OID) and replication semantics - DCShadow, Delpy & Le Toux, BlueHat IL 2018. https://www.dcshadow.com/
- harmj0y, Hunting With Active Directory Replication Metadata (2017). https://blog.harmj0y.net/defense/hunting-with-active-directory-replication-metadata/
- simondotsh, Understanding and Abusing DirSync (2022). https://simondotsh.com/infosec/2022/07/11/dirsync.html
- Tenable, Using Conflicting Objects in Active Directory to Gain Privileges (2024). https://medium.com/tenable-techblog/using-conflicting-objects-in-active-directory-to-gain-privileges-243ef6a27928
Share on
Learn More
About the author
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.
Learn more on this subject
ManageEngine Alternatives: AD Management and Security Tools
Create AD Users in Bulk and Email Their Credentials Using PowerShell
How to create, change, and test passwords using PowerShell
How to Add and Remove AD Groups and Objects in Groups with PowerShell
Trusts in Active Directory