DCSync是AD域渗透中常用的凭据窃取手段,默认情况下,域内不同DC每隔15分钟会进行一次数据同步,当一个DC从另外一个DC同步数据时,发起请求的一方会通过目录复制协议(MS- DRSR)来对另外一台域控中的域用户密码进行复制,DCSync就是利用这个原理,“模拟”DC向真实DC发送数据同步请求,获取用户凭据数据,由于这种攻击利用了Windows RPC协议,并不需要登陆域控或者在域控上落地文件,避免触发EDR告警,因此DCSync时一种非常隐蔽的凭据窃取方式。
1.攻击场景
2.利用条件
在默认情况下,只有域控机器用户、域管理员(Domain Admins)、企业管理员(Enterprise Admins)等高权限账户才有DCSync操作的权限,从更细粒度的ACL层面来说,DCSync需要以下两个权限
3.原理分析
在AD域环境中域控制器(Domain Controller)扮演了最核心的角色,承担了域内用户的管理、认证、票据授权等作用,为了防止一台域控宕机导致整个域环境崩溃,企业内通常会部署多台域控,为了保证这些域控中存储的用户数据一致性,这些域控之间会利用MS-DRSR 协议中的drsuapi RPC 接口来进行数据同步。
DCSync攻击就是“模拟了”域控同步的行为去调用DRSGetNCChanges函数,这个函数返回的数据中就包含了用户的密码。
目前DCSync攻击的常用攻击工具一般为mimikatz或impacket工具包中的Secretsdump.py
从mimikatz代码来分析,整个过程一共调用了以下4个RPC函数
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函数主要是获取域控的一些信息,在这里目的是获取目标服务端域控的GUID,这是调用DRSGetNCChanges函数必须使用的参数。
DRSCrackNames作用是查询目录内的对象,并将结果返回给调用方,我们在dump hash是这里的对象就是传入的用户名,DRSCrackNames接受多种形式的用户名,包括UPN、FQDN、SPN等。
在Secretdump中只有以下两种格式被支持。
DS_NT4_ACCOUNT_NAME
mimikatz中支持的格式就比较多了
传入的用户名类型需要在DRS_MSG_CRACKREQ结构体的formatOffered参数中指定,在rpNames传入需要查询的用户名,这里是一个数组,可以一次传入多个用户。这一步最核心的目的是获取用户的GUID,这是调用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.参考