正则表达式如何绕过preg_match?

网络安全攻防演练里,preg_match 常常被开发者用作一道简单的字符串过滤屏障。但如果你以为一个正则表达式就能高枕无忧,那可能已经把大门钥匙藏在了脚垫下面。

绕过,从理解匹配机制开始

绕过 preg_match 的核心,在于精准打击其“匹配”而非“过滤”的本质。这个函数只关心输入的字符串里是否存在符合正则模式的子串。一旦找到,立刻返回 true。正是这种“首次命中即止”的特性,为攻击者留下了操作空间。

策略一:利用多行匹配与行首/行尾锚点

很多开发者习惯用 ^(行首)和 $(行尾)来锁定整个字符串。但如果正则表达式未使用 /m 修饰符(多行模式),这两个锚点匹配的是整个字符串的绝对开头和结尾。

$pattern = '/^keyd+$/';
$input = "key123nkey456"; // 注意中间的换行符
var_dump(preg_match($pattern, $input)); // 输出: int(0)

然而,攻击者可以尝试注入换行符 %0a。如果目标系统在处理输入时,恰巧将换行符视为普通字符,或者后续的字符串处理逻辑会分割、拼接,那么 ^$ 就可能失效。更危险的是,如果开发者意图用 ^key.*$ 来匹配整个“key...”字符串,攻击者提交 key123%0aDROP TABLE users;,前半部分轻松通过匹配,后面的恶意载荷就滑了进去。

策略二:精准打击贪婪与懒惰模式

正则表达式的量词(如 .*, .+, .{4,7})默认是贪婪的:它会尽可能多地匹配字符。懒惰模式(在量词后加 ?,如 .*?)则相反。

设想一个检测“不允许出现`cat`”的规则:/cat.*dog/。开发者本意是禁止“cat后面跟着dog”。但面对输入“cat123dog456cat”,贪婪的 .* 会一直吞到字符串末尾的“cat”,导致整个匹配失败(因为最后没有“dog”)。preg_match 返回 false,这反而让包含“cat”的字符串溜了过去。如果逻辑是“匹配到就拦截”,那这个拦截就失效了。

策略三:字符编码与空字节的魔术

这在特定历史版本的PHP或配置不当的环境下曾是致命漏洞。正则引擎(如PCRE)和后续的字符串处理函数(如include()file_get_contents())对空字节(%00)的解释可能不同。

例如,正则 /^key.php$/ 用来限制文件名为“key.php”。攻击者提交参数 key.php%00evil.jpg。对 preg_match 来说,空字节是字符串的一部分,匹配自然失败。但如果这个参数后续被直接拼接到文件路径里,file_get_contents('key.php%00evil.jpg'),在底层C语言函数看来,空字节就是字符串终止符,它实际读取的只是“key.php”。

防御视角:没有银弹

所以,把安全寄托在单个 preg_match 上极其危险。它应该是验证层的一环,而非唯一关卡。有效的做法是采用白名单原则,对于复杂逻辑使用多重、独立的校验,并在最后执行操作前,对数据进行严格的类型转换和标准化处理。

攻击者在构造payload时,那种反复试探边界、像拧动生锈门把手一样寻找缝隙的感觉,恰恰说明了安全是一个动态的过程。正则表达式是锋利的工具,但握着工具柄的,始终是人。

参与讨论

0 条评论

    暂无评论,快来发表你的观点吧!