PCRE回溯的过程是怎样的,如何对次数限制
Admin 2022-08-03 群英技术资讯 454 次浏览
<?php function is_php($data){ return preg_match('/<\?.*[(`;?>].*/is', $data); } if(!is_php($input)) { // fwrite($f, $input); ... }
大意是判断一下用户输入的内容有没有PHP代码,如果没有,则写入文件。这种时候,如何绕过is_php()函数来写入webshell呢?
这道题看似简单,深究其原理,还是值得写一篇文章的。
正则表达式是一个可以被“有限状态自动机”接受的语言类。
“有限状态自动机”,其拥有有限数量的状态,每个状态可以迁移到零个或多个状态,输入字串决定执行哪个状态的迁移。
而常见的正则引擎,又被细分为DFA(确定性有限状态自动机)与NFA(非确定性有限状态自动机)。他们匹配输入的过程分别是:
由于NFA的执行过程存在回溯,所以其性能会劣于DFA,但它支持更多功能。大多数程序语言都使用了NFA作为正则引擎,其中也包括PHP使用的PCRE库。
所以,我们题目中的正则<\?.*[(`;?>].*
,假设匹配的输入是<?php phpinfo();//aaaaa,实际执行流程是这样的:
见上图,可见第4步的时候,因为第一个.*
可以匹配任何字符,所以最终匹配到了输入串的结尾,也就是//aaaaa
。但此时显然是不对的,因为正则显示.*
后面还应该有一个字符[(`;?>]
。
所以NFA就开始回溯,先吐出一个a
,输入变成第5步显示的//aaaa
,但仍然匹配不上正则,继续吐出a
,变成//aaa
,仍然匹配不上……
最终直到吐出;
,输入变成第12步显示的<?php phpinfo()
,此时,.*
匹配的是php phpinfo()
,而后面的;
则匹配上[(`;?>]
,这个结果满足正则表达式的要求,于是不再回溯。13步开始向后匹配;
,14步匹配.*
,第二个.*
匹配到了字符串末尾,最后结束匹配。
在调试正则表达式的时候,我们可以查看当前回溯的次数:
这里回溯了8次。
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)
延伸一下,很多基于PHP的WAF,如:
if(preg_match('/SELECT.+FROM.+/is', $input)) { die('SQL Injection'); }
均存在上述问题,通过大量回溯可以进行绕过。
另外,我遇到更常见的一种WAF是:
if(preg_match('/UNION.+?SELECT/is', $input)) { die('SQL Injection'); }
这里涉及到了正则表达式的“非贪婪模式”。在NFA中,如果我输入UNION/*aaaaa*/SELECT,这个正则表达式执行流程如下:
.+?
匹配到/
.+?
停止匹配,而由S
匹配*
S
匹配*
失败,回溯,再由.+?
匹配*
.+?
停止匹配,而由S
匹配a
S
匹配a
失败,回溯,再由.+?
匹配a
回溯次数随着a的数量增加而增加。所以,我们仍然可以通过发送大量a,来使回溯次数超出pcre.backtrack_limit限制,进而绕过WAF:
那么,如何修复这个问题呢?
其实如果我们仔细观察PHP文档,是可以看到preg_match函数下面的警告的:
如果用preg_match对字符串进行匹配,一定要使用===全等号来判断返回值,如:
function is_php($data){ return preg_match('/<\?.*[(`;?>].*/is', $data); } if(is_php($input) === 0) { // fwrite($f, $input); ... }
这样,即使正则执行失败返回false,也不会进入if语句。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
PHP中的重载是什么意思,如何应用呢?一些朋友可能会遇到这方面的问题,对此在下文小编向大家来讲解一下,内容详细,易于理解,希望大家阅读完这篇能有收获哦,有需要的朋友就往下看吧!
类的变量成员叫做“属性”,或者叫“字段”、“特征”,在本文档统一称为“属性”。属性声明是由关键字 public,protected 或者 private 开头,然后跟一个普通的变量声...
这篇文章主要介绍了简述PHP7.4 新特性和废弃的功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
PHP单例模式是什么?使用单例模式有什么好处?很多人新手在学习PHP时,对于单例模式不是很理解,下面就给大家介绍一下关于php单例模式,以及我们要如何设计单例模式和建立数据库连接。
1.函数explode(); 这个是字符串转化为数组, implode();这个是数组转化为字符串。$array=explode(separator,$string); $string=implode(glue,$array);使用和理解这两个函数的关键之处是分隔符(separator)和胶合符(glue)关系。当把一个数组转换成一个字符串时,将会设置胶合符——将被插入到生成字符串中的数
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008