一些因错误使用正则表达式而引起的安全问题
常见的错误一
正则中有很多特殊意义的符号,比如常见的限定首尾的^和$,大部分开发者使用这两个元字符的本意是匹配字符串的开头和结尾,但是这两个元字符的原本意思是行的开头和结尾,这里就可能出现问题。
1 |
|
这里原本意思是传入的test内容由a开头,z结尾,中间允许有若干字母。
但因为使用了^$定界符,加上使用了m修饰符,导致可以通过传入test=aaz%0a其他字符来绕过检测。
常见错误二
1 |
|
这里原本意思是检测用户是否有输入SQL注入的payload,但这里可以用一个换行符来绕过:test=select password %0a from users.
这里可以通过添加s修饰符来使.拥有匹配\n的能力。
single-line和multi-line以及s和m修饰符
signal-line是单行匹配,意思是将待匹配的文本视为一行,换行符不再作为“换行”的标志。multi-line是多行匹配,表示按行来匹配正则。可以理解为:将待匹配的文本用换行符分割后,每一部分都对其进行正则匹配,并将结果用OR运算来计算,得出最终结果。
m修饰符让正则匹配设置为multi-line模式(PCRE中默认为signal-line)s修饰符是让.元字符能够匹配\n(不是将指定为signal-line)
那么现在就可能有如下情况:
- 不加
s或m修饰符 ->single line,.不能匹配换行符 - 单独加
s修饰符 ->single line,且.匹配包括换行符在内的所有字符 - 单独加
m修饰符 ->multi line - 同时加
m和s两个修饰符 ->multi line,且.匹配包括换行符在内的所有字符
这样一来,^和$原本表示的意思是“匹配行的开始和行的结束”,所以,他们在single-line与multi-line两个情境下就会有差异了.
在
multi-line时,因为是多行模式,所以^和$只需要匹配上其中一行就行了,这也是错误一的原因所在。在
single-line时,我们将整个文本视为一行,所以^和$自然就可以理解为文本的开头和结尾了。
很多人对于这两个符号的理解也正因为PCRE正则默认情况下是single-line,所以你将他们俩理解为匹配文本的开始和结束,是没有太大问题的。但在下面这种情况下还是会出现问题:1
var_dump(preg_match('/^a[a-z]*z$/', 'abcz\n')); ==> 1
这里将
^和$理解为匹配文本的开始和结束,但这里可以看到,在文本的末尾加了一个换行,正则结果仍然为1
导致这个结果的原因是,行这个事物本生就存在两种情况:- 非最后一行的行,其结尾就是换行符
- 最后一行,其结尾就是字符串结尾
所以,
$也就可以匹配到两种情况:字符串结尾或换行符。即使在single-line模式下,行的性质仍然是不变的。
解决方法
php中有一个D修饰符(php独有),其作用就是告诉$引擎仅匹配文本结尾,不在匹配到一个换行符。
其实,正则中具有首尾定界符意思的字符有这几个:^、$、\A、\Z、\z
其中,\A表示字符串的开头,\Z表示行的结尾(效果和$一样),\z表示字符串的结尾
所以,所以,其实 \A 和 \z 这两个界定符才是真正表示“字符串开头”和“字符串结尾”的,我们一直错误地使用^ 和 $ 其实只是碰巧没有出现问题罢了
PHP利用PCRE回溯次数限制绕过某些安全限制
贴上P神Blog PHP利用PCRE回溯次数限制绕过某些安全限制
大概意思是利用php的pcre.backtrack_limit限制来使正则函数出错返回False从而绕过一些限制。
举个Blog中的栗子:
1 |
|
可以发送超长的数据包使preg_match函数出错而返回False,比如'aaa<?php eval($_POST[txt]);//' + 'a' * 1000000
修复方法是使用===判断,而不是直接放到if中
1 |
|
