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

  • 相关阅读:
    java.lang.IllegalArgumentException: When allowCredentials is true, allowedOrigins cannot contain the special value "*" since that cannot be set on the "Access-Control-Allow-Origin" response header.
    spring-session-data-redis依赖冲突问题
    centos7启动iptables时报Job for iptables.service failed because the control process exited with error cod
    图片上传后台服务报内存溢出 Out Of Memory Java heap space
    mysql 数据库密码忘记重置 进行远程连接
    打Jar包
    Type interface com.innovationV2.mapper.UserMapper is not known to the MapperRegistry
    关于java基础类型Integer String的clone()
    clion使用clang编译
    token & refresh token 机制总结
  • 原文地址:https://www.cnblogs.com/wh4am1/p/10733549.html
Copyright © 2011-2022 走看看