正则安全相关

一些因错误使用正则表达式而引起的安全问题

常见的错误一

正则中有很多特殊意义的符号,比如常见的限定首尾的^$,大部分开发者使用这两个元字符的本意是匹配字符串的开头和结尾,但是这两个元字符的原本意思是行的开头和结尾,这里就可能出现问题。

1
2
3
4
5
6
<?php
if(preg_match('/^a[a-z]+z$/m', $_GET['test']))
{
...;
}
?>

这里原本意思是传入的test内容由a开头,z结尾,中间允许有若干字母。
但因为使用了^$定界符,加上使用了m修饰符,导致可以通过传入test=aaz%0a其他字符来绕过检测。

常见错误二

1
2
3
4
5
6
7
<?php
if(preg_match('/select.+from/i', $_GET['test']))
die('sql injection');
else{
...;
}
?>

这里原本意思是检测用户是否有输入SQL注入的payload,但这里可以用一个换行符来绕过:test=select password %0a from users.

这里可以通过添加s修饰符来使.拥有匹配\n的能力。

single-line和multi-line以及sm修饰符

signal-line是单行匹配,意思是将待匹配的文本视为一行,换行符不再作为“换行”的标志。
multi-line是多行匹配,表示按行来匹配正则。可以理解为:将待匹配的文本用换行符分割后,每一部分都对其进行正则匹配,并将结果用OR运算来计算,得出最终结果。

m修饰符让正则匹配设置为multi-line模式(PCRE中默认为signal-line
s修饰符是让.元字符能够匹配\n(不是将指定为signal-line

那么现在就可能有如下情况:

  • 不加sm修饰符 -> single line. 不能匹配换行符
  • 单独加s修饰符 -> single line,且 . 匹配包括换行符在内的所有字符
  • 单独加m修饰符 -> multi line
  • 同时加ms两个修饰符 -> multi line,且 . 匹配包括换行符在内的所有字符

这样一来,^$原本表示的意思是“匹配行的开始和行的结束”,所以,他们在single-linemulti-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
2
3
4
5
6
7
8
<?php
function is_php($data){
return preg_match('/<\?.*[(`;?>].*/is', $data);
}

if(!is_php($input)) {
// fwrite($f, $input); ...
}

可以发送超长的数据包使preg_match函数出错而返回False,比如'aaa<?php eval($_POST[txt]);//' + 'a' * 1000000

修复方法是使用===判断,而不是直接放到if

1
2
3
4
5
6
7
8
9
<?php
function is_php($data){
return preg_match('/<\?.*[(`;?>].*/is', $data);
}

if(is_php($input) === 0) {
// fwrite($f, $input); ...
}

Refer

P神知识星球 https://govuln.com/attachment/676/

文章作者: Dar1in9
文章链接: http://dar1in9s.github.io/2020/04/28/web安全/正则安全相关/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Dar1in9's Blog