Bugcrowd Student Finale CTF 2025 Writeup: Predictable
In this chal, I was given a compromising an admin account on a website. I had to figure out how password reset tokens were generated and create a valid one to hijack the account. The hint suggested that the tokens were generated using predictable patterns based on timestamps and email addresses.
1. Reconnaissance: Finding the Flaw
I started by analyzing the source code to understand the application's logic. My search led me to a file named ResetToken.js, which was responsible for generating the recovery links.
Inside, I found the "smoking gun." The code for generating tokens looked like this:
const seed = email + currentTime.toString();
const token = crypto.createHash('md5').update(seed).digest('hex');
The Vulnerability
This implementation is critically flawed. Here is exactly what it does:
- It takes the user’s email address (e.g.,
admin@hackthebox.com). - It appends the current system time in milliseconds (e.g.,
1763942850430). - It concatenates them and creates an MD5 hash.
The problem? Both variables are predictable.
- The Email: This is public knowledge (
admin@hackthebox.com). - The Time: I can estimate roughly when the token was created by tracking when I sent the request.
Because the "randomness" relies entirely on the clock, this isn't random at all—it’s just a math problem waiting to be solved.
2. The Attack Plan
To exploit this, I devised a five-step strategy:
- Trigger a Reset: Request a password reset for the admin and record the exact time.
- Define a Time Window: Calculate a range of milliseconds around the request time.
- Offline Generation: Generate fake tokens locally for every millisecond in that window using the formula
MD5(email + timestamp). - Brute Force: Send these tokens to the server until one is accepted.
- Account Takeover: Reset the password, log in, and grab the flag.
3. Building the Exploit Script
I wrote a JavaScript tool to automate this process. Since milliseconds move fast, I needed a script that was both accurate and efficient.
Step 1: Synchronization
When my script sends the reset request, it records the timestamp immediately before sending and immediately after receiving the response. This gives me a specific window where the server must have generated the token. I used the middle of this range as my starting point to account for network latency.
Step 2: The Search Algorithm
Trying every single millisecond blindly can be slow. I implemented a heuristic approach to find the correct timestamp:
- Coarse Search: First, jump by 100ms increments to find the general area.
- Fine Search: Narrow it down to 10ms jumps.
- Precision Search: Finally, try every single 1ms to find the exact match.
My script also sent these requests in batches (concurrency) to speed up the feedback loop.
Step 3: Validation
For every timestamp generated, the script computed: MD5("admin@hackthebox.com" + timestamp)
It then fired the token at the verification endpoint. If the server responded with a success message, the loop broke, and the token was saved.
4. Execution: Running the Exploit
When I ran the script, here is how the attack played out:
- It triggered the reset and estimated the token was created around timestamp 1763942850430.
- It began the coarse search (100ms jumps), taking about 101 tries.
- It switched to the fine search (10ms jumps), taking about 1017 tries.
- Success! It found the valid token matching timestamp 1763942850430.
With the valid token in hand, the script automatically reset the admin password to hacked123.
I logged into the dashboard using the new credentials, and there it was.
The Flag was HTB{pr3d1ct4bl3_s33ds_4r3_s0000_pr3d1ct4bl3!_e3ee706eff39406fc98ae0ef1c84db84}