本篇博客主要介绍了php伪随机数的相关内容!!!

相关函数

mt_rand()

mt_rand函数有两个可选参数 min 和 max,如果没有提供可选参数,mt_rand函数将返回返回 0 到 mt_getrandmax() 之间的伪随机数。例如想要 5 到 15(包括 5 和 15)之间的随机数,用 mt_rand(5, 15)。

mt_srand()

用于播下一个更好的随机数发生器种子,用 seed 来给随机数发生器播种。 没有设定 seed 参数时,会被设为随时数。自 PHP 4.2.0 起,不再需要用 srand()mt_srand() 给随机数发生器播种 ,因为现在是由系统自动完成的。

这张图感觉挺好的

一个题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 <?php
show_source(__FILE__);
include "flag.php";
$a = @$_REQUEST['hello'];
$seed = @$_REQUEST['seed'];
$key = @$_REQUEST['key'];

mt_srand($seed);
$true_key = mt_rand();
if ($key == $true_key){
echo "Key Confirm";
}
else{
die("Key Error");
}
eval( "var_dump($a);");
?>

可以看出来这个题目传了三个参数,seed可控,因此我们只需在phpstudy选择相同的php版本设定相同的seed,输出产生的随机数就行

php版本差别

php5和php7会用不同的算法来生成随机数,所以即使seed相同生成的随机数也不相同

php_mt_seed

php_mt_seed是一个破解mt_rand函数seed的工具,在最简单的调用模式下,它能通过mt_rand第一次输出的值寻找mt_rand的seed,在更高级的模式中它能匹配不是第一次输出的和不明确具体输出的情况。

p_mt_seed基于命令行运行,命令行可以使用1,2,4或者更多的参数。这些参数需要详细说明mt_rand()的输出。

1
2
git clone https://github.com/lepiaf/php_mt_seed.git
make

4.0版本从这里下载:https://www.openwall.com/php_mt_seed/php_mt_seed-4.0.tar.gz

一个参数

当只有一个参数的时候,这个参数代表mt_rand第一次输出的值。

两个参数

当有两个参数的时候,他们代表mt_rand第一次输出应该位于什么区间内。

第一个参数为最小值,第二个参数为最大值。

四个参数(高级模式)

前两个参数表示mt_rand输出的值,后两个参数表示mt_rand输出的区间(从0开始)。

多于五个参数(高级模式)

每四个参数一组,但是最后一组可以是1,2或4个参数,按照上面的格式。

详细使用参见:https://www.openwall.com/php_mt_seed/README

简单使用

这里用的是php7.0

假设有这样一个生成随机序列的算法,生成了下图的随机数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
function user_password($length = 10) {
$allowable_characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$len = strlen($allowable_characters) - 1;
$pass = '';
for ($i = 0; $i < $length; $i++) {
$pass .= $allowable_characters[mt_rand(0, $len)];
}
return $pass;
}

mt_srand(time());
echo user_password(), "\n";
echo user_password(), "\n";
echo user_password(), "\n";
?>

写一个程序来转换生成的随机数

1
2
3
4
5
s="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
pwd="UHsI89HQVr"
for i in pwd:
print(str(s.index(i))+" "+str(s.index(i))+" 0 61 ",end="")
//结果:46 46 0 61 33 33 0 61 18 18 0 61 34 34 0 61 60 60 0 61 61 61 0 61 33 33 0 61 42 42 0 61 47 47 0 61 17 17 0 61

然后我们验证一下用爆破出来的seed是否能生成相同的序列

可以看到成功实现

Discuz X3.3 authkey生成算法漏洞

用户在初次安装软件时,系统会自动生成一个authkey写入全局配置文件和数据库,之后安装文件会被删除。该authkey用于对普通用户的cookie进行加密等密码学操作,但是由于生成算法过于简单,可以利用公开信息进行本地爆破。authkey生成算法位于Discuz_X3.3_SC_UTF8/upload/install/index.php

authkey的生成方法如下:

1
$authkey = substr(md5($_SERVER['SERVER_ADDR'].$_SERVER['HTTP_USER_AGENT'].$dbhost.$dbuser.$dbpw.$dbname.$username.$password.$pconnect.substr($timestamp, 0, 6)), 8, 6).random(10);

可以看出authkey主要由两部分组成:

MD5的一部分(前6位) + random生成的10位

跟入random函数

1
2
3
4
5
6
7
8
9
10
function random($length) {
$hash = '';
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
$max = strlen($chars) - 1;
PHP_VERSION < '4.2.0' && mt_srand((double)microtime() * 1000000);
for($i = 0; $i < $length; $i++) {
$hash .= $chars[mt_rand(0, $max)];
}
return $hash;
}

由于字符生成集合是固定的,且没有重复字符,那么函数中每一次生成hash都唯一对应了chars数组中的一个位置,而且是使用同一个seed生成的。

在之后的代码中使用了同样的random函数:

1
$_config['cookie']['cookiepre'] = random(4).'_';

Cookie的前四个字节是已知的,并且使用了同样的random函数,那么思路很明显:

通过已知的4位,算出random使用的种子,进而得到authkey后10位。那剩下的就需要搞定前6位,根据其生成算法,只好选择爆破的方式,由于数量太大,就一定要选择一个本地爆破的方式(即使用到authkey而且加密后的结果是已知的)。

在调用authcode函数很多的地方都可以进行校验,在这里使用找回密码链接中的id和sign参数:

sign生成的方法如下:

1
2
3
function dsign($str, $length = 16){
return substr(md5($str.getglobal('config/security/authkey')), 0, ($length ? max(8, $length) : 16));
}

爆破authkey 的流程:

  1. 通过cookie前缀爆破随机数的seed。使用php_mt_seed工具。
  2. 用seed生成random(10),得到所有可能的authkey后缀。
  3. 给自己的账号发送一封找回密码邮件,取出找回密码链接。
  4. 用生成的后缀爆破前6位,范围是0x000000-0xffffff,和找回密码url拼接后做MD5求出sign。
  5. 将求出的sign和找回密码链接中的sign对比,相等即停止,获取当前的authkey。

mt_rand()的一种新思路

参见Hgame上面的一道题:https://www.moonback.xyz/2020/01/23/HGAME%E6%AF%94%E8%B5%9B%E9%83%A8%E5%88%86writeup/#%E4%BA%8C%E5%8F%91%E5%85%A5%E9%AD%82%EF%BC%81

rand()函数安全

后来了解到这个函数在某些条件下生成的随机数也是可以预测的

先说下在linux下,PHP rand函数在底层使用的是glibc rand(),它会保留前面生成随机数的数据,作为后面随机数生成的依据,以此保证伪随机数的均匀性,但这样会导致严重的安全问题,也就是如果我们知道前面生成的随机序列,那么完全可以预测后面的随机数,公式:

1
num[n] = (num[n-3] + num[n-31]) mod (MAX)

举个例子:

1
2
3
4
5
6
7
8
9
10
<?php
$num = array();
//生成50个伪随机数
for ($i=0; $i < 50; $i++) {
$num[$i] = rand(0,100000);
}
for ($i=31; $i < 50; $i++){
echo "第".($i+1)."个随机数:".$num[$i]."\t"."向前数第三个数:".$num[$i-31]."\t向前数第31个数:".$num[$i-3]."\t%计算后:".(($num[$i-31]+$num[$i-3])%100000)."\n";
}
?>

可以看到虽然有可能有误差,但误差都为1,gtfly大佬给的提示可以能精度的问题感觉很对

再看一下windows,是无法用上面的方式预测的,但是可以爆破

详细看:https://github.com/Sjord/crack-ezchatter-token

参考:

https://www.freebuf.com/vuls/192012.html

https://blog.cfyqy.com/article/5aa79cea.html

https://xz.aliyun.com/t/31

http://wonderkun.cc/index.html/?p=585

https://xz.aliyun.com/t/1520#toc-10

评论