PHP-Audit-Labs
项目地址:https://www.ripstech.com/php-security-calendar-2017/
1 | <?php |
考点为:parse_str函数覆盖变量,类似b=a[0]=
功能 :parse_str的作用就是解析字符串并且注册成变量,它在注册变量之前不会验证当前变量是否存在,所以会直接覆盖掉当前作用域中原有的变量。
定义 :
void parse_str( string $encoded_string [, array &$result ] )
如果 encoded_string 是 URL 传入的查询字符串(query string),则将它解析为变量并设置到当前作用域(如果提供了 result 则会设置到该数组里 )。
实例分析
漏洞分析
DedeCmsV5.6 该版本的buy_action.php处存在SQL注入漏洞,这里其实和 parse_str 有很大关系.
官网于20140225发布了V5.7.36 正式版0225常规更新补丁,这里面的改动一共四个文件dede/sys_info.php、 dede/templets/sys_info.htm 、include/uploadsafe.inc.php 、member/buy_action.php
sublime的FileDiffs插件来进行diff对比查找member/buy_action.php的改动情况。
改动部分,主要针对加密函数的强度进行了加强,所以做一个推断这个漏洞应该是由于 mchStrCode 这个编码方法造成的。在读这个函数时发现,如果在我们知道 cfg_cookie_encode 的情况下,被编码字符串是可以被逆推出来的。
Ctrl+Shitf+F
全局搜索mchStrCode函数。
在文件DedeCmsV5.6-UTF8-Final\uploads\member\buy_action.php的第十七行发现parse_str函数
1 | if(isset($pd_encode) && isset($pd_verify) && md5("payment".$pd_encode.$cfg_cookie_encode) == $pd_verify) |
foreach($mch_Post as $k => $v) $$k = $v;
存在变量覆盖,将 $mch_Post 中的key定义为变量,同时将key所对应的value赋予该变量。然后,再向下就是执行SQL查询了。
跟入mchStrCode
查看函数如何定义的
找到DedeCmsV5.6-UTF8-Final\uploads\member\buy_action.php第147行
1 | function mchStrCode($string,$action='ENCODE') |
这里将 $_SERVER["HTTP_USER_AGENT"] 和 $GLOBALS['cfg_cookie_encode'] 进行拼接,然后进行md5计算之后取前 18 位字符,其中的 $_SERVER["HTTP_USER_AGENT"] 是浏览器的标识,可以被我们控制,关键是这个 $GLOBALS['cfg_cookie_encode'] 是怎么来的。通过针对补丁文件的对比,发现了 /install/index.php 的 $rnd_cookieEncode 字符串的生成同样是加强了强度, $rnd_cookieEncode 字符串最终也就是前面提到的 $GLOBALS['cfg_cookie_encode']
diff一下补丁和源文件
改动部分,主要针对加密函数的强度进行了加强,所以做一个推断这个漏洞应该是由于 mchStrCode 这个编码方法造成的。在读这个函数时发现,如果在我们知道 cfg_cookie_encode 的情况下,被编码字符串是可以被逆推出来的。
这个漏洞在乌云上爆出来的时候,是sql注入,所以我推断可能在调用这个编码函数进行解码的地方,解码之后可能没有任何过滤和绕过,又或者可以可绕过过滤,导致sql语句拼接写入到了数据库,而且这里解码的函数可以被攻击者控制,从而导致了SQL注入的产生。
在 /install/index.php查看$rnd_cookieEncode
的定义。
1 | $rnd_cookieEncode = chr(mt_rand(ord('A'),ord('Z'))).chr(mt_rand(ord('a'),ord('z'))).chr(mt_rand(ord('A'),ord('Z'))).chr(mt_rand(ord('A'),ord('Z'))).chr(mt_rand(ord('a'),ord('z'))).mt_rand(1000,9999).chr(mt_rand(ord('A'),ord('Z'))); |
这段代码生成的加密密匙很有规律,所有密匙数为26^6*(9999-1000)=2779933068224,把所有可能的组合生成字典,用passwordpro暴力跑MD5或者使用GPU来破解,破解出md5过的密匙也花不了多少时间。 当然这个是完全有可能的,但是很耗时间,所以下一步看看有没有办法能够绕过这个猜测的过程,让页面直接回显回来。
利用思路
虽然整个漏洞利用原理很简单,但是利用难度还是很高的,关键点还是如何解决这个 mchStrCode , mchStrCode 这个函数的编码过程中需要知道网站预设的 cfg_cookie_encode ,而这个内容在用户界面只可以获取它的MD5值。虽然cfg_cookie_encode的生成有一定的规律性,我们可以使用MD5碰撞的方法获得,但是时间成本太高,感觉不太值得。所以想法是在什么地方可以使用 mchStrCode 加密可控参数,并且能够返回到页面中。所以搜索一下全文哪里调用了这个函数。
于是,我们在 member/buy_action.php 的104行找到了一处加密调用:$pr_encode = str_replace('=', '', mchStrCode($pr_encode)); 我们来看一下这个分支的整个代码:
1 | foreach($_REQUEST as $key => $val) |
这里的 第110行 有一 $tpl->LoadTemplate(DEDEMEMBER.'/templets/buy_action_payment.htm');
在 /templets/buy_action_payment.htm 中,我找到了页面上回显之前加密的 $pr_encode 和 $pr_verify
1 | <input type="hidden" name="pd_encode" value="<?php echo $pr_encode;?>"> |
通过这部分代码,我们可以通过 [cfg_dbprefix=SQL注入] 的提交请求,进入这个分支,让它帮助我来编码 [cfg_dbprefix=SQL注入] ,从而获取相应的 pr_encode 和 pr_verify 。 但是 common.inc.php 文件对用户提交的内容进行了过滤,凡提交的值以cfg、GLOBALS、GET、POST、COOKIE 开头都会被拦截,如下图第11行。
1 | function _RunMagicQuotes(&$svar) |
这个问题的解决就利用到了 $REQUEST 内容与 parse_str 函数内容的差异特性。我们url传入的时候通过[a=1&b=2%26c=3]这样的提交时, $REQUEST 解析的内容就是 [a=1,b=2%26c=3] 。而通过上面代码的遍历进入 parse_str 函数的内容则是 [a=1&b=2&c=3] ,因为 parse_str 函数会针对传入进来的数据进行解码,所以解析后的内容就变成了[a=1,b=2,c=3]。所以可以通过这种方法绕过 common.inc.php 文件对于参数内容传递的验证。
修复建议
为了解决变量覆盖问题,可以在注册变量前先判断变量是否存在,如果使用 extract 函数可以配置第二个参数是 EXTR_SKIP 。使用 parse_str 函数之前先自行通过代码判断变量是否存在。
这里提供一个demo漏洞样例代码,以及demo的修复方法。
存在漏洞的index.php
1 | <php |
payload
1 | http://127.0.0.1/PHP-Audit-Labs/day/day7/test.php?test=b=1 |
修复后的index.php
1 | <?php |
isset() 函数用于检测变量是否已设置并且非 NULL。
如果已经使用 unset() 释放了一个变量之后,再通过 isset() 判断将返回 FALSE。
若使用 isset() 测试一个被设置成 NULL 的变量,将返回 FALSE。
ctf
index.php
1 |
|
uploadsomething.php
1 | <?php |
考点一
parse_str变量覆盖
id=$a[0]=xxxxxxx
xxxxx为md5弱比较类型,PHP Hash比较存在缺陷 ,它把每一个以”0E”开头的哈希值都解释为0。这里上一篇的CTF也遇到过写过总结了
payload
1 | http://127.0.0.1/PHP-Audit-Labs/day/day7/CTF/index.php?id=a[0]=s878926199a |
考点二
在uploadsomething.php的3-4行,校验referer,若是没有referer为空,则无法访问上传页面。http://192.168.1.15/PHP-Audit-Labs/day/day7/CTF/uploadsomething.php
1 | $referer = $_SERVER['HTTP_REFERER']; |
如果不带referer会返回报错
1 | Notice: Undefined index: HTTP_REFERER in C:\Users\***\Desktop\Manager\website\phpStudy\PHPTutorial\WWW\PHP-Audit-Labs\day\day7\CTF\uploadsomething.php on line 3 |
考点三
此处存在竞争上传漏洞
1 | if ((@$_GET['filename']) && (@$_GET['content'])) { |
直接上传返回一个上传文件的物理地址
http://192.168.1.15/PHP-Audit-Labs/day/day7/CTF/uploads/5143634216cdfdcdc379517d66dfb743700feb4c/1
点击进去显示too slow
这里用到的是竞争上传的手段。
① 首先burp -> intruder -> Null payloads 发送大量数据包
② 脚本批量访问上传后的链接,由于链接地址写死,循环访问即可
1 | import requests as r |
flag
1 | HRCTF{y0u_n4ed_f4st} by:l1nk3r |