python编写的ldap查询工具不能编译,不方便在windows主机上使用,于是想着用golang去重构。golang有ldap v3库,可以满足基本的查询需求。但是,该库没有实现SDDL的解析。在其issues中有提到相关的解析文章,可以自己实现。
解析
要解析SDDL数据,需要先获取一段原始数据,做如下查询并将nTSecurityDescriptor
的原始数据打印出来,如下图。
整个SDDL结构分为NtSecurityDescriptor头
+Sacl头
+Sacl Ace条目
+Dacl头
+Dacl Ace条目
后续的解析都以我在编写该文章之前的数据进行解析
NtSecurityDescriptor header
根据微软官方[MS-DTYP]: 2.4.6的描述,该部分是一个SECURITY_DESCRIPTOR
结构,有如下字段。其中,Revision
、Sbz1
、Control
、OffsetOwner
、OffsetGroup
、OffsetSacl
、OffsetDacl
这几个字段总共占20个字节。
OffsetOwner
和OffsetGroup
是所有者、所在组的数据偏移,OffsetSacl
和OffsetDacl
是Sacl header、Dacl header数据的偏移地址。根据这些偏移找到对应的数据并将数据解析赋值给变量OwnerSid
、GroupSid
、Sacl
、Dacl
上述结构对应我们Dump出来的数据如下图。我们主要关心的是其偏移大小。OffsetOwner
占4个字节,为C0 08 00 00
,按照小端解析为00 00 08 C0
,转换为10进制值为2240
。OffsetGroup
同理。
OffsetSacl
的数据为14 00 00 00
,转换为10进制后是20
,也就是说,NtSecurityDescriptor header
之后紧跟着Sacl header
(偏移的起始地址为NtSecurityDescriptor header
的第一个字节)。
OffsetDacl
的数据为8C 00 00 00
,按照小端排序为00 00 00 8C
,转换为10进制之后是0x8C=140
NtSecurityDescriptor header
的数据就解析完了,紧跟着的是Sacl header
Sacl 解析
Sacl
这一部分的数据包含Sacl header
和多个 Sacl Ace
条目。
根据微软MS-DTYP: 2.4.5中的描述,Acl
结构总共8个字节,我们需要关心的是AclSize
和AceCount
这两个字段,顾名思义,一个是Sacl
数据的大小,一个是Sacl Ace
的条目数量。
我们找到原始数据中对应的字节数,如下图,就是Sacl header
这是解析之后的。Acl Size
字段数据为78 00
,转换后的10进制值为120
。这个大小是Sacl header
的大小 + 所有Sacl Ace
的大小
Ace Count
的值为2,也就是说Sacl有两条Ace
Sacl header
之后紧跟着的是其对应的Ace数据。
先来看Ace数据结构,在微软的文档只将AceType
、AceFlags
、AceSize
作为Ace Header
的字段,而我这里是将AceMask
、Extended
、SID
都放在了AceStruct
中进行解析。
AceType
代表该Ace条目的类型是允许的ACE还是拒绝的ACE,或者是系统审核的ACE、特定于对象访问允许/拒绝的ACE。
AceFlags
用于指定一组特定于 ACE 类型的控制标志。此字段可以是多个值的组合
AceSize
是这条Ace的数据大小
AceMask
用于对对象的用户权限进行编码。访问掩码既用于对分配给主体的对象的权限进行编码,也用于在打开对象时对请求的访问权限进行编码。
Extended
是Ace的扩展位,该值存在4种情况。
- 为0代表不存在扩展位(
ObjectType
、InheritedObjectType
不存在) - 值为1代表存在
ObjectType
字段 - 为2存在
InheritedObjectType
字段 - 为3则
ObjectType
和InheritedObjectType
都存在
SID
是该ACE对应的所有者SID,可能为用户也可能为组
如下图是我已经解析好的原始数据**。**07 5A
分别是Ace Type
和Ace Flags
字段,他们各占用1个字节。
Ace Size
占用2个字节,为38 00
,转换后10进制值为56,代表该条ACE条目总共有56个字节(07 5a 30 00
一直到第19行的00 00 00 00
,共56个字节),下图中还有一个ACE也是56个字节(23行的07 5a 38 00
到26行的01 00 00 00 00
)。还记得上面Sacl header
中的Acl size
的值吗?这个值的计算就是两个ACE的大小(2 * 56) + Sacl header
的大小(8),总共占用120个字节。
Ace Mask
占用4个字节,原始数据为20 00 00 00
,小端数据为0x00000020
,该字段对应的权限可参考微软MS-DTYE:2.4.3
Extended
也就是扩展位,值为0x03,所以有ObjectType
和InheritedObjectType
两个字段。
ObjectType
和InheritedObjectType
都是16字节大小的数据,是GUID结构,数据以4 + 2 + 2 +2 +6的格式进行分割,如下图ObjectType
解析后的可读字符串为F30E3BBE-9FF0-11D1-B603-0000F80367C1
。改ObjectType
是一个GP-Link属性。解析后的字符串对应的权限可以在MS-ADTS: 5.1.3.2.1中查询。
SID
结构的信息可以在微软Security identifiers | Microsoft Learn文档中找到,已有人实现了这一部分的解析。SID
的大小不是固定的,需要做一个运算。下图中,SID
的数据大小为Ace Size
的值 - Ace Type
大小-Ace Flags
大小 - Ace Size
大小 - Extended
大小 - ObjectType
大小 - InheritedObjectType
大小,即56-1-1-2-4-16-16=12字节
Dacl解析
Sacl Ace
条目解析完之后,就是Dacl header
数据,Dacl header
的数据大小和结构与Sacl header
一样。
Dacl
的数据包含了Dacl header
和Dacl Aces
。Dacl
的数据起始地址存放在NtSecurityDescriptor header
中的OffsetDacl
,当时解析出来的10进制为120,手动偏移后就是下图中标记的数据,正好紧跟在Sacl Ace
条目之后
Dacl header
中,Acl Size
按照上面的解析方法,得到的值为2100,All Count
值为46
Dacl header
后面的数据就是Dacl Ace
条目,一共有46条。Dacl Ace
的数据结构和上面说的Sacl Ace
结构相同,不再赘述。
下图中我解析了前两条Ace,拿第一条来看:
Ace Size
为0x0038=56
Ace Mask
为0x00000010
Extended(扩展)
为0x000001=ObjectType
ObjectType
解析后为4C164200-20C0-11D0-A768-00AA006E0529
SID
解析后为S-1-5-21-556516541-2133182263-1194801646-553
OwnerSid和GroupSid
OwnerSid
和GroupSid
都是SID结构,OwnerSid
数据起始地址为NtSecurityDescriptor header
中的OffsetOwner
的值,数据大小为OffsetGroup
的值(2268) - OffsetOwner
的值(2240)为28个字节。GroupSid
的数据为OffsetGroup
的偏移到Dump数据的末尾。如下图
至此,整个SDDL原始数据我们已经手动解析完成。
GUID结构转换成字符串
我们拿上面Dacl 第一条ACE
中的ObjectType
数据做解析,上文说过,该结构占用16字节,解析该数据需要以4+2+2+2+6的格式进行解析
00 42 16 4c c0 20 d0 11 a7 68 00 aa 00 6e 05 29
//按照4+2+2+2+6的格式进行分割
00 42 16 4c
c0 20
d0 11
a7 68
00 aa 00 6e 05 29
该结构的前8个字节按照小端进行解析,后8个字节是按照大端进行解析的,就是
4C 16 42 00
20 C0
11 d0
a7 68
00 aa 00 6e 05 29
所以上文的ACE转换成字符串就是
4C164200-20C0-11D0-A768-00AA006E0529
代码如下
SID结构转换成字符串
幸运的是,该结构已经有现成的代码可以使用了
Golang自动化解析
我们编写如下测试代码,以管理员账户administrator
连接目标dc.test.lab
ldap服务器,设置过滤器(objectclass=domain)
,查询属性为nTSecurityDescriptor
,第39 ~ 50行两个for循环是打印出解析完成后的nTSecurityDescriptor
数据
运行后结果如下:
NtSecurityDescriptor header
数据
Sacl
数据
Dacl
数据
查出了具有DS_Replication_Get_Changes
权限的DAcl Ace
普通用户不能查询DCSync权限
普通用户如果想要查询哪些用户具有DCSync权限,查询的返回结果是空。原因在这篇文章中有提到,简单来说就是默认情况下查询的SDDL属性中包含Sacl
和Dacl
,Sacl
的属性是需要鉴权的,如果查询的用户没有读取Sacl
的权限,域控就不会返回任何数据(Sacl + Dacl)。如下图,使用普通用户查询域控的nTSecurityDescriptor
属性,(因为普通用户没有读取Sacl的权限)域控返回的结果中不包含任何内容。
解决方法是只查询Dacl
部分就行了。在python impack中可以设置controls的sdflags位为0x04
而在golang中,这篇文章提到了ldap3 controls位的设置,于是我们可以在查询请求中配置controls位为0x04
,查询结果如下。此时返回的数据中就不包含Sacl
数据了,OffsetSacl
的值为0
参考
Process low level NtSecurityDescriptor - IT Insights Blog