DCSync是AD域渗透中常用的凭据窃取手段,默认情况下,域内不同DC每隔15分钟会进行一次数据同步,当一个DC从另外一个DC同步数据时,发起请求的一方会通过目录复制协议(MS- DRSR)来对另外一台域控中的域用户密码进行复制,DCSync就是利用这个原理,“模拟”DC向真实DC发送数据同步请求,获取用户凭据数据,由于这种攻击利用了Windows RPC协议,并不需要登陆域控或者在域控上落地文件,避免触发EDR告警,因此DCSync时一种非常隐蔽的凭据窃取方式。
1.攻击场景
- DCSync通常是作为其他攻击的先兆性攻击:
- 例如攻击者可能会使用DCSync窃取krbtgt用户的hash来制作黄金票据;
- 在hash传递攻击中,攻击者会利用DCSync去获取低权限用户的hash进行登录。
- DCSync也可能是其他一些漏洞的后置性攻击手段:
- 例如CVE-2020-1472漏洞,攻击者利⽤Netlogon协议漏洞将域控机器账户密码置空,然后利用空密码对域控进行DCSync攻击获取域内用户凭据。
- DCSync也可以会作为一种维权手段
- 攻击者在控制某个域之后可能会对域内某些普通用户或者用户组赋予DCSync权限,以便在下一次进入内网后直接利用该低权限用户凭据来dump域管凭据,实现对AD域的隐蔽控制。
2.利用条件
在默认情况下,只有域控机器用户、域管理员(Domain Admins)、企业管理员(Enterprise Admins)等高权限账户才有DCSync操作的权限,从更细粒度的ACL层面来说,DCSync需要以下两个权限
- 目录复制同步
(Replicating Directory Changes) - 目录复制同步所有项
(Replicating Directory Changes All)
3.原理分析
在AD域环境中域控制器(Domain Controller)扮演了最核心的角色,承担了域内用户的管理、认证、票据授权等作用,为了防止一台域控宕机导致整个域环境崩溃,企业内通常会部署多台域控,为了保证这些域控中存储的用户数据一致性,这些域控之间会利用MS-DRSR 协议中的drsuapi RPC 接口来进行数据同步。
DCSync攻击就是“模拟了”域控同步的行为去调用DRSGetNCChanges函数,这个函数返回的数据中就包含了用户的密码。
目前DCSync攻击的常用攻击工具一般为mimikatz或impacket工具包中的Secretsdump.py
从mimikatz代码来分析,整个过程一共调用了以下4个RPC函数
DRSBind;
- DRSBind
DRSBind函数的作用是初始化drs句柄,与服务端进行消息版本和加密方式的协商,这是调drsuapi中函数之前的必要操作。
在这个函数中有两个参数,一个是puuidClientDsa,指向调用方的GUID的指针,另一个是pextClient,pextClient参数主要包含的是协商信息,它指向的是一个DRS_EXTENSIONS_INT结构体,在这个结构体中的dwFlags字段标识了调用方支持的功能列表。
在mimikatz中dwFlags被设置为
DRS_EXT_GETCHGREPLY_V6 | DRS_EXT_STRONG_ENCRYPTION。
在Secretsdump工具dwFlags设置的是
DRS_EXT_GETCHGREQ_V6 | RS_EXT_GETCHGREPLY_V6 | DRS_EXT_GETCHGREQ_V8 | DRS_EXT_STRONG_ENCRYPTION
puuidClientDsa表示调用方的GUID,在两个工具中都是一样的 “e24d201a-4fd6-11d1-a3da-0000f875ae0d” 。
在[MS-DRSR]文档中这个guid被称作NTSAPI_CLIENT_GUID,微软在文档中表示只有puuidClientDsa是NULL GUID时服务端才会返回错误,我在实际测试中修改成其他的GUID也没有对DCSync造成影响,不知道这两个工具将puuidClientDsa设置为NTSAPI_CLIENT_GUID是出于什么目的。
- DRSDomainControllerInfo
DRSDomainControllerInfo函数主要是获取域控的一些信息,在这里目的是获取目标服务端域控的GUID,这是调用DRSGetNCChanges函数必须使用的参数。
- DRSCrackNames
DRSCrackNames作用是查询目录内的对象,并将结果返回给调用方,我们在dump hash是这里的对象就是传入的用户名,DRSCrackNames接受多种形式的用户名,包括UPN、FQDN、SPN等。
在Secretdump中只有以下两种格式被支持。
DS_NT4_ACCOUNT_NAME
mimikatz中支持的格式就比较多了
传入的用户名类型需要在DRS_MSG_CRACKREQ结构体的formatOffered参数中指定,在rpNames传入需要查询的用户名,这里是一个数组,可以一次传入多个用户。这一步最核心的目的是获取用户的GUID,这是调用DRSGetNCChanges函数另一个必要参数。
- DRSGetNCChanges
这是整个DCSync过程最重要的一个函数
输入的参数是一个DRS_MSG_GETCHGREQ结构体,这里以DRS_MSG_GETCHGREQ_V8为例:
uuidDsaObjDest和uuidInvocIdSrc分别表示客户端DC和服务端DC的GUID,secretsdump中将这两个参数都设置成DRSDomainControllerInfo请求中获取的服务端DC的GUID。
而mimikatz只设置了uuidDsaObjDest,uuidInvocIdSrc默认为NULL
在实际测试中将uuidDsaObjDest设置为NULL或者服务端DC的GUID时,uuidInvocIdSrc设置为任意的GUID时DCSync都可以成功,所以实际上DRSDomainControllerInfo这一步是可以省略的。
pNC表示需要复制的对象,这是一个DSName结构,可以通过GUID、SID、或者是DN来表示域内的一个对象,impacket和mimikatz都是通过GUID来标识用户的。
实际上,在知道用户的SID或者DN的情况下也是可以成功的
所以在知道目标sid或者dn的情况下DRSCrackNames这一步也是可以省略的。
pPartialAttrSet包含了需要复制的对象属性,类型是ATTRTYP,这里的ATTRTYP实际上是压缩形式的oid, 属性名和对应的oid在mimikatz和secretsdump中都有定义。
其中密码字段就保存在unicodePwd属性中,这里只要有这一个属性就能获取到对象的密码。
DRSGetNCChanges返回的是DRS_MSG_GETCHGREPLY结构,两个工具协商的返回类型都是DRS_EXT_GETCHGREQ_V6,所以这里以DRS_MSG_GETCHGREPLY_V6为例分析。
对象的信息包含在pObjects属性中,这是一个链表结构,在mimikatz中对这个链表进行了遍历,并根据不同属性的类型进行解析。
这里我们最关注的属性就是unicodePwd,在复制以下属性时会将这些属性用sessionKey进行加密,加密方式是RC4。
unicodePwd
kull_m_rpc_drsr_ProcessGetNCChangesReply函数是mimikatz中对这些属性进行解密的关键函数,详情如下。
最后使用rid生成的key对unicodePwd进行解密就的到我们想要的ntlm hash。
- 总结
通过对DCSync原理及利用过程进行分析,发现在以上4个RPC函数中DRSBind不可以省略,DRSDomainControllerInfo目的是获取drsuapi服务端的GUID,这个GUID可以是NULLGUID,可以省略,DRSCrackNames的作用是获取用户的GUID,DRSGetNCChanges可以接受SID或者DN形式的用户名,也可以省略。
因此,可以对现有工具进行代码简化,简化之后可以用直接用DRSBind和DRSGetNCChanges函数来进行DCSync,利用过程如下图所示。
4.防御和检测
由于DCSync攻击需要Replicating Directory Changes和Replicating Directory Changes All这两个权限,可以通过LDAP查询域内对象的ACL,对域内有DCSync权限的用户进行排查。
在网络层面,除了一些特殊情况,从非域控ip发起的IDL_DRSGetNCChanges rpc请求基本上可以判定为攻击行为。
5.参考
- https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-drsr/f977faaa-673e-4f66-b9bf-48c640241d47
- https://github.com/gentilkiwi/mimikatz
- https://github.com/SecureAuthCo