写脚本处理文本时,正则表达式用起来特别顺手。但如果你在 ref="/tag/2034/" style="color:#8B0506;font-weight:bold;">Perl 里动态拼接正则,又没做防护,那可能已经埋下了安全隐患——这就是所谓的“正则注入”。
什么是正则注入?
想象你有个搜索功能,用户输入关键词,程序自动把它放进正则里去匹配日志文件。比如用户输了个 error.*500,你直接拼成 /$user_input/ 去匹配。这看着没问题,但如果他输入的是 (.*){1000}evil 这种恶意模式呢?
这种输入可能触发回溯灾难,让 CPU 直接拉满,服务卡死。更糟的是,有些场景下还能绕过过滤逻辑,把不该匹配的内容放过去。
一个真实的翻车现场
有次上线个日志分析工具,允许运维填自定义过滤规则。结果某天系统突然无响应,查下来是有人误用了 .*.*.*.* 这类嵌套通配符,导致单条正则执行了几分钟。虽然不是恶意攻击,但效果等同于拒绝服务。
怎么防?三条实用建议
1. 别让用户直接拼正则
最简单的办法就是不让他们碰正则语法。比如提供“包含”“以…开头”“精确匹配”这些选项,后台转成安全的模式。用户想搜 login,你就用 \Q$user\E 包一层:
my $safe_pattern = quotemeta($user_input);
if ($log_line =~ /$safe_pattern/) { ... }
quotemeta 会把所有元字符转义,不用担心 . 或 * 被滥用。
2. 限制执行时间和复杂度
Perl 本身不支持超时中断正则,但可以用 alarm 搞个兜底:
eval {
local $SIG{ALRM} = sub { die 'timeout'; };
alarm(3);
if ($text =~ /$dangerous_regex/) {
# 处理匹配
}
alarm(0);
};
if ($@ && $@ !~ /timeout/) { die $@; }
这样哪怕遇到恶性回溯,最多卡 3 秒就会退出。
3. 白名单过滤 + 长度控制
如果非得让用户写正则,那就加上限制。比如只允许字母、数字、点号和基本量词,禁止嵌套括号和贪婪匹配。再限制整个表达式不超过 100 个字符。
if (length($user_regex) > 100 || $user_regex =~ /[\{\[\^\$\|\(\*\+\?]/) {
die '不允许的正则格式';
}
虽然不能防住所有情况,但能挡住大部分明显有问题的输入。
小改动,大不同
正则注入不像 SQL 注入那么出名,但危害一样不小。特别是在自动化运维、日志分析这类场景里,一条失控的正则能让整个系统瘫痪。花几分钟加个转义或超时机制,远比半夜被报警电话叫醒强。