php代码审计-函数篇_Day7
parse_str函数缺陷
关于函数
parse_str — 将字符串解析成多个变量
parse_str的作用就是解析字符串并且注册成变量,它在注册变量之前不会验证当前变量是否存在,所以会直接覆盖掉当前作用域中原有的变量。
漏洞分析:DedeCMS5.6
member/buy_action.php:
在该文件中第17行,存在一个parse_str函数,跟进查看一下这里的代码:
首先是使用mchStrCode函数将$pd_encode参数进行解密,然后将解密后的结果存放到$mch_Post中以数组形式保存,之后的遍历语句,使用了两个$$符,在这里明显存在变量覆盖的点。
跟进函数mchStrCode查看其过程:
其中,key的获取是通过取$_SERVER[“HTTP_USER_AGENT”]和$GLOBALS[‘cfg_cookie_encode’]连接字符的第8-18位。其中ua我们是可以获取到的,但是cfg_cookie_encode我们没法拿到。在这里跳到文件install/index.php:
这里的$rnd_cookieEncode变量就是cfg_cookie_encode,但是该变量的加密过程比较复杂且数据量太大,这里看看能不能绕过去。
在member/buy_action.php文件中有一处利用mchStrCode函数加密的地方:
这里通过$_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:
而$pr_encode也是我们可控的值。
因此我们可以利用这个分支语句,传入所需变量值以及构造的SQL语句值,得到mchStrCode函数加密的值,在页面源码中拿到$pr_encode和$pr_verify变量的值,然后进入到一开始的分支判断中,利用parse_str函数会注册变量的特性,导致变量覆盖,然后将数据带入到SQL语句中。
那么SQL语句的构造点是在哪里呢?
在这个地方有一个#@__,通过追踪变量得到定义:
因此我们可以通过$cfg_dbprefix进行构造SQL语句带入到SQL查询语句中,产生SQL注入。而这里利用的点就是一开始经过parse_str函数处理之后的地方:
因此我们可以利用$cfg_dbprefix传入恶意构造SQL语句经过函数加密后获取$pr_encode和$pr_verify变量的值,然后再经过函数解密,经过parse_str函数注册变量导致的变量覆盖,将参数的值传入到SQL语句中,造成SQL注入。
1 | payload: |
首先传入product和pid是为了先进入分支获取$pr_encode和$pr_verify变量的值,而传入$a是为了构造%26。
这里是因为include/common.inc.php 文件对用户提交的内容进行了过滤:
凡是$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的值:
1 | payload: |
但是自己的复现没有成功,我也不知道是哪里出了问题,啊不写啦。
参考文章
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 | //index.php |
当if ($a[0] != ‘QNKCDZO’ && md5($a[0]) == md5(‘QNKCDZO’)) 这个判断语句成立的时候就会出现链接跳转,利用MD5碰撞,找一个md5之后值也是0e开头的即可,在弱比较类型中会将0e开头的值看成0,因此得以满足条件。这里传入$a[0],因为id值的传入经过函数parse_str(),该函数会注册变量,我使用的是%26绕过:
点击链接跳转页面:
文件会对referer头进行验证,直接访问文件的话会提示错误:
需要自行创建一个uploads文件夹,在代码中,当传入fllename和content时,会在uploads下生成一个文件夹,下面存放$filename为文件名的文件,内容却为Too slow!
这里注意到有函数usleep():
usleep — 以指定的微秒数延迟执行
因此想要输出flag就需要利用到条件竞争,在100000微妙执行后输出Too slow之前访问到指定文件输出flag。
使用burp抓包修改高线程:
使用200线程:
在执行start attack之前需要执行以下脚本一直访问uploads/4b84b15bff6ee5796152495a230e45e3d7e947d9/flag,
利用条件竞争获取flag:
1 | import requests as r |