PHP正则匹配如何绕过?

在PHP安全领域,正则匹配绕过这个话题,有点像一场精密仪器与魔术师之间的斗法。开发者用preg_match()这类函数筑起一道看似严密的过滤墙,而攻击者则总能在意想不到的角落找到那枚失效的螺丝钉。这远不只是技巧的堆砌,更是对PHP语言特性和正则引擎底层逻辑的深度理解。

当“换行符”成为特洛伊木马

一个最经典的、也最容易被忽视的绕过点,在于正则表达式中的修饰符。比如,开发者使用了/^key.*key$/m,意图匹配以“key”开头和结尾的整行。这里的m修饰符意味着多行模式,此时^$会匹配每一行的开始和结束,而不是整个字符串的。

这就给了攻击者可乘之机。如果输入是key123keynbypass_payload,在第一行,正则匹配成功,函数返回TRUE。但程序后续如果直接使用整个原始输入去执行其他危险操作(比如evalinclude),那么换行符后面的bypass_payload就被完美地“夹带”了进来。防御者以为的终点线,不过是攻击者真正的起跑线。

回溯限制:不是保险,是陷阱

为了防范正则表达式的拒绝服务攻击(ReDoS),PHP引入了pcre.backtrack_limit配置项,默认值通常是100万。当回溯次数超过此限制,preg_match会返回FALSE(匹配失败)。有人误以为这是安全的保障,殊不知在特定逻辑下,它能成为绕过的跳板。

考虑这段代码逻辑:if (preg_match('/^(a+)+$/i', $input)) { die('bad'); } else { // 执行安全操作 }。其本意是:匹配到危险模式就拦截,否则放行。但如果攻击者提交一个精心构造的、能引发大量回溯的字符串(例如包含大量“a”但末尾有个“b”),当回溯次数爆表,preg_match返回FALSE,程序会错误地走进else分支,认为输入是“安全”的。这种因防御机制本身触发的逻辑反转,颇具讽刺意味。

数组输入:规则的“真空地带”

PHP的变量松散类型特性,在这里再次扮演了“猪队友”的角色。preg_match()函数在接收到数组类型的输入时(例如$_GET['id']被传入id[]=payload),它不会去匹配数组内容,而是直接返回FALSE,并可能产生一个警告。但许多开发者写的条件判断是if (preg_match(...) === 1),他们只期待严格的TRUE(即1)。

preg_match因为输入是数组而返回FALSE时,===的比较结果为假,程序流程就可能绕过了包含正则检查的if语句,继续向下执行。此时,那个数组可能被后续的代码以其他方式处理,例如implode拼接成字符串,从而让恶意载荷“金蝉脱壳”。这种绕过不需要理解复杂的正则语法,只需要知道参数可以传数组,就足以让一堵高墙形同虚设。

“.”真的匹配一切吗?

点号.在默认情况下不匹配换行符。这个知识点人尽皆知,但在紧张的代码审计中依然会翻车。假设过滤逻辑是//i,意图剔除所有script标签。攻击者只需在标签间插入一个换行符:<script>nalert(1)n</script>,正则匹配就会失败,恶意脚本得以幸存。加上s修饰符(单行模式)让.匹配所有字符,是修复方法,但前提是开发者得意识到这个坑的存在。

说到底,绕过正则匹配的艺术,核心在于寻找“意图”与“实现”之间的缝隙。开发者的思维是线性的、基于文本的,而PHP的运行环境和正则引擎的行为则是多维的、充满边角案例的。安全从来不是写下一个正则表达式就能划上的句号,而是一场关于细节、逻辑和想象力的持久博弈。你永远不知道,下一个绕过点,会不会藏在你认为最不可能的那个默认参数里。

参与讨论

0 条评论

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