深入理解PHP中mt_rand()随机数的安全

阅读: 评论:0

深⼊理解PHP中mt_rand()随机数的安全
前⾔
在前段时间挖了不少跟mt_rand()相关的安全漏洞,基本上都是错误理解随机数⽤法导致的。这⾥⼜要提⼀下php官⽹manual的⼀个坑,看下关于mt_rand()的介绍:中⽂版^cn 英⽂版^en,可以看到英⽂版多
了⼀块黄⾊的 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()来⽣成安全令牌、核⼼加解密key等等导致严重的安全问题。
伪随机数
mt_rand()并不是⼀个真·随机数⽣成函数,实际上绝⼤多数编程语⾔中的随机数函数⽣成的都都是伪随机数。关于真随机数和伪随机数的区别这⾥不展开解释,只需要简单了解⼀点
伪随机是由可确定的函数(常⽤线性同余),通过⼀个种⼦(常⽤时钟),产⽣的伪随机数。这意味着:如果知道了种⼦,或者已经产⽣的随机数,都可能获得接下来随机数序列的信息(可预测性)。
简单假设⼀下 mt_rand()内部⽣成随机数的函数为: rand = seed+(i*10) 其中 seed 是随机数种⼦, i 是第⼏次调⽤这个随机数函数。当我们同时知道 i 和 rand 两个值的时候,就能很容易的算出seed的值来。
⽐如 rand=21 , i=2 代⼊函数 21=seed+(2*10) 得到 seed=1 。是不是很简单,当我们拿到seed之后,就能计算出当 i 为任意值时候的 rand 的值了。
PHP的⾃动播种
从上⼀节我们已经知道每⼀次mt_rand()被调⽤都会根据seed和当前调⽤的次数i来计算出⼀个伪随机数。⽽且seed是⾃动播种的:
2bkeyNote: ⾃ PHP 4.2.0 起,不再需要⽤ srand() 或 mt_srand() 给随机数发⽣器播种,因为现在是由系统⾃动完成的。
那么问题就来了,到底系统⾃动完成播种是在什么时候,如果每次调⽤mt_rand()都会⾃动播种那么破解seed也就没意义了。关于这⼀点manual并没有给出详细信息。⽹上了⼀圈也没靠谱的答案只能
去翻源码^mtrand了:
PHPAPI void php_mt_srand(uint32_t seed)
{
/* Seed the generator with a simple uint32 */
php_mt_initialize(seed, BG(state));
php_mt_reload();
/* Seed only once */
BG(mt_rand_is_seeded) = 1;
}
/* }}} */
/* {{{ php_mt_rand
*/
PHPAPI uint32_t php_mt_rand(void)
{
/* Pull a 32-bit integer from the generator state
Every other access function simply transforms the numbers extracted here */
register uint32_t s1;
if (UNEXPECTED(!BG(mt_rand_is_seeded))) {
php_mt_srand(GENERATE_SEED());
}
if (BG(left) == 0) {
php_mt_reload();
}
--BG(left);
s1 = *BG(next)++;
s1 ^= (s1 >> 11);
s1 ^= (s1 << 7) & 0x9d2c5680U;
s1 ^= (s1 << 15) & 0xefc60000U;
return ( s1 ^ (s1 >> 18) );
}
可以看到每次调⽤mt_rand()都会先检查是否已经播种。如果已经播种就直接产⽣随机数,否则调⽤php_mt_srand来播种。也就是说每个php cgi进程期间,只有第⼀次调⽤mt_rand()会⾃动播种。接下来
都会根据这个第⼀次播种的种⼦来⽣成随机数。⽽php的⼏种运⾏模式中除了CGI(每个请求启动⼀个cgi进程,请求结束后关闭。每次都要重新读取php.ini 环境变量等导致效率低下,现在⽤的应该不多了)
以外,基本都是⼀个进程处理完请求之后standby等待下⼀个,处理多个请求之后才会回收(超时也会回收)。
写个脚本测试⼀下
<?php
//pid.php
echo getmypid();
<?php
//test.php
$old_pid = file_get_contents('localhost/pid.php');
$i=1;
while(true){
$i++;
$pid = file_get_contents('localhost/pid.php');
if($pid!=$old_pid){
echo $i;
break;
}
}
测试结果:(windows+phpstudy)
apache 1000请求
nginx 500请求
当然这个测试仅仅确认了apache和nginx⼀个进程可以处理的请求数,再来验证⼀下刚才关于⾃动播种的结论:
<?php
//pid1.php
if(isset($_GET['rand'])){
echo mt_rand();
}else{
echo getmypid();
}
<?php
//pid2.php
复合片钻头
echo mt_rand();
<?php
//test.php
$old_pid = file_get_contents('localhost/pid1.php');
echo "old_pid:{$old_pid}\r\n";
while(true){
$pid = file_get_contents('localhost/pid1.php');
if($pid!=$old_pid){
led线形投光灯echo "new_pid:{$pid}\r\n";
for($i=0;$i<20;$i++){
$random = mt_rand(1,2);
echo file_get_contents("localhost/pid".$random.".php?rand=1")." ";
}
break;
}
}
通过pid来判断,当新进程开始的时候,随机获取两个页⾯其中⼀个的 mt_rand() 的输出:
苜蓿根
old_pid:972 new_pid:7752 1513334371 2014450250 1319669412 499559587 117728762 1465174656 1671827592 1703046841 464496438 1974338231 46646067 981271768 1070717272 571887250 922467166 606646473 134605134 857256637 1971727275 210拿第⼀个随机数 1513334371 去爆破种⼦:
smldhz@vm:~/php_mt_seed-3.2$ ./php_mt_seed 1513334371 Found 0, trying 704643072 - 738197503, speed 28562751 seeds per second seed = 735487048 Found 1, trying 1308622848 - 1342177279, speed 28824291 seeds per second seed = 1337331453 Fo 爆破出了3个可能的种⼦,数量很少⼿动⼀个⼀个测试:
<?php
mt_srand(735487048);//⼿⼯播种
for($i=0;$i<21;$i++){
echo mt_rand()." ";
}
输出:
369ii所以我们得到结论:
php的⾃动播种发⽣在php cgi进程中第⼀次调⽤mt_rand()的时候。跟访问的页⾯⽆关,只要是同⼀个进程处理的请求,都会共享同⼀个最初⾃动播种的种⼦。
php_mt_seed
我们已经知道随机数的⽣成是依赖特定的函数,上⾯曾经假设为rand = seed+(i*10) 。对于这样⼀个简单的函数,我们当然可以直接计算(⼝算)出⼀个(组)解来,但 mt_rand() 实际使⽤的函数可是相当
复杂且⽆法逆运算的。有效的破解⽅法其实是穷举所有的种⼦并根据种⼦⽣成随机数序列再跟已知的随机数序列做⽐对来验证种⼦是否正确。php_mt_seed^phpmtseed就是这么⼀个⼯具,它的速度⾮常
快,跑完2^32位seed也就⼏分钟。它可以根据单次mt_rand()的输出结果直接爆破出可能的种⼦(上⾯有⽰例),当然也可以爆破类似mt_rand(1,100)这样限定了MIN MAX输出的种⼦(下⾯实例中有⽤
到)。
安全问题
说了这么多,那到底随机数怎么不安全了呢?其实函数本⾝没有问题,官⽅也明确提⽰了⽣成的随机数不应⽤于安全加密⽤途(虽然中⽂版本manual没写)。问题在于开发者并没有意识到这并不是⼀个
真·随机数。我们已经知道,通过已知的随机数序列可以爆破出种⼦。也就是说,只要任意页⾯中存在输出随机数或者其衍⽣值(可逆推随机值),那么其他任意页⾯的随机数将不再是“随机数”。常见的
输出随机数的例⼦⽐如验证码,随机⽂件名等等。常见的随机数⽤于安全验证的⽐如回密码校验值,⽐如加密key等等。⼀个理想中的攻击场景:
夜深⼈静,等待apache(nginx)收回所有php进程(确保下次访问会重新播种),访问⼀次验证码页⾯,根据验证码字符逆推出随机数,再根据随机数爆破出随机数种⼦。接着访问回密码页⾯,⽣成的
回密码链接是基于随机数的。我们就可以轻松计算出这个链接,回管理员的密码…………XXOO
实例
PHPCMS MT_RAND SEED CRACK致authkey泄露⾬⽜写的⽐我好,看他的就够了
Discuz x3.2 authkey泄露这个其实也差不多。官⽅已出补丁,有兴趣的可以⾃⼰去分析⼀下。
总结
非隔离电源模块以上就是这篇⽂章的全部内容了,希望本⽂的内容对⼤家的学习或者⼯作具有⼀定的参考学习价值,如果有疑问⼤家可以留⾔交流,谢谢⼤家对的⽀持。

本文发布于:2023-05-16 13:50:54,感谢您对本站的认可!

本文链接:https://patent.en369.cn/patent/4/102012.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:进程   请求   播种   序列   找回   没有   函数   问题
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 369专利查询检索平台 豫ICP备2021025688号-20 网站地图