PHP伪随机数 | mt_rant()的浅分析

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_randrand()所产生的的随机数分别是一样的,所以如果我们在代码中自己播种了随机数种子,但是泄露了这个种子,就会导致产生的随机数序列被别人猜到,造成安全问题。

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

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值