Cracking Time-Based Tokens: A Glimpse from a Workshop During leHACK 2025-Singularity
On the night of June 28th, 2025 (technically June 29th, because the workshop was at 3AM — yes, 3 in the morning), I attended a hands-on workshop at leHACK 2025 Singularity, titled:
“Insecure time-based secret in web applications and Sandwich attack exploitation”
by Tom Chambaretaud — Technical Lead @YesWeHack & Bug Bounty Hunter
This blog post covers only a small part of the workshop: the exploitation of password reset tokens based on PHP's uniqid()
function.
For more advanced scenarios and techniques (MongoDB ObjectIDs, rainbow tables, etc.), I highly recommend checking out Tom’s blog:
👉 https://www.aeth.cc
The Scenario: Predictable Token Generation
Many real-world web applications generate password reset tokens using uniqid()
, a PHP function that creates identifiers based solely on the current timestamp in microseconds, with no added randomness.
Example of a password reset token generated using uniqid()
.
We can then easily identify that it’s a uniqid()
token from its 13-character hexadecimal structure (or by using tools such as Reset Tolkien, as demonstrated in later in this blog).
Official PHP documentation for the uniqid()
function.
When used in token generation for a password reset request for example, this results in predictable values, raising the question:
Can an attacker exploit this by guessing the token generation time and recreating it?
The answer: yes.
Attack Flow in Simple Terms
Here's a simplified version of how an attacker can take advantage of this vulnerability:
- The attacker triggers a password reset for the victim’s email.
- They estimate the time when the server generated the token.
- They reproduce a range of tokens using
uniqid(timestamp)
over a short time window. - They try each token until one is accepted.
How an attacker can exploit predictable token generation with timing guesses.
The Risk of Missing the Target
But what happens if the attacker is just slightly off with their timing?
So close, yet so far. The attacker stops just before hitting the correct token.
Sometimes, you're just a couple of tokens away from gold.
The Sandwich Attack: Bounding the Victim's Token Timestamp
To avoid missing the valid token, the attacker must pinpoint and constrain the token’s generation time window. This is where the Sandwich Attack comes in — a clever way to trap the victim's token between two known timestamps.
How It Works
- Send a reset request for an attacker-controlled email → receive
token₁
(known). - Send a reset request for the victim’s email →
token₂
(unknown). - Send another reset request for the controlled email → receive
token₃
(known).
This traps the victim’s token token₂
between token₁
and token₃
in time.
The victim’s token is guaranteed to have been generated between two attacker-controlled tokens.
The attacker now knows:
t₁
: time of first known tokent₃
: time of third known tokent₂
: must be between them
This reduces the number of bruteforce attempts significantly.
The guessing window is now tightly bounded, making bruteforce faster and more effective.
Optimizing the Attack: Time Guessing Techniques
Even with a bounded range, bruteforce might still take too long — especially if the tokens are based on microsecond-level timestamps.
To further improve precision, the workshop demonstrated two key techniques.
1. Burp Suite: Sequential or Parallel Requests
By using Burp Suite to send requests very close together in time, either sequentially or in parallel, you can reduce timing noise.
After intercepting the request in Burp and sending it to Repeater, you can rename the tabs to better understand which request is which.
The first request will give us the first controlled token.
The second request will give us the second uncontrolled (victim's) token.
The third request will give us the third controlled token.
Right-clicking on one of the tabs, then choosing "Create tab group".
Name the group, assign a color
After grouping, we can send the requests in sequence. Sending tightly timed reset requests helps reduce uncertainty.
Example tokens from real testing:
-
1st token:
6862d10ad835d
First controlled token received by the attacker. -
3rd token:
6862d10ae865f
Third controlled token received by the attacker. -
Victim’s token:
6862d10addf2a
Token received by the victim between two attacker-controlled requests.
Using send group in parallel
follows the same logic, and in some instances proved to have better results than sending them in sequential order.
This is, of course, for demonstration purposes only — in real-world scenarios, we wouldn't have access to the victim's inbox (and therefore the reset token). It's used here solely to validate that the exploitation technique is viable.
Now using code adapted from Tom's article, we can check and prove that the victim's token is indeed "sandwiched" between our two controlled tokens, and calculated the difference in microseconds :
import math
import datetime
def uniqid(timestamp: float):
sec = math.floor(timestamp)
usec = round(1000000 * (timestamp - sec))
return "%8x%05x" % (sec, usec)
def reverse_uniqid(value: str):
return float(
str(int(value[:8], 16))
+ "."
+ str(int(value[8:], 16))
)
def check():
t = datetime.datetime.now().timestamp()
u = uniqid(t)
return t == reverse_uniqid(u)
if check():
tokens = ["6862d10ad835d", "6862d10ae865f", "6862d10addf2a"]
timestamps = [reverse_uniqid(t) for t in tokens]
datetimes = [datetime.datetime.fromtimestamp(ts) for ts in timestamps]
for token, ts, dt in zip(tokens, timestamps, datetimes):
print(f"{token} - {ts} => {dt}")
delta_seconds = abs(timestamps[1] - timestamps[0])
delta_time = abs(datetimes[1] - datetimes[0])
delta_microseconds = delta_seconds * 1_000_000
print(f"\nDifference between token 1 and 2:")
print(f" {delta_seconds:.6f} seconds")
print(f" {delta_microseconds:.0f} microseconds")
print(f" {delta_time} (timedelta)")
t0, t1, t2 = timestamps
if min(t0, t1) < t2 < max(t0, t1):
print("\nThe third token is between the first and second.")
else:
print("\nThe third token is NOT between the first and second.")
else:
print("check() failed: reverse_uniqid(uniqid(t)) != t")
The victim’s token timestamp is confirmed to be between the two attacker-controlled ones.
2. Use the Date
Header from the Server
In some situations where precise timestamps are hard to determine (or when you don’t have access to reverse-controlled tokens), you can fall back on the HTTP Date
header (Introduced in HTTP/1.1). This value is usually added by a proxy or the application server and reflects a stable, server-synced clock.
Example:
Date: Mon, 30 Jun 2025 18:01:46 GMT
Reading the Date
header to sync with server-side token generation time.
The overall workflow is similar to what we demonstrated earlier: use the timestamp as a base, generate token guesses, and attempt bruteforce.
However, this technique is more effective when the application uses token generation functions based on seconds rather than microseconds (e.g., a custom time()
-based scheme instead of uniqid()
).
This is because the Date
header itself only provides precision up to the second, so using it for uniqid()
-based tokens would result in a wide and inefficient brute-force range.
Bonus Tip: Convert Burp Request to Python
Burp Suite includes an extension that allows you to copy an HTTP request as a Python script, making it easier to paste on your script to automate the attack flow.
Burp Suite feature to convert requests into Python scripts.
Right-click on the request, then choose "Copy as requests" from the Extensions menu.
Paste the request into your python file
Reset Tolkien: Automate the Attack
Tom presented his open-source tool Reset Tolkien, which automates the full attack chain:
- Detection of time-based token generation
- Sandwich bounding
- Efficient bruteforce across narrow windows
You can test it on a demo app provided by him using Docker (this was tested on Kali Linux):
sudo apt update && sudo apt install docker.io docker-compose -y
wget https://www.aeth.cc/public/workshop-lehack-2025.tar.gz
tar -xvf workshop-lehack-2025.tar.gz
cd workshop-lehack-2025/workshop-step-1
sudo docker-compose up --build -d
Visit localhost:8000 to explore:
For this demo, Level 1 will be used (uniqid).
Follow the instructions in the GitHub repo to use the tool with each vulnerable scenario.
This article only covers the first scenario, more details on the other scenarios on Tom's blog.
Capture the request using Burp Suite, then reproduce the attack using Burp Repeater as previously described. Extract the three tokens from Burp Suite and insert them into the Python script. Finally, use the Reset Tolkien tool to generate the list of possible tokens (the tool offers many more features).
python3 script.py
reset-tolkien sandwich -bt 1751321547.343637 -et 1751321547.504555 -o output.txt --token-format="uniqid" token
sort -u output.txt > tokens.txt
ffuf -w tokens.txt:FUZZ -u 'http://localhost:8000/?level=1&token=FUZZ' -fs 2886
Go Further
This post only scratches the surface of what was covered in the workshop.
There are many other ways timestamps can leak into token generation — including hashed timestamps
, UUIDv1
, or even MongoDB ObjectIDs
— each with their own quirks and attack paths.
The tool presented, Reset Tolkien, is far more powerful than what we covered here. It supports:
- Multiple token formats (including MD5 and SHA-based derivations)
- Detection strategies for different token sources
- and more !
👉 Check out the full project and details here: https://www.aeth.cc/
Final Thoughts
It was a real pleasure attending this workshop — I learned a lot about subtle time-based flaws and how they can lead to impactful vulnerabilities like account takeovers.
If you're working in AppSec, doing bug bounty, or simply curious about token security, this is definitely a topic worth diving into.
Big thanks to Tom Chambaretaud for the quality of his explanations, the depth of the workshop, and the open-source tooling he shared. And hats off to the organizers of leHACK for making this kind of hands-on training available — even at 3AM.
Some of the diagrams and phrasing in this article are adapted—or directly screenshotted—from Tom Chambaretaud's original documentation and workshop material.