preg_replace与代码执行

案例

image-20241112193912955

就是 preg_replace 使用了 /e 模式,导致可以代码执行,而且该函数的第一个和第三个参数都是我们可以控制的。我们都知道, preg_replace 函数在匹配到符号正则的字符串时,会将替换字符串(也就是上图 preg_replace 函数的第二个参数)当做代码来执行,然而这里的第二个参数却固定为 ‘strtolower(“\1”)’ 字符串,相当于 eval(‘strtolower(“\\1”);’) 结果

1
2
3
4
5
strtolower("\\1") 是 PHP 中的一个函数调用,目的是将匹配的字符串内容转换为小写形式
strtolower() 是 PHP 的一个函数,用于将字符串中的所有字符转换为小写
\\1 表示正则表达式中的反向引用,即对匹配到的第一个捕获组的引用
在正则表达式替换中,\\1 会被替换成正则表达式中第一个括号捕获到的内容
strtolower("\\1") 的作用是将第一个捕获组中的内容全部转换为小写字母

然后是W3Cschool里的解释

反向引用

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

所以这里的 \1 实际上指定的是第一个子匹配项

所以上面t的payload可以是

1
/?.*={${phpinfo()}}
1
2
原先的语句: preg_replace('/(' . $regex . ')/ei', 'strtolower("\\1")', $value);
变成了语句: preg_replace('/(.*)/ei', 'strtolower("\\1")', {${phpinfo()}});

上传出问题

.*{${phpinfo()}}直接嵌入在代码里的话,肯定没问题

但是没能执行,发现get传上去后.*变成了_*

这是由于在PHP中,对于传入的非法的 $_GET 数组参数名,会将其转换成下划线,这就导致我们正则匹配失效。

fuzz 一下PHP会将哪些符号替换成下划线

1
2
3
4
5
 (空格)
+
.
[
_

当非法字符为首字母时,只有点号会被替换成下划线

那就换一个正则表达式,让其匹配到 {${phpinfo()}} 即可执行 phpinfo 函数

1
\S*=${phpinfo()}

{${phpinfo()}}解析

由于可变变量的问题,在PHP中双引号包裹的字符串中可以先解析成变量,但是单引号里就直接当成函数解析了,**${phpinfo()}** 中的 phpinfo() 会被当做变量先执行