php伪随机数

前言

前段时间因为和伙伴搞其他事情了没有把精力放在技术学习上,这几天有空了才开始学习,学的东西还比较少。所以这次就写一下PHP伪随机数漏洞。

正文

PHP里面的伪随机数主要是因为mt_rand()这个函数,其实这个函数生成的随机数并不是绝对随机。这个在php manual(英文版)里面有说

Caution:This function does not generate cryptographically secure values, and should not be used for cryptographic purposes. If you need a cryptographically secure value, consider using random_int(), random_bytes(), or openssl_random_pseudo_bytes() instead.

意思是说注意函数的安全,不能用来生成密码安全值,不要引用于加密,如果需要可以用其他函数代替。然而中文版的手册没有这句话。而现在大部分工程师开发都是看中文版的手册。这就导致了伪随机数漏洞的出现。来看看 mt_rand()这个函数

mt_rand

(PHP 4, PHP 5, PHP 7)

mt_rand — 生成更好的随机数


说明

int mt_rand( void)

int mt_rand( int $min, int $max)

很多老的 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)。 


参数


min
可选的、返回的最小值(默认:0) 
max
可选的、返回的最大值(默认:mt_getrandmax()) 



返回值

返回 min (或者 0) 到 max (或者是到 mt_getrandmax() ,包含这个值)之间的随机整数。

根据传入参数类型的不同会有不同的随机数。参数为空的话生成的是 0 到 mt_getrandmax() 之间的伪随机数,如果提供两个参数max 和min那么将会生成一个介于min和max之间的伪随机数(包括min和max)。

先来理清一下这个函数的运行细节,首先是判断是否有随机数种子,如果没有提供随机数种子,那么系统会自己生成一个随机数种子,然后再生成随机数。如果随机数种子提供给了系统,那么随机数就是根据种子的值和取随机数的次数计算出来的。可以理解为随机数是一个含随机数种子 seed 和运行次数 n 两个参数的复杂算法(其中可能包括线性同余法、单向散列函数法、密码法等;这个算法根据php版本的不同而变化,但是PHP 3.0.7 到5.2.0,PHP 5.2.1 到 7.0.x, 和 PHP 7.1.0+三段版本的公式是各自相同的)

需要注意的是:这个随机数的产生都是依赖于随机数种子的,就是说一开始提供的随机数种子相同的话,那么后面每次产生的随机数都是相同的。

所以这个漏洞的利用思路是只要知道了随机数种子和php版本,就可以知道之后的每一个随机数了

利用方法

接下来就是怎么知道随机数种子的值,可以利用php_mt_seed这个工具,提供一个下载链接https://www.openwall.com/php_mt_seed/

下载下来在win下编译目测通过不了,缺少sys/times.h模块,网上说这个模块是unix独有的?

最后我把它放到了linux里面编译

gcc php_mt_seed.c -o php_mt_seed

现在来看一下这个工具的接收参数

一个参数

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

两个参数

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

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

四个参数(高级模式)

前两个参数表示mt_rand第一次输出的区间,后两个参数表示mt_rand输出的区间。

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

每四个参数一组,但是最后一组可以是1,2或4个参数。每一组引用对应的输出。

所以总体思路是:获得第一次出现的随机数 — 利用php_mt_seed获取随机数种子seed — 用种子来生成任意次的随机数 — 进而判断出每一次的随机数具体值

先来看一下演示

<?php
echo mt_rand();
?>

得到的结果是

现在通过第一次随机数结果来得到随机种子

跑得比较慢,不过跑到这里已经可以知道随机数种子了

先看一下我的php版本

我的php版本是5.5.9,那么对应的随机数种子是 436886441 或者 1356202521 两个都可以

<?php
echo "set seed:436886441"."\n";
mt_srand(436886441);
echo mt_rand();

?>

得到的结果

没错,第一个随机数又是43962452

实战题目

2019年GWCTF的一道web题目–枯燥的抽奖 就是考的伪随机数漏洞,复现在buuoj里面

控制台中可以得到check.php代码

<?php
#这不是抽奖程序的源代码!不许看!
header("Content-Type: text/html;charset=utf-8");
session_start();
if(!isset($_SESSION['seed'])){
$_SESSION['seed']=rand(0,999999999);
}

mt_srand($_SESSION['seed']);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
    $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);       
}
$str_show = substr($str, 0, 10);
echo "<p id='p1'>".$str_show."</p>";


if(isset($_POST['num'])){
    if($_POST['num']===$str){x
        echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>";
    }
    else{
        echo "<p id=flag>没抽中哦,再试试吧</p>";
    }
}
show_source("check.php");

意思是用 mt_rand() 生成一个20位的密码,然后给出了前十位,叫猜整串密码是多少

mt_rand() 多次被调用,所以用多个结果一起回溯随机数种子

可以看到随机数结果被处理过了,现在要恢复到原来的随机数,exp如下

<?php
$pass_now = "W0dCwtjQ3A";
$allowable_characters = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$len = strlen($allowable_characters) - 1;
for ($j = 0; $j < strlen($pass_now); $j++) {
    for ($i = 0; $i < $len; $i++) {
        if ($pass_now[$j] == $allowable_characters[$i]) {
            echo "$i $i 0 $len ";
            break;
        }
    }
}

得到

21 21 0 61 32 32 0 61 22 22 0 61 40 40 0 61 40 40 0 61 25 25 0 61 58 58 0 61 5 5 0 61 51 51 0 61 31 31 0 61

然后再linux下用php_mt_seed拿到随机数种子(kali和ubuntu都试了跑起来还是很慢)

./php_mt_seed 21 21 0 61 32 32 0 61 22 22 0 61 40 40 0 61 40 40 0 61 25 25 0 61 58 58 0 61 5 5 0 61 51 51 0 61 31 31 0 61

跑出来种子是149108886,拉过去放置然后生成随机数,前20个随机数按题目进行处理就可以得到密码

<?php
mt_srand(149108886);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
    $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);       
}

echo $str;

最后结果是

结尾

这样的情况不止在php里面出现,在python里面也同样存在

python里面有个random库是用来生成随机数的,生成随机数部分代码如下

random.setstate()
random.randint(min,max)
random.random()

如果setstate()放置的随机数种子被知道,那和php一样,接下来的所有随机数也被知道了

演示如下

x=getstate()得到的随机数种子再重新用random.setstate(x)放置,最后每一次得到的随机数都是一样的


在易语言之中的规则也是这样

.版本 2
.支持库 spec

.程序集 窗口程序集_启动窗口
.程序集变量 i, 整数型

.子程序 _按钮1_被单击

i = i + 1
置随机数种子 (54154885 + i)
调试输出 (“输出1:” + 到文本 (取随机数 (1, 100)))

.子程序 __启动窗口_创建完毕



.子程序 _按钮2_被单击

i = i + 1
置随机数种子 (54154885 + i)
调试输出 (“输出2:” + 到文本 (取随机数 (1, 100)))


.子程序 _按钮3_被单击

i = 0

结果如下

不知道其他语言是不是都是这样(怂.jpg)

参考链接

https://www.cnblogs.com/fanqiusha1988/p/13322371.html

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

https://yml-sec.top/

说点什么
支持Markdown语法
好耶,沙发还空着ヾ(≧▽≦*)o
Loading...