TL;DR? Here.
PS: You need to do pip install z3-solver requests
for this to work.
Okay so a little confession: Before the CTF challenge, I didn’t think much of Cryptographically Secure Pseudo Random Generators (CSPRNGS), and thought they were just for very high security purposes, like defence against a state level hacking agency. I thought normal PRNGs were enough for day-to-day purposes and no one could realistically break it.
After this CTF…oh boy, it takes 30 mins to break PRNGs (the one used by Javascript in Chrome/Firefox) and I’m never going to use those for security again. I should’ve never…
Anyway, let’s get started!
Challenge
Challenge Name: JS Lotto
Challenge Description:
I found this lotto website called JS Lotto. Wanna test your luck? I heard if you guess all 5 numbers correctly, you can win a flag!
Link
So, there was this challenge where you had to enter 5 numbers between 1 to 1000 and the server would match these numbers with 5 randomly generated ones. It was sending a POST request:
|
|
Solution
After a bit of fiddling, I realized I had to break the random number generator and predict the numbers in future to win this challenge. From the name (JS Lotto) I guessed the backend was in JavaScript and Math.random()
was being used (cause this won’t be a break-random() anymore if a CSPRNG was being used).
After a bit of googling, I came to know XORShift128+ is used in JavaScript for generating random numbers. And it can be broken by a high-schooler. Which I am. Lucky.
I came across XorShift128Plus predictor Python code on Github which had the code for Google Chrome and Firefox based JS engines. (Safari is there too but broken).
So first I got 2 random numbers from the site using CURL (1 for using as a generation seed and one for validation):
|
|
The original code required the numbers to be in 0-1 range so I divided all numbers by 1000:
|
|
It worked with the default browser config (Chrome) but there was another issue in the code, that the first generated number (862.676114246725 in this case) was getting skipped. It was relatively easy to fix, on line 189, changed generated = []
to:
|
|
After it’s generated, I had to remultiply the numbers by 1000 by using:
|
|
Now, I did some print
statements to check if validation numbers were in generated
, and voila! It worked.
|
|
Now I had to just add in requests
and automate it, and print the flag. It was relatively simple:
|
|
Flag is: rtcp{th3_h0us3_d1dnt_w1n_th15_t1m3_5bcbf4}
. GGWP.
Conclusion
Note: It may not always work since the requests have to be consecutive. A fast internet will greatly help.
Here is the full code on GitHub Gist. Ask me in the comments for any issues!
It was a fun challenge and it made me realize: NEVER use non-cryptographically secure PRNGs for ANYTHING related to security. Thanks jammy for the challenge!