Python service generates arbitrary amount of random numbers.
On the URL http://ctf.phdays.com:12391/admin/ we found the Django admin page.
So, we tried http://ctf.phdays.com:12391/password_reset/ and it worked:
IF email@example.com <or userX@domain.com> exists the link will be 'http://example.com/reset/0-str(hashlib.md5(str('%.16f'%random.random())).hexdigest())/'
Looks like modified password reset algo – firstname.lastname@example.org worked ok.
As we know – Python uses modified Mersenne twister for random generation, so it’s not cryptographically secure and we can predict or find some values having it’s state. Every random number gives us 2 state values of 624 total. Then algo should look like:
- Get 312 floats
- Fetch reset link
- Restore MT state from the floats
- Predict reset link value
But it didn’t work, and we were too tired to investigate the reason, so more stupid way was used.
def gen_rand(a,b): a ^= (a >> 11) a ^= (a << 7) & 2636928640 a ^= (a << 15) & 4022730752 a ^= (a >> 18) b ^= (b >> 11) b ^= (b << 7) & 2636928640 b ^= (b << 15) & 4022730752 b ^= (b >> 18) a = a >> 5 b = b >> 6 return ((a*67108864.0+b)*(1.0/9007199254740992.0))
Algo described was modified for using with map-reduce.
Final exploit looks like (thx @touzoku):
- We fetch 312 random numbers (known)
- We run password_reset for selected user (unknown)
- Fetch 312 more random numbers (known)
Now we have 624 known numbers and need to find 313th unkown.
- Precompute _state_2_1 and _state_2_2 (according to PoC from PT)
- Map it to our map-reduce cluster and get candidates for a and b
- Run reduce and generate 256 random numbers one of which is correct
- Having correct number, we can reset users password and enter the admin interface to get the flag