PHP代码审计——Day 8-Candle
漏洞解析
header("Content-Type: text/plain");
function complexStrtolower($regex, $value) {
return preg_replace(
'/(' . $regex . ')/ei',
'strtolower("\\1")',
$value
);
}
foreach ($_GET as $regex => $value) {
echo complexStrtolower($regex, $value) . "\n";
}
考察点:代码执行
preg_replace
:(PHP 5.5)
- 功能 : 函数执行一个正则表达式的搜索和替换
- 定义 : mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
搜索 subject 中匹配 pattern 的部分, 如果匹配成功以 replacement 进行替换,如果没找到匹配项,则返回原始字符串。
$pattern 存在/e
模式修正符,允许代码执行
/e
模式修正符,是preg_replace()
将 $replacement 当做php代码来执行
/e
是一个已经被废弃的模式修正符,它在 PHP 5.5.0 版本中已经被移除。
传入的GET参数在complexStrtolower
函数中调用,可控制preg_replace
的第一个和第三个参数,第二个参数固定为'strtolower("\\1")'
,此处涉及到正则表达式反向引用的知识,即此处的 \\1
。\\1
表示正则匹配到的第一个内容,然后用strtolower
函数转成小写字母。
反向引用
对一个正则表达式模式或部分模式两边添加圆括号,将导致相关匹配存储到一个临时缓冲区中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。
缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 '\n' 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。这对于查找重复的模式或者匹配配对的字符串非常有用。
反向引用使用 \
加数字来表示。其中 \1
表示第一个捕获组的内容,\2
表示第二个捕获组的内容,以此类推。
举例来说,
$string = "hello hello";
$pattern = "/(\w+)\s\1/";
if (preg_match($pattern, $string, $matches)) {
echo "找到重复的单词: " . $matches[0]; // 输出: "找到重复的单词: hello hello"
} else {
echo "未找到重复的单词";
}
在这个例子中,正则表达式 (\w+)\s\1
匹配一个单词后跟一个空格,然后再次匹配相同的单词。\1
是对第一个捕获组 (\w+)
的反向引用,表示要匹配与第一个捕获组相同的内容。
继续说回有问题的代码。
想要执行phpinfo()函数,需要匹配到{${phpinfo()}}
或者${phpinfo()}
,原因在于php可变变量,也就是$$
坑一:
在PHP中双引号包裹的字符串中可以解析变量,而单引号则不行。 ${phpinfo()}
中的phpinfo()
会被当做变量先执行,执行后,即变成${1}
(phpinfo()成功执行返回true)。
坑二:
正则参数使用.*
来匹配任何字符是最方便的选择,但是这里通过GET传入后发现没有匹配到任何字符,因为我们传上去的.*
变成了_*
,这是由于在PHP中,对于传入的非法的 $_GET
数组参数名,会将其转换成下划线,这就导致我们正则匹配失效。那么可以换用\S*
来达到相同的效果。(\S
:用于匹配除单个空格符之外的所有字符)
当非法字符为首字母时,只有点号会被替换成下划线。
构造payload:\S*=${phpinfo()}
参考文章
https://lanvnal.com/2020/02/19/rips-php-security-calendar-2017-xue-xi-ji-lu/#toc-heading-2
https://github.com/hongriSec/PHP-Audit-Labs/blob/master/Part1/Day8/files/README.md