Netwrix 1Secure 提供跨数据和身份的统一可见性——免费试用14天,享有完全访问权限。开始免费试用

资源中心博客

强大的 LDAP 扩展控制:在 AD 中进行反修复与隐形侦察

强大的 LDAP 扩展控制:在 AD 中进行反修复与隐形侦察

Jul 3, 2026

我对每个 MS-ADTS LDAP 扩展控制都进行了审计。大多数都与文档中的描述完全一致;其中有两个因潜在的攻击用途而脱颖而出。两者都滥用合法的控制,但都不是权限提升:

  1. FORCE_UPDATE → 赢得复制冲突(anti-remediation)。 一个无操作(no-op)的 LDAP MODIFY 携带 LDAP_SERVER_FORCE_UPDATE.1974) 会在不改变属性值的情况下,膨胀该属性的“按属性复制版本”。AD 会先按版本打破冲突(仅在需要作为并列决胜时才使用时间戳),因此攻击者只要能 向至少一个属性写入 就能让自己的值在另一个 DC 上“压过”防御方随后对该属性所做的修正。修正会悄无声息地回滚。只需要 WriteProperty;无需 DA、无需复制权限、也无需 rogue DC。
  2. OBJECT_SECURITY DirSync → 隐形的大规模枚举。 DirSync(.841)并启用 OBJECT_SECURITY 标志就是非特权路径:任何域用户(Domain User)都可以批量读取他们已被允许读取的所有内容,包括安全描述符(security descriptors),并且它 不会记录任何日志(无事件 1644,无事件 4662)。噪音很低的收集原语(collection primitive)。

贯穿始终的主题:一种有文档记载的 LDAP 控制项,在机制层面按其预期方式使用时,会产生微软的遥测数据和大多数防御者都不预期的效果。

在双 DC 环境 cloud.lab 中进行了演示(Windows Server 2022,林功能级别 2016)。仅限实验室/获授权研究场景。

发现 1 - FORCE_UPDATE:仅通过一次 LDAP 写入赢得复制冲突

用通俗语言解释这个想法

如果你已经彻底掌握 AD 复制(replication),可以直接跳到后面。否则,下面用一个类比来说明。

想象一家公司的两个不同办公室里各有两个完全相同的档案柜。这就是两台域控制器(domain controllers,DC)。为了保持同步,对其中一台所做的每一次更改都会复制到另一台。这种复制过程就是复制(replication)。

如果两个人在不同办公室里、在复制赶上之前同时编辑同一个文件会怎样呢?两个档案柜里的内容就会不一致,而 AD 需要一条规则来决定谁胜出:

  1. 版本号更高的一方获胜。 每个字段都会记录它被更改了多少次,而这个计数就是它的“version(版本)”。
  2. 只有当版本号相同(并列)时,AD 才会查看谁最后编辑(时间戳)。
  3. 如果仍然相同,就通过柜体的 ID 来打破平局。

诀窍: 通常情况下,如果你通过把 已存在的精确值 写回去来“编辑”某个字段,AD 会耸耸肩,说“没有变化”,版本就会保持不变。 FORCE_UPDATE 是一个标志,意思是“无论如何,请把它当作真正的变更。” 因此你可以把 Bob 写到已经写着 Bob 的字段上 6 次;即使值从未改变,版本也会升到 6。

因此攻击者先对他们可以编辑的某个字段做一次恶意更改,然后用 FORCE_UPDATE 触发几次,把版本“刷到”6,接着等待。当防御者在 另一个 机柜(任意其他 DC)上修复时,这就是一次新的、 更晚的 编辑;但在该机柜上,这个字段的 第一次 变更,因此版本为 1。机柜同步后,AD 会比较 6 vs 1,并且 6 获胜:攻击者的值又会回来,而防御者的修复会悄无声息地消失。攻击者强制制造了一次能够胜过更新且合法变更的更改。

两个真实的“代价”: 它只对那些你 已经能够编辑 的字段起作用(也就是说它是 持久化,而不是权限升级),而且它并不“隐形”。它会留下这样一种字段:在值未发生变化的情况下,版本却跳涨了。真正新颖的部分是“入场费用:著名的 DCShadow 攻击也会做同样的“我的版本获胜”操作,但需要接近域“神”的能力(假装 be 一个 DC);而这种做法只需要对某个字段进行一次普通写入以及相应权限。

背景:AD 如何决定谁获胜

Active Directory 是 multi-master,这意味着每个可写 DC 都会接受会向外复制的更改。在复制完成并协调之前,可以让两个 DC 对同一对象的 同一属性 进行更改。对于 按属性(per-attribute) 的冲突,其裁决顺序(tiebreak)如下:

  1. 版本: 每次源头写入时,按属性递增的计数器。
  2. 时间戳: 仅在版本相等时使用。
  3. 服务器(DSA)GUID: 仅在版本 时间戳平局时使用。

此元数据按属性划分,可通过 repadmin /showobjmetamsDS-replAttributeMetaData 查看。关键属性: 较低的版本无论“多新”,都会总是输给较高的版本。 以及 MODIFY 这种把属性的现有值写回的操作通常是 no-op(AD 不会递增版本)。通常不能通过重写相同的值来把版本“升级(run the version up)”。

控制项:LDAP_SERVER_FORCE_UPDATE1.2.840.113556.1.4.1974

FORCE_UPDATE 是一种已在文档中说明的、按“通过上下文/场景来定性”的良性控制:根据 MS-ADTS,它会告诉 DC 在本来将是无操作(no-op)的情况下也要处理该修改(“即使新数据相同也更新”)。Java 库 ldaptive 提供了一个 ForceUpdateControl,它正是做了这一点,而且没有任何攻击性的“包装/场景”。关键的副作用是:因为强制写入会被计为真正的源(originating)写入,所以它 在不改变值的情况下递增每个属性的版本号。这就是桥梁。版本戳通常被认为是 LDAP 客户端无法触及的(因此 DCShadow 会以“恶意/伪装的 DC(rogue DC)”身份通过复制协议来操纵它);而 FORCE_UPDATE 则把它从普通的已认证 LDAP MODIFY中变得可达。

该技术

  1. 选择一个目标属性,主体(principal)已经具备写入权限(一个 WriteProperty ACE)。
  2. 设置恶意值 于 DC-A。
  3. 膨胀版本: 发送 N 个额外的相同值 MODIFY,每个都带 FORCE_UPDATE,每次只提升版本而不产生任何可见变化。
  4. 等等: 防御者会修正该值——自然是在其连接到的任意域控制器上完成(DC-B)。他们一次修正只会使版本号增加 1(到 current+1 )。只要攻击者把版本号 高过 这一点,攻击者仍然会压过它。 (在实验室中,防御者落到版本 1 只是因为该属性先前未设置;一般情况下,攻击者只需让自己的版本号高于修正值即可,而这不需要额外成本。)
  5. 收敛: AD 会比较版本号;攻击者被水涨的版本号将高于防御者后发但版本更低的修正。 攻击者的值在两个 DC 上都胜出。

防御者看到自己的改动“没有生效”:他们修正了,看起来修好了,但几分钟后又恢复原状。

权限模型 vs DCShadow:新颖之处

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

不那么显而易见的洞察:LDAP 根本就能触达每个属性的版本时间戳。 FORCE_UPDATE 会悄悄打破客户端层面“你必须以 DC 的身份使用复制协议”的假设。

演示(双 DC 实验环境)

攻击者 = svc-research,普通域用户,仅被委派 only WP;description(没有复制权限)。攻击者的操作在其自身的 NTLM 绑定下运行;管理员上下文仅用于实验室编排(委派、复制暂停/恢复、模拟防御方)。

      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)
      

整个进攻要素就是一个 MODIFY,并附带控制项:

      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
      

影响、范围与限制

一种 anti-remediation / persistence 原语:让攻击者的值对 清理(cleanup) 变得异常固执——适用于攻击者已能写入的任何属性,例如 msDS-AllowedToActOnBehalfOfOtherIdentity(RBCD 后门),servicePrincipalName(重新断言 Kerberoast 目标),scriptPath / gPLink(牢固的落脚点)。

  • 范围边界(已测试): Linked 属性(member / memberOf不受 影响。它们通过带有 per-value 元数据的 Linked-Value Replication 进行复制;对现有成员进行一次无操作(no-op)的 FORCE_UPDATE 重新添加会成功,但不会 增加链接值的版本。因此,无法用这种方式固定(pin)组成员关系;只能固定单值和多值但不属于 Linked 的属性。
  • 需要已有的写入访问权限(不是权限提升),并且防御方必须在不同的 DC(或在收敛之前)进行修正,冲突才会存在。
  • 不隐蔽: 它会在没有值变化的情况下留下异常的版本跳跃,并且在修复后该值会“重新出现”(harmj0y, Hunting With AD Replication Metadata, 2017)。

新颖性: 通过多轮先例(prior-art)审查,再加上经过身份验证的 GitHub 原生代码搜索(约 1,000+ 次 OID 命中),未发现对这一特定原语的任何公开发布。几乎所有命中都毫无影响(SDK 头文件、语言绑定、解剖器、CTF supportedControl 转储)。最接近的相邻技术是 DCShadow(相同效果,通过恶意/异常的 DC 实现 DA/EA),以及 LDAPAngel/RIFM(一种森林恢复工具,会 sends FORCE_UPDATE,但并非以关键方式用于其运行目的:用于 FSMO/GC 的业务需求,而不是为了造成版本膨胀),ldaptive 的良性 ForceUpdateControl,以及 Tenable 的面向对象层面的“Conflicting Objects”竞争。它们都没有使用这种技术。证据的缺失并不等于证明不存在,但这种“冲突获胜 / anti-remediation application ”似乎尚未公开发表。

发现 2 - OBJECT_SECURITY DirSync:在没有日志痕迹的情况下枚举 Active Directory

用通俗语言说明

普通用户已经可以在 Active Directory 中查询到大多数内容:例如名称、组成员关系,甚至是他们有权读取的对象上的权限列表(ACL)。通常情况下,这些查询 可能 会被 DC 的查询日志记录。 DirSync 是一种同步功能,旨在让诸如 Entra Connect 之类的工具拉取更改。其中一个选项是 OBJECT_SECURITY:它允许普通用户运行它,以读取他们已经有权限读取的 Active Directory 中的所有内容,但由于它走的是 replication 这套管道,而不是正常的 search 路径,因此 DC 不会记录任何内容。这与用户本来就可以收集到的数据相同,但没有任何痕迹。该方法还会提供一个“书签”(cookie),让你之后只获取 AD 中发生变化的部分。

工作机制

DirSync (LDAP_SERVER_DIRSYNC_OID, .841) 有两种模式:

  • flags=0 使用完整的复制(replication)语义,并且 需要 Get-Changes 复制权限。 这是特权的、与 DCSync 相邻的路径,并且它 会通过 Event 4662 记录。
  • OBJECT_SECURITY (flag 0x1) 是为普通调用者记录的 非特权 路径:它不需要 任何复制权限,也不需要更改权限,只需 Domain Users,并将结果范围限定为调用者已能够读取的数据。(这是 Simon Décosse,simondotsh 在 2022 年记录的路径。我的贡献是经验证的 检测 结果。)

因为 OBJECT_SECURITY 不使用 Get-Changes 权限,因此它会产生 不产生 Event 4662;同时因为 DirSync 使用的是 复制(replication)代码路径,而不是搜索(search)路径,因此它会产生 不产生 Event 1644。它完全落在两个主机日志源之间。

它返回的 DirSync cookie 还允许你之后重新连接,并且只拉取自上次以来的 变化,因此它也可以兼任 低噪音的变更监控器

为什么这是一个狡猾的机会

这是 recon / enumeration 阶段的难得机会:

  • 没有特殊访问权限: 任何被攻破的域账户都可以。无需请求复制权限,也不会更改架构或 searchFlags;更没有任何本身看起来可疑的操作。
  • 几乎不留痕: 进行批量对象与成员信息收集时不会产生“额外成本”的日志痕迹,并且内置了用于持续监控的增量同步。

这是一种隐蔽(stealth),而不是新的访问权限。 它不会把攻击者原本无法读取的数据交给他们。它的范围限定为调用者的 effective read access,返回的内容与普通的 LDAP 搜索完全一致。关键在于,它 不会绕过 机密属性门禁(searchFlags 0x80):只有在调用者确实有资格读取时,才会返回某个机密属性只有当调用者真正有权读取它(持有 CONTROL_ACCESS 权限)。这种绕过属于 另一个 DirSync 模式,即 flags=0。它利用复制(replication)语义,把机密属性泄露给任何拥有 Get-Changes 权限的人(配套文章 detection blind spots 的主题)。OBJECT_SECURITY 也无法读取秘密(它并非 DCSync,因此没有密码材料)。不同之处纯粹在于 规避(evasion):普通的 SD_FLAGS 扫描 在启用 LDAP 查询日志的任何地方留下 Event 1644 记录;OBJECT_SECURITY 的 DirSync 版本在任意日志中都不会留下记录。对攻击者而言,价值在于 低噪声的收集方式,用它来击败防御者依赖的 LDAP 搜索遥测, 这也是为什么即使它不授予新的权限,仍然值得了解。

检测与防御

这两项发现有一个共同主题:最 显而易见 的检测思路行不通,而主机日志比防御者想象的还要“看不见”。下面所有内容都通过在实验室中触发这些技术进行验证。

FORCE_UPDATE (Finding 1)

  • 主要 - 复制元数据(replication-metadata)排查: 快照 msDS-replAttributeMetaData(或 repadmin /showobjmeta)针对敏感属性,并在每个属性的 版本上升但值哈希不变时发出告警。在实验室中,这能干净地标记出攻击(description 版本 28 → 29,值不变)。第二个症状:一个在 处置(remediation)后又重新出现 的值。
  • 事件 1644 对此无感: FORCE_UPDATE 搭乘 MODIFY;事件 1644 只记录 搜索(searches),因此该 modify 不会产生 任何 1644 事件。所以,以 .1974 这一 OID 为键的查询日志规则永远不会触发。要进行链路检测需要 PCAP/ETW,而该控制需要 signed bind(拒绝简单/Basic bind),从而限制了对明文 389(cleartext-389)的可见性。
  • 加固(Hardening): 最小化 WriteProperty 在敏感属性上的权限(整个前提);在同一台 DC 上进行处置(如果可能),并在之后验证版本。

OBJECT_SECURITY DirSync(发现 2)

  • 主机日志看不到它: 没有 4662(没有 Get-Changes 权限),也没有 1644(复制路径),因此没有可编写的事件规则;这就是盲点。
  • 真正有效的手段有限: 现实的抓手是 网络 / ETW 抓取 在链路上对 DirSync 控制进行捕获(受 LDAPS 影响)以及 为 DirSync 使用建立基线 并从任何非同步源对其发出告警。SACL 读取-canary 是 可靠的后备方案:在测试中,针对属性的普通读取,ReadProperty 审计 ACE 并不会触发事件 4662。AD 的主机读取审计不可靠,而 DirSync 的复制路径读取也不会改变这一点。(SACL canaries FORCE_UPDATE 写入 以及具有复制权限的 DirSync/DCSync 是可靠的,只是不对这种隐蔽的读取生效。)
  • 也要关注 ADWS 这一侧: PowerShell 的 AD cmdlet(Get-ADUser 及相关命令)不会直接讲 LDAP。相反,它们会通过 Active Directory Web Services(ADWS,TCP 9389),由 ADWS 在本地将查询转发给 DC。因此,事件 1644 会将客户端记录为 127.0.0.1(即 DC 本身),而不是操作员的真实地址。
    • 原因在于: 每一条 1644 规则都必须排除 127.0.0.1。因为 DC 自身的内部搜索会持续产生回环噪音,所以同样的排除条件也会把攻击者通过 ADWS 代理的任何活动都丢掉。只有网络/ETW 的可见性才能看到真实来源。

SACL canaries:它们是什么,以及在哪些场景中发挥作用

上面有几项建议都依赖于 SACL canaries,所以我们来聊聊它们。每个 AD 对象的安全描述符(security descriptor)都包含两个访问控制列表:

  • DACL: 决定 谁可以做什么 (权限)
  • SACL: 决定 哪些会被审计

SACL 审计 ACE 会说:“当访问此对象或属性时,发出(emit) Windows Security Event 4662。” 一个 canary 是一种在高价值对象上有意放置的“honeytoken 风格”陷阱(tripwire),该对象很少被正常活动触及,因此任何访问都会将其触发。由于它是在对象访问层(而不是控制层或日志层)进行评估,所以不管使用了 哪种 LDAP 控制或传输方式 都会触发。

部署方法:

  • 启用 Audit Directory Service Access(成功),并为你关心的“crown jewels”(核心资产)相关访问添加审计 ACE(例如 RBCD 属性 msDS-AllowedToActOnBehalfOfOtherIdentity,特权组 memberAdminSDHolder)。

关键点:对写入很强,对读取很弱(已验证)。

这两个发现会失败于不同的防御,因此蜂鸟(canary)只能起到不均衡的作用:

  • FORCE_UPDATE 是一个 write 在目标属性上设置的 SACL write-canary 会在恶意修改时可靠触发 4662,而复制元数据搜查(replication-metadata hunt)无论如何都会捕获它。(对某个 control-access right 进行的 SACL 审计,同样会通过 Get-Changes 4662 可靠捕获复制权限 DirSync/DCSync。)
  • OBJECT_SECURITY DirSync 是一种 read AD 的主机读取审计并不可靠。在我的测试中,一个 ReadProperty 审计 ACE 即使对于普通的 该属性的读取 也没有触发 4662。因此,SACL 金丝雀无法稳定捕获这种隐蔽的读取。需要依靠 网络/ETW 抓取 以及 DirSync 基线 来实现。

直白一点:审计金丝雀是你针对 侧以及复制权限滥用的最佳诱饵(触发器)。但这种隐蔽的 需要“盯住链路”的可视性。

工具

我为红队(Red Team)和蓝队(Blue Team)构建了两个概念验证(proof-of-concept)工具,用于探索并防御 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

每个工具的功能

red/ldapctl(进攻)

三个子命令,各自实现一种经过验证的扩展控制(extended-control)技术。完整的参数标志、权限以及输出格式在 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(防御)

一个 PowerShell 模块,包含 5 个导出的函数,覆盖 4 项检测/加固(detection/hardening)能力。参数和示例输出在 blue/README.md 中。需要 PowerShell 5.1+ 以及 RSAT(ActiveDirectory),并从能够访问 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


参考

分享到

了解更多

关于作者

Darryl baker

Darryl Baker

高级安全研究员

Darryl G. Baker 是 Netwrix 的高级安全研究员,并在 Identity 与 Active Directory 安全领域享有广泛认可的权威地位。凭借十余年的身份系统经验,他领导并开展了面向 Active Directory、Entra ID 和 Azure 环境的企业安全评估、身份安全培训以及以威胁仿真(threat emulations)为重点的工作。Darryl 曾在 BlueTeamCon、BSidesCT、The Experts Conference 以及 Wild Wild West Hackin’ Fest 上提供备受好评的培训与演示。他是众多“动手”攻击仿真实验室背后的设计者——借助当前的红队与蓝队工具,帮助防御方从攻击路径分析到威胁狩猎(threat hunting)全面掌握所需技能。在他的课程中,Darryl 将深厚的技术洞察与真实世界的案例研究相结合,赋能蓝队专业人士强化其 Identity 安全态势,并抵御不断演进的对手技术。