0%

preg_replace与代码执行

前言:

 以前刷题刷到过但是当时看了wp觉得已经懂了,今天做题发现并不如此,那么我就再学习学习记录记录。

初识 preg_replace

 preg_replace(str1,str2,$x)函数就是将$x中的str1 替换为 str2(str1可以是正则)。为了方便php开发团队提出了 preg_replace /e 模式,使用方法是在str1 后面加/e,如果有匹配 str1 成功它会先将 str2执行一遍,相当于eval(执行结果可以显示)并把结果替换到 $x 中匹配字符。简单看看下面例子:

注:记得str1有//定界符,没有会报错
但是专家们很早就已经意识到/e的危险性了,在php5.5.0开始就废弃了/e模式(但好像还可以用,7.*用不了了),事实上仅仅把 preg_replace 里面有 /e 修饰符的代码 改动成 preg_replace _callback就可以了达到原来的效果了。

来看一个案例

 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
<?php

function fun($a,$b){
return preg_replace('/('.$a.')/e','strtolower("\1")',$b);
// 只有被双引号引起来才会被解析,\1 替换为\\1 也可以达到效果。
}
foreach($_GET as $a=>$b){
echo fun($a,$b);
}


?>

这个我们该怎么利用呢?/e 模式只会 执行表达式二,但是表达式二看起来好像并不是可控变量呀。

正则之反向引用

 表达式在匹配时,表达式引擎会将小括号 “( )” 包含的表达式所匹配到的字符串记录下来。在获取匹配结果的时候,小括号包含的表达式所匹配到的字符串可以单独获取。
 “\1” 引用第1对括号内匹配到的字符串,”\2” 引用第2对括号内匹配到的字符串……以此类推。如果没有括号 \1 引用的就是所有。
 那么,前面的 strtolower(“\1”) 应该可以理解为 双引号解析调用 \1 也就是调用表达式一中第一对括号所匹配到的值。但是函数已经定死了,只能是转小写我们该怎么办?

PHP 可变变量

 以前知道 $a='b';$$a='c';echo $b ; 会输出 c。仔细屡屡,它为什么会知道$b ? 因为它先执行了 $a 。那么我们可以利用这个方法来达到命令执行的效果。例如我们执行${phpinfo()} 当然这里的 {}只是普通的括号而已,如果不括起来就相当于定义一个名为phpinfo() 的变量,肯定会报错,应该为命名不复合规则。
 在看看我们 定义一个 $a 变量 $a="{${phpinfo()}}"; 它也会执行了我们的phpinfo()函数。但是为什么没有最外面一层{}会报错呢?我们看看引号里面是怎么解析的,首先发现 ${phpinfo()} 返回的值为 NULL,然后…. 好吧,我没看明白如果是不带()号的变量可以解析,估计是()导致它解析错误了。不过我们知道{${phpinfo()}}是可以自己自动执行的就好办了。比如上面,strtolower("\1") 中 \1匹配到的是{${phpinfo()}}就可以执行了,从而达到了任意命令执行的效果。

GET传值的改变

 通过上面的学习我们不难得出payload 为 ?.*={${phpinfo()}} 但是传进去后无法达到预期结果,这是 why ?这是由于在PHP中,对于传入的非法的 $_GET 数组参数名,会将其转换成下划线,这就导致我们正则匹配失效。当非法字符为首字母时,只有点号会被替换成下划线。
.*来匹配{${phpinfo()}} 行不通了,但是并不是只有它可以匹配的到要。这时我们想起了\S* 它表示匹配所有匹配任何非空白字符。
 那么这个问题的解决办法就是换一个匹配正则了,当然正则填写不唯一啦。

最后,看结果

 上面已给出解决办法,下面我们传入?\S*={${phpinfo()}} 来看看效果:

成功执行啦。

再来一个简单的;

代码如下:

1
2
3
4
5
6
7
8
9
10
11
<?php

function fun($a,$b){
return preg_replace("/\[php\](.+?)\[\/php\]/ie",'strtolower("\\1")',$b); // 只有被双引号引起来才会被解析
}
foreach($_GET as $a=>$b){
echo fun($a,$b);
}


?>

利用及结果如图:

附加知识,preg_replace函数到底是怎么运行的 ?

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
测试一:var_dump(preg_replace('/a/','*','ac'));
结果:*c , 和预期一样。
测试二:var_dump(preg_replace('/.*/','*','ac'));
结果: ** ,预期结果是 *
测试三:var_dump(preg_replace('/.*/','*',''));
结果:* ,和预期结果一样
测试四:var_dump(preg_replace('/.*/U','*','a'));
结果: ** , 预期 *
测试五: var_dump(preg_replace('/.*/U','*','ab'));
结果: ***** ,预期 **
测试六:var_dump(preg_replace('/.*/U','*','abc'));
结果:******* ,预期 ***
测试七:var_dump(preg_replace('/\S*/U','*','abc'));
结果:******* ,预期 ***
测试八:var_dump(preg_replace('/a/','*','a'));
结果: *
测试九:var_dump(preg_replace('/(\W*)/','*','a{bc'));
结果:*a**b*c*
其中 /U 是禁用贪婪模式

虽然上面测试并不全面,但还是勉强总结一下规律:

  1. 如果表达式一中,只有普通字符就会按我们的预期发展,其它的可能导致奇奇怪怪的结果。
  2. 算了算了,不强行总结了。有需要的时候当场测试吧。。。

-------------本文结束感谢您的阅读-------------