mt_rand()工作原理
这里引用一下php手册
mt_rand( void) : int
mt_rand( int $min, int $max) : int
很多老的 libc 的随机数发生器具有一些不确定和未知的特性而且很慢。PHP 的 rand() 函数默认使用 libc 随机数发生器。mt_rand() 函数是非正式用来替换它的。该函数用了 » Mersenne Twister 中已知的特性作为随机数发生器,它可以产生随机数值的平均速度比 libc 提供的 rand() 快四倍。
如果没有提供可选参数 min 和 max,mt_rand() 返回 0 到 mt_getrandmax() 之间的伪随机数。例如想要 5 到 15(包括 5 和 15)之间的随机数,用 mt_rand(5, 15)。
提到了mt_rand()就要说一下mt_srand()函数:
mt_srand([ int $seed] ) : void
用 seed 来给随机数发生器播种。 没有设定 seed 参数时,会被设为随时数。使用者在进行一次mt_srand()操作后,seed数值将被固定下来,给接下来的mt_rand()函数使用。
mt_rand()存在的问题
由于mt_rand()的生成的随机数只跟seed和调用该函数的次数有关。举一个简单的例子来说明一下这个问题,假设使用mt_srand(1111111)进行了一次播种操作,接下来调用mt_rand()函数,第一次生成的数值为a,第二次生成的为b,第三次生成的为c。任何一个人拿到这样的一串代码,所执行的结果都是跟刚刚描述的一样。所以当你的seed数值被他人知道后,就可以预测出你接下来的数值是多少,这就是该函数的一个问题,他并不能起到一个真随机数的作用。
说到mt_rand() 和mt_srand(),这里也顺便提及rand()和 srand()
我们看如下代码:
<?php
header("Content-Type:text/html;charset=utf-8");
mt_srand(1234);
srand(123);
echo "rand在种子是123时产生的随机数序列:\n";
for($i = 0; $i < 5 ;$i++){
echo rand()."\n";
}
echo "srand在种子是1234时产生的随机数序列:\n";
for($k=0 ;$k < 5; $k++){
echo mt_rand()."\n";
}
?>
我们运行可以看到:
当种子固定时,每次运行test.php
时,mt_rand
和rand()
所产生的的随机数分别是一样的,所以如果我们在代码中自己播种了随机数种子,但是泄露了这个种子,就会导致产生的随机数序列被别人猜到,造成安全问题。
Note: 自 PHP 4.2.0 起,不再需要用 srand() 或 mt_srand() 给随机数发生器播种 ,因为现在是由系统自动完成的。
每一次mt_rand()都会随机进行播种?
每个php cgi进程期间,只有第一次调用mt_rand()会自动播种。接下来都会根据这个第一次播种的种子来生成随机数。而php的几种运行模式中除了CGI(每个请求启动一个cgi进程,请求结束后关闭。每次都要重新读取php.ini
环境变量等导致效率低下,现在用的应该不多了)以外,基本都是一个进程处理完请求之后standby等待下一个,处理多个请求之后才会回收(超时也会回收)。
如何利用?
这里我们是可以通过爆破随机数来得到种子的:
mt_rand()和rand()产生的最大随机数都是2^31-1
,这里通过php_mt_rand
进行爆破https://github.com/lepiaf/php_mt_seed.git
这里我直接使用mt_rand()生成一个随机数(也是随机种子,通过这个工具来爆破种子)
可以看到利用爆破得到的种子果然得到了之前那个随机数(可能爆破有偏差),这说明我们之前mt_rand()
使用的种子很有可能就是其二中的一个!
利用
下面看这段代码:
<?php
function wp_generate_password($length = 12, $special_chars = true) {
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
if ( $special_chars )
$chars .= '!@#$%^&*()';
$password = '';
for ( $i = 0; $i < $length; $i++ )
$password .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
return $password;
}
$key = wp_generate_password(16, false);
echo "[*] This is a key for public:".$key."\n";
$private = wp_generate_password(10,false);
echo "[*] Create a private key which you don't know:".$private."\n";
?>
大致是通过mt_rand
产生一个12位数的公钥和私钥,那么我们能否通过公钥来推测出私钥,或者是私钥推测出公钥呢?答案是可以的,我们先运行一下。
我们要做的就是先将公钥的字符对应成相应的随机数(因为字符是从字符串中按mt_rand()产生的随机数来分配的,我们倒退即可)
<?php
$pub = 'uS66FDD9LCR62UV3';
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
for($i = 0; $i < strlen($pub) ;$i++){
$pos =strpos($chars, $pub[$i]);
echo $pos.' '.$pos.' '.'0 '.(strlen($chars) - 1).' ';//将其保存为是php_rand_seed的默认格式
/*
php_mt_seed输入数字可以使每四个为一组,中间以一个空格为间隔,前面两个数是随机数的结果区间,后两位是随机数的随机范围区间(0-strlen($pub)-1)
*/
}
echo "\n";
?>
现在开始爆破
得到种子后,我们手工添加mt_srand()
:
和刚才一模一样,这样我们拿到公钥就能够通过种子得到私钥了!
CTF实题
<?php
//生成优惠码
$_SESSION['seed']=rand(0,999999999);
function youhuima(){
mt_srand($_SESSION['seed']);
$str_rand = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$auth='';
$len=15;
for ( $i = 0; $i < $len; $i++ ){
if($i<=($len/2))
$auth.=substr($str_rand,mt_rand(0, strlen($str_rand) - 1), 1);
else
$auth.=substr($str_rand,(mt_rand(0, strlen($str_rand) - 1))*-1, 1);
}
setcookie('Auth', $auth);
}
?>
比如我们现在有一条优惠码为:
youhuima = "hM7HljJR5ZHzWGF"
生成优惠码的字符串范围为
$str_rand = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
现在给了15位的优惠码,但是要求我们返回24位优惠码的完整形式,上述PHP代码是取前7位从左向右的随机数来生成优惠码,我们可以直接就用优惠码的前七位即可:
<?php
$str = "hM7HljJ";
$str_rand = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for($i = 0; $i < strlen($str); $i++){
$pos = strpos($str_rand, $str[$i]);
echo $pos.' '.$pos.' '.'0 '.(strlen($str_rand)-1).' ';
}
echo "\n";
?>
爆破seed:
这样我们得到种子后在将优惠码拓展至24位即可,只要设置种子mt_srand(824990031)
即可得到完整优惠码。
参考链接:
https://xz.aliyun.com/t/31#toc-3
http://wonderkun.cc/index.html/?p=585#comment-3255