前言:
以前刷题刷到过但是当时看了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 | <?php |
这个我们该怎么利用呢?/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 | <?php |
附加知识,preg_replace函数到底是怎么运行的 ?
测试:
1 | 测试一:var_dump(preg_replace('/a/','*','ac')); |
虽然上面测试并不全面,但还是勉强总结一下规律:
- 如果表达式一中,只有普通字符就会按我们的预期发展,其它的可能导致奇奇怪怪的结果。
- 算了算了,不强行总结了。有需要的时候当场测试吧。。。