不论是商业安全产品还是开源安全产品,在规则运营中,写正则一直是很重要的一个事情,而正则使用中有一些基础知识,可能会被很多人忽视。
比如 ^号是匹配每一行的开头还是匹配一个文件的开头呢?在 yara、modsecurity、suricata 等开源安全产品引擎中答案都是一样的吗?
比如 捕获分组、断言 等用法是所有引擎都支持的吗?
每个产品用的正则引擎库可能是不同的,所以能支持的特性、写法会稍微有些差别。
在c语言中,pcre库[1]应该是应用最广泛的正则引擎库,modsecurity、早期的yara都是用的它。
你可以在 https://www.debuggex.com/cheatsheet/regex/pcre 这个站点查看 PCRE、JavaScript、Python 支持的用法区别。
举个例子,在应用广泛的waf规则集crs中,有一条规则是检查响应内容是不是以 #!/
开头,已检查 #!/bin/bash
等脚本源码泄露,它的规则如下
SecRule RESPONSE_BODY "@rx ^#\!\s?/" \
"id:950140,\
...
实际上这条规则就和预期不同。在 modsecurity v3版本中,实际上它是在匹配响应内容"每一行"是否以 #!/
开头。
modsecurity的正则引擎是pcre,所以 ^号是匹配每一行的开头还是匹配一个文件的开头,这取决于 PCRE_MULTILINE 修饰符是否开启。
在 https://github.com/SpiderLabs/ModSecurity/blob/v3.0.9/src/utils/regex.cc#L68
PCRE2_SPTR pcre2_pattern = reinterpret_cast<PCRE2_SPTR>(pattern.c_str());
uint32_t pcre2_options = (PCRE2_DOTALL|PCRE2_MULTILINE);
if (ignoreCase) {
代码中看到 PCRE_MULTILINE选项 默认是开启的,所以^号匹配每一行的开头。所以上面的规则可以优化成
SecRule RESPONSE_BODY "@rx ^(.{10})" \ // 先取出响应头前十个字节
"id:950140,\
...
setvar:'tx.first_ten_chars=%{tx.1}',\
chain"
SecRule TX:FIRST_TEN_CHARS "@rx ^#\!\s?/" \ // 针对前十个字节做匹配
..."
更多讨论,可以看 https://github.com/coreruleset/coreruleset/issues/3266。modsecurity v3、modsecurity v2、Coraza 等表现都不一致。
其他语言的正则库提供的接口也会提供MULTILINE修饰符,比如Python如下
>>> import re
>>> re.findall("^2","1\n2\n3") # 默认没有开启MULTILINE,^就只匹配文本的开头
[]
>>> re.findall("^2","1\n2\n3",re.MULTILINE) # 开启MULTILINE,^就会匹配每一行的开头
['2']
>>> re.findall("(?m)^2","1\n2\n3") # (?m) 也可以开启MULTILINE
['2']
虽然正则初学者可以用大模型去写正则,但是也应该了解到不同引擎支持的正则特性不同、正则的修饰符等基础知识。
在规则编写前,也应该清楚正则引擎默认开启的修饰符选项有哪些。
pcre库: https://www.pcre.org/