CTF中preg_match函数的利用技巧
TOPIC SOURCE
Bugku-字符?正则?
在CTF赛场上,preg_match往往是把握关键权限的刀锋。它的匹配结果直接决定了后端逻辑的分支,稍有不慎就会让payload化为乌有。于是,熟悉其正则语法、PCRE特性以及PHP对输入的预处理方式,成为选手们在逆向题目中抢占先机的必修课。
正则表达式的核心要点
PCRE引擎支持的断言((?=...)、(?!...))以及字符类的细粒度控制,是构造“看似合法、实则匹配”模式的基石。忽略大小写可通过i修饰符实现,而s则让点号匹配换行,常被用来跨行捕获。
常见的绕过技巧
- 利用
x00或插入空字符,迫使trim或strlen误判长度。 - 在字符类中混入
[:punct:]、[:graph:]等 POSIX 类,扩大匹配范围,却不破坏整体结构。 - 用非捕获分组
(?:...)隐藏冗余子表达式,防止$match数组膨胀导致后续检查失效。
实战案例分析
某题给出的代码片段如下:
<?php
$key = 'KEY{********}';
$IM = preg_match("/key.*key.{4,7}key://.//(.*key)[a-z][[:punct:]]/i", trim($_GET["id"]), $match);
if ($IM) { die('key is: '.$key); }
?>
要让$IM为真,只需让正则成功捕获任意子串。一个常用的payload是:
http://example.com/vuln.php?id=keyABCkey12345key:/x/foobarkeyb!
这里keyABCkey12345key满足key.*key.{4,7}key的长度要求,随后:/x/与foobar填补.//的占位,最后的b!匹配[a-z][[:punct:]]。一旦匹配成功,die立即泄露KEY{...}。
细节决定成败:若在浏览器地址栏直接输入#或?id=后留空,trim会去掉所有空白,导致正则匹配失败;而在payload中加入%00则可以制造隐藏字符,使得strlen判断仍为非零,却让正则看到完整模式。

参与讨论
空字符截断现在还能用?PHP版本是不是得卡老一点?
key.*key.{4,7}key 这个长度限制卡得真死,试了好久才凑对
感觉用x修饰符加注释也能绕,不过例子没提
trim吃掉空格太坑了,本地测通线上挂,气死
%00在高版本PHP里早被干掉了,这题环境估计是5.6?
非捕获分组确实省事,不然$match下标乱七八糟的
看不懂正则那段,有没有大佬画个匹配流程图?
之前搞过类似题,也是靠[:graph:]混进去的
又是PCRE特性题,每次看都头大 😩
payload最后那个b!匹配[a-z][[:punct:]],感叹号算标点?
说白了就是钻正则和字符串处理的空子,挺脏但有效
这题要是没给代码片段根本没法下手吧
i修饰符忽略大小写,那KeyKEYkey行不行?
实战案例这payload构造得真细,服了
这题我做过,payload里那个[:punct:]差点没绕过去
@元宝 这题的正则也太绕了吧,能看懂的都是神仙
@ 石膏 其实多拆解几次就明白了,先看key.*key这部分,后面跟上4-7个任意字符,最后用b!这样的字母加标点收尾就行。
payload里那个空字符%00的利用挺巧妙
trim去空白的细节常被忽视,真坑人
@ 荒野 踩过这个坑的举手
断言和字符类配合真巧妙
这个payload长度卡得真准