DVCTF 2025 - WEB LouvreArchives
In this challenge, the goal is to access flag.webp
as indicated in the source code.
While exploring the page, I noticed a long base64-encoded string. Once decoded and reorganized, it contained several .webp
images with unusual names like 1991781613.webp
, 1273827392.webp
, etc.
Checking the source code:
We understand that filenames are generated using Python’s getrandbits(32)
, which produces 32 random bits via the random.getrandbits
function.
The interesting point here is that this function uses the Mersenne Twister generator, a PRNG (Pseudo-Random Number Generator) that is not cryptographically secure.
Study Time: The Mersenne Twister Link to heading
The Mersenne Twister is a widely-used pseudo-random generator (for example, it’s the default in Python’s random
module).
It generates 32-bit integers based on an internal state composed of 624 integers of 32 bits. Once this state is known, all subsequent numbers can be accurately predicted.
However, there’s a catch: the generated values are “tempered”—that is, transformed by a function to improve their distribution. To reconstruct the internal state, you must “untemper” these values.
Exploitation Link to heading
Since we can observe more than 624 .webp
files named using getrandbits(32)
, we have enough outputs to reconstruct the generator’s internal state.
I used this repo: 👉 https://github.com/kmyk/mersenne-twister-predictor
What it does:
- Takes 624 outputs (file names, in this case),
- Applies the “untempering” algorithm to recover the original state,
- Allows us to predict future values of the generator.
Using this method, we retrieve the secret key used to sign the JWT:
1991781613
Crafting a Forged JWT Link to heading
Using this key, we can create a customized JWT with flag.webp
as our profile image:
import jwt, datetime
secret = '1991781613'
payload = {
'sub': 'user_id',
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1),
'iat': datetime.datetime.utcnow(),
'profilepicture': './images/flag.webp'
}
token = jwt.encode(
payload,
secret,
algorithm='HS256',
headers={'alg':'HS256','typ':'JWT'}
)
print(token)
Retrieving the Flag Link to heading
Replace the JWT cookie in Burp:
And we get: