- What: Analysis of PHP's random number generator and its vulnerability to seed reconstruction
- Impact: Developers using PHP's default PRNG may be at risk of predictable random numbers
getting the pid from random numbers php, until this day, uses the mersenne twister algorithm as its default pseudo random number generator. it is not cryptographically secure, as its state can be reconstructed from at most 624 consecutive outputs. cracking the seed of the marsenne twister has been proven possible not only in php but also in python , which also uses this algorithm as its default random number generator. i want to take a different approach on attacking this prng in php and reverse-engineer how the initial seed for the algorithm is generated. specifically, i will focus on php 7.0 which took a very interesting approach for generating the seed. it turns out that it is possible to determine the process id of the php process which generated a random number if we know exactly when it's called. finding the seed of the seed in order to find out how php generates the seed for the random number generator, we need to look in the php-src repository which holds the implementation of the php built-in functions /* {{{ proto int mt_rand([int min, int max]) Returns a random number from Mersenne Twister */ PHP_FUNCTION(mt_rand) { <SNIP> if (!BG(mt_rand_is_seeded)) { php_mt_srand(GENERATE_SEED()); } <SNIP> the above code snippet is the implementation of the php function mt_rand . i removed parts of the code to only show the important part - the seed generation. we see that if a seed is needed, which is on every first call of the function in a new process, the result of GENERATE_SEED() function is supplied to the seeding function. #define GENERATE_SEED() (((zend_long) (time(0) * getpid())) ^ ((zend_long) (1000000.0 * php_combined_lcg()))) it turns out that this function is actually a macro which combines the current time, process id and the current value of php_combined_lcg() . php_compined_lcg so far we see that the seed for the default random number generator in php 7.0 depends on the current time and pid. this information does not help us much as long as we don’t understand how php_combined_lcg works. understanding the inner workings of the algorithm itself, i.e. the linear congruential generator , is not needed to break the algorithm - we just have to analyse its implementation. PHPAPI double php_combined_lcg(void) /* {{{ */ { php_int32 q; php_int32 z; if (!LCG(seeded)) { lcg_seed(); } MODMULT(53668, 40014, 12211, 2147483563L, LCG(s1)); MODMULT(52774, 40692, 3791, 2147483399L, LCG(s2)); z = LCG(s1) - LCG(s2); if (z < 1) { z += 2147483562; } return z * 4.656613e-10; } the above code shows how each consecutive value of the lcg is calculated. just as with the implementation of mt_rand , the initial state of the lcg must be initialized using its respective function lcg_seed() . let’s look at its implementation: static void lcg_seed(void) /* {{{ */ { struct timeval tv; if (gettimeofday(&tv, NULL) == 0) { LCG(s1) = tv.tv_sec ^ (tv.tv_usec<<11); } <SNIP> LCG(s2) = (zend_long) getpid(); <SNIP> if (gettimeofday(&tv, NULL) == 0) { LCG(s2) ^= (tv.tv_usec<<11); } <SNIP> } well what do we have here - the seeding function for the lcg is again dependent on the time and the current pid. putting seed generators together all together we know: php uses GENERATE_SEED() to seed the mt_rand which combines the time, pid and the output of the php_combined_lcg time(0) returns the current unix time in seconds php_combined_lcg is initialized using the current time and the pid gettimeofday and its tv_usec attribute returns the microseconds of the current unix time this function is called twice for ‘more entropy’. the time difference between the calls in my tests is less than 10us. given that each php request is executed in the same thread and shares the pid, we need to only guess it once in order to reverse-engineer the seed of an mt_rand instance in php we need: the output of the random number generator a somewhat precise time of when the number was generated. having this information, we can brute-force the possible values for the pid and the microseconds used in lcg_seed and figure out which configuration returns our number brute-forcing pid consider the simple php code: <?php echo "start: ".microtime().PHP_EOL; echo "rand: ". mt_rand().PHP_EOL; echo "end: ".microtime().PHP_EOL; ?> with the following output: start: 0.80655400 1776516932 rand: 1917976159 end: 0.80658100 1776516932 based on what we know about how the mt_rand function in php, the above code yields enough information for us to determine the pid of the process which handled the above code. we can reuse the implementation of the GENERATE_SEED() macro and php_combined_lcg() and parameterize it such that it does not use the true time and pid, but values we provide. then, we can construct something like this: long unix_time = 1776516932; // iterate between the possible microsecond values usec1 to usec2 for (int i = 806554; i <= 806581; i++) { // go in through the '10us window' between // the two calls of gettimeofday in php_combined_lcg for (int j = i; j <= i+10; j++) { // it...