测试用代码
<?php
function complexStrtolower($regex,$value){
return preg_replace( '/('. $regex .')/ei','strtolower("\\1")' , $value);
}
foreach($_GET as $regex => $value){
echo complexStrtolower($regex,$value). '\n';
}
看几个例子
例一
<?php
preg_replace('/(.*)/ei', 'strtolower("\\1")', ${phpinfo()});
然而这里的第二个参数却固定为 'strtolower("\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);
}
例一的payload是直接写在源码里面的。但是通过/?.*={${phpinfo()}}方式传入却无法执行。在echo输出语句后面输出一下$_GET数组
var_dump($_GET);
这里我们发现了.变成了_,这是因为php会将传入的非法的参数名转成下滑线,所以我们的正则匹配才会失效。
当非法字符为首字母时,只有点号会被替换成下划线,对payload稍微做一下修改即可,
Payload:
\S*=${phpinfo()}
补充
下面再说说我们为什么要匹配到 {${phpinfo()}} 或者 ${phpinfo()} ,才能执行 phpinfo 函数,这是一个小坑。这实际上是 PHP可变变量 的原因。在PHP中双引号包裹的字符串中可以解析变量,而单引号则不行。 ${phpinfo()} 中的 phpinfo() 会被当做变量先执行,执行后,即变成 ${1} (phpinfo()成功执行返回true)。