一些因错误使用正则表达式而引起的安全问题
常见的错误一
正则中有很多特殊意义的符号,比如常见的限定首尾的^
和$
,大部分开发者使用这两个元字符的本意是匹配字符串的开头和结尾,但是这两个元字符的原本意思是行的开头和结尾,这里就可能出现问题。
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 |
|