php中preg_match绕过的换行匹配问题
记录一下关于preg_match()
的绕过方法,写一下自己的一下理解...
preg_match()
绕过一共就几种方法:
1、数组绕过
int preg_match ( string $pattern , string $subject [, array &$matches [, int $flags = 0 [, int $offset = 0 ]]] )
有函数原型可知,参数是string
类型,所以传入数组就能直接返回false
2、PCRE回溯次数限制
这里的话p神的文章写的很清楚
长度默认是是1000000
贴一个POC
import requests |
3、换行符绕过
这个是文章的重点,很多文章都说不设置多行的preg_match()
只会匹配第一行,其实这种说法是比较片面的,并不是%0A
之后的内容就不匹配了,而是要看设置的表达式能不能匹配到%0A
的问题
首先我们来看一下正则的修饰符先:

以下的讨论都建立在没有设置m
修饰符的情况下
再来看看几个重要的表达式:
^
:匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配“\n
”或“\r
”之后的位置
$
:匹配输入字符串的结束位置。如果设置了RegExp对象的Multiline属性,$也匹配“\n
”或“\r
”之前的位置
.
:匹配除“\
n
”之外的任何单个字符。要匹配包括“\
n
”在内的任何字符,请使用像“(.|\n)
”的模式
也就是说不设置m
的话,^
是不会匹配%0A
之后的内容,$
也类似,但是这只是这两个符号不能匹配而已,如果这两个符号之外还有别的表达式能匹配的话,是可以匹配的(后面的例子就会看到)
这里以buuoj的[FBCTF2019]RCEService
作为例子
它的表达式是
/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/ |
然后一个可行的payload是{%0a"cmd":"/bin/cat index.php"%0a}
接下来我们就看看这是怎么绕过的?后面的那个%0A
有没有必要?又是什么作用?
如果按照一些文章的理解只匹配第一行,那么第一个%0A
之后的就不用管了,但是为什么如果去掉后面的%0A
就不能绕过了?
首先是^.*
的部分,这里会从字符串的开头开始匹配所有.
能匹配到的,所以在payload
中,就会匹配到{
;然后剩下的就要接着看
中间括号里面的是只匹配一次而已,并且在最后是包含了\x0A
的也就是%0A
,所以在{
后面的%0A
就会被中间括号里面的表达式命中,但是括号的表达式只会匹配一次(可以看作是只发挥一次作用)
所以第一个%0A
后面的"cmd":"/bin/cat index.php"%0a}
全部要交给.*$
,意思就是如果.*$
能匹配剩下的所有的字符的话,那就表示这个正则表达式就命中我们输入的payload
,就不能绕过了
那我们就看看.*$
能不能匹配到剩下的所有字符,"cmd":"/bin/cat index.php"
这一部分毫无疑问肯定是能被.*$
匹配到的,但是接下来的%0A
,.
就无法匹配了,而且这没有m
标识不能多行匹配,$
也不能匹配%0A
,所以这最后的%0A
就无法被匹配了
再看看为什么%0a{"cmd":"/bin/cat index.php"}%0a
这个就不能绕过呢?
按照我们上面的分析,这个肯定也是能绕过才对的,因为会剩下一个%0A
不能匹配,但是!!!
我们测试一下就知道,使用preg_match()
的第三个参数,就是保存匹配到的结果的参数,我们输出一下,直接用上面题目的例子
传参:%0a{"cmd":"/bin/cat index.php"}%0a
(注意使用bp来传,如果你是window的话,浏览器会变成%0d%0a)
再看一下匹配的结果:%0a{"cmd":"/bin/cat index.php"}
(url解码后)
你就会发现他并没有完全匹配但是它仍然返回了1,并且触发了Hacking
这就涉及到了$
的匹配的问题:
因为这是单行模式,所以PCRE
就会认为这是一行字符串而已,所以$
就匹配了文本的结尾,又因为%0A
在最后,所以在逻辑上,%0A
就被匹配了(相当于是认为是单行文本,最后没有%0A
的),所以返回了true
,但是实际上字符串并没有匹配出来

这个师傅也讲了相关的问题
所以如果我们改成%0a{"cmd":"/bin/cat index.php"%0a}
就可以了