zoukankan      html  css  js  c++  java
  • 浅谈PHP随机数安全的分析

    之前在身边有很多学PHP的朋友写一些小程序的时候,很多时候会使用PHP随机数函数rand()和mt_rand()函数去生成随机数

    可是,随机数真的随机吗?这篇文章讲从多个实例中探讨随机数,当然,有写作不当的地方,还望斧正!

    关于随机函数rand()和mt_rand()

    rand()和mt_rand()两个函数皆是PHP中生成随机数的函数,可相比之下,mt_rand()的生成速度缺是rand()的四倍!在没有参数的情况下,两者生成的数值范围也是不一样的。

    echo mt_getrandmax()." and ".getrandmax();

    可以看到页面上的输出,mt_rand()相比较rand()默认取值的范围更大。而且经过测试,在不同的PHP版本中,rand随机数种子相同的时候,随机数缺不相同!

    左边web页面使用的是PHP7.0.12,而右边的Powershell却是使用的PHP5.2.17。从上所述,只有mt_rand()所生成的随机数是无版本差异的!

     而这两个生成随机数的时候,是可以通过srand()和mt_srand()设置随机数的种子

     1 <?php
     2 mt_srand(666);
     3 srand(666);
     4 echo "rand 函数在种子是666时产生的随机数序列:<br/>";
     5 for($i=1;$i<5;$i++){
     6    echo rand()."<br/>";
     7 }
     8 echo '<br/>';
     9 echo "mt_rand 函数在种子是666时产生的随机数序列:<br/>";
    10 for($i=1;$i<5;$i++){
    11    echo mt_rand()."<br/>";
    12 }
    13 ?>

     运行如下图所示

    当你发现,设置的种子为666的时候,不管你刷新多少次,页面中生成的随机数值都不会变!

     

    所以,当种子泄露的时候,随机数的安全就不堪一击了。

    可有什么方法能够得到种子?

    目前我们唯一能知道的就是随机数是不会变的,而之前有说过mt_srand()生成的随机数范围在0-2147483647之间,我们就可以通过最普通的爆破方式

    去获取随机数的种子,这里推荐使用php_mt_seed,项目地址:http://www.openwall.com/php_mt_seed/

    这里踩到个坑,生成后的随机数分为PHP7.x版本的和5.x版本,具体原因还不知道,但是在php7.x版本以上,mt_srand()函数定义的种子将不能超过int,也就是2147483647,但可以等于该值

    所以,为了更好的研究,下面将会使用PHP5.2.17的版本

    首先通过mt_rand()创建一个随机数,然后利用脚本爆破

    php -r "echo mt_rand();"

    爆破出来的种子所生成的随机数正好是一样的

    那么,页面只播种一次,生成的多个随机数也会一样吗

     

    事实证明,一次播种,全页不愁。

    竟然已经了解了随机数的相关安全性,下面就放上一些CTF中,或者在某个CMS中出现的有关随机数的审计

    审计案例①(mt_rand()安全性)

    此案例是出自第三届陕西网络空间安全技术大赛

     1 <?php
     2 error_reporting(0);
     3 function cpassword($pw_length = 10){
     4     $randpwd = "";
     5     for ($i = 0; $i < $pw_length; $i++)
     6     {
     7         $randpwd .= chr(mt_rand(33, 126));
     8     }
     9     return $randpwd;
    10 }
    11 
    12 session_start();
    13 mt_srand(time());
    14 $pwd=cpassword(10);
    15 if($pwd === $_GET['pwd'])
    16 {
    17     echo "Good job, you get the key is:".$pwd;
    18 }
    19 else{
    20     echo "Wrong!";
    21 }
    22 
    23 $_SESSION['userLogin']=cpassword(32).rand();
    24 ?>
    View Code

    从给出的代码中可以看到

    $pwd=cpassword(10);

    程序通过时间戳播种,然后通过cpassword建立一个密码,并验证密码是否正确,否则就输出flag

    通过之前研究发现mt_rand()生成的随机数是有固定的,只需要知道mt_srand(time());的结果行了

    写个脚本,直接用time函数生成的时间戳设置种子,再发送数据内容过去,就能获取到flag

    脚本如下

     1 <?php
     2 function do_get($url, $params) {
     3     $url = "{$url}?" . http_build_query ( $params );
     4     $ch = curl_init ();
     5     curl_setopt ( $ch, CURLOPT_URL, $url );
     6     curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, 1 );
     7     curl_setopt ( $ch, CURLOPT_CUSTOMREQUEST, 'GET' );
     8     curl_setopt ( $ch, CURLOPT_TIMEOUT, 60 );
     9     curl_setopt ( $ch, CURLOPT_POSTFIELDS, $params );
    10     $result = curl_exec ( $ch );
    11     curl_close ( $ch );
    12     return $result;
    13 }
    14 
    15 function cpassword($pw_length = 10){
    16     $randpwd = "";
    17     for ($i = 0; $i < $pw_length; $i++)
    18     {
    19         $randpwd .= chr(mt_rand(33, 126));
    20     }
    21     return $randpwd;
    22 }
    23 
    24 mt_srand(time());
    25 $pwd=cpassword(10);
    26 echo 'send:'.$pwd.'<br/>';
    27 $url="http://localhost/index.php";
    28 $params=array('pwd'=>$pwd);
    29 $result=do_get($url,$params);
    30 echo 'result:'.json_encode($result);
    31 ?>
    审计案例① POC

    发送的密码与服务器所生成的密码是一样的!

    假如按照刚才的生成密码的函数

    1 function cpassword($pw_length = 10){
    2     $randpwd = "";
    3     for ($i = 0; $i < $pw_length; $i++)
    4     {
    5         $randpwd .= chr(mt_rand(33, 126));
    6     }
    7     return $randpwd;
    8 }

     这次给出生成后的密码,现在怎样才能得到mt_srand()设置的种子

    生成的密码:

    {|M}2x0:kW

     按照程序执行流程,应该先得到每个字符在chr(33)~chr(126)中的位置索引

     1 <?php
     2 $str = "!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~";
     3 $ss = "{|M}2x0:kW";
     4 for($i=0;$i<strlen($ss);$i++){
     5    $pos = strpos($str,$ss[$i]);
     6    echo $pos." ".$pos." "."0 ".(strlen($str)-1)." ";
     7    //整理成方便 php_mt_seed 测试的格式
     8   //php_mt_seed VALUE_OR_MATCH_MIN [MATCH_MAX [RANGE_MIN RANGE_MAX]]
     9 }
    10 ?>
    PHP code

     通过这个脚本换成方便php_mt_seed测试的格式

    90 90 0 93 91 91 0 93 44 44 0 93 92 92 0 93 17 17 0 93 87 87 0 93 15 15 0 93 25 25 0 93 74 74 0 93 54 54 0 93

     最终得到种子数1555747516

    审计案例②(rand()函数安全性)

     1 <?php
     2  include('config.php');
     3  session_start();
     4  
     5  if($_SESSION['time'] && time() - $_SESSION['time'] > 60){
     6     session_destroy();
     7     die('timeout');
     8  } else {
     9     $_SESSION['time'] = time();
    10  }
    11  
    12  echo rand();
    13  if(isset($_GET['go'])){
    14     $_SESSION['rand'] = array();
    15     $i = 5;
    16     $d = '';
    17     while($i--){
    18         $r = (string)rand();
    19         $_SESSION['rand'][] = $r;
    20         $d .= $r;
    21     }
    22     echo md5($d);
    23  }else if(isset($_GET['check'])){
    24     if($_GET['check'] === $_SESSION['rand']){
    25         echo $flag;
    26     } else {
    27         echo 'die';
    28         session_destroy();
    29     }
    30  } else {
    31     show_source(__FILE__);
    32  }
    33 ?>
    PHP code

     这是出自0ctf的一道赛题

    首先我们先了解rand()生成的随机数是有规律可循的

    可以参考这篇文章:http://www.sjoerdlangkemper.nl/2016/02/11/cracking-php-rand/

    state[i] = state[i-3] + state[i-31]
    return state[i] >> 1

     下面这个程序形象的预测了rand()的随机数

     1 <?php
     2 $randStr = array();
     3 for($i=0;$i<60;$i++){  //先产生 32个随机数
     4     $randStr[$i]=rand(0,30);
     5     if($i>=31) {
     6         //echo  "$randStr[$i]=(".$randStr[$i-31]."+".$randStr[$i-3].") mod 31"."<br/>";
     7         if ($randStr[$i] == $randStr[$i-31]+$randStr[$i-3]){
     8             echo  "$randStr[$i]=(".$randStr[$i-31]."+".$randStr[$i-3].") mod 31"."<br/>";
     9         }
    10     }
    11 }
    12 ?>
    PHP code

  • 相关阅读:
    快速了解layui中layer的使用
    导航栏切换按钮事件
    jQuery中 end(); 的用法
    JS中关于 一个关于计时器功能效果的实现
    js 中 setInterval 的返回值问题
    javascript 构造函数中的属性与原型上属性优先级的比较
    斐波那契数列 -- 递归算法(-)
    javascript 变量声明有var与无var 的区别
    作用域的理解--第一篇
    Javascript---数组常用方法
  • 原文地址:https://www.cnblogs.com/wh4am1/p/10733549.html
Copyright © 2011-2022 走看看