CTF 中那些奇奇怪怪的正则表达式

Oyst3r 于 2023-02-05 发布

在 CTF 里面的比赛,经常会遇到正则表达式相关的知识,尤其是在白盒审计中,基本正则表达式就是家常便饭了,当然有些 ex 题确实会黑盒也写一个正则表达式,那就需要经验了哈;至于我为什么要写这篇文章,一就是自己从来没有仔细的思考过为什么要这么去绕过,而更多的就是 FUZZ,还是觉得不踏实吧(说白了就是闲的),所以还是专门研究一下这个东西,二来就是这个东西真的在 CTF 中太常见了,有必要去把它单拉出来去讲一下

https://www.runoob.com/regexp/regexp-rule.html

这个网站做的很好一点就是把各个符号都按照用途去分类了,大家学起来也更加轻松一点,然后简单说几个比较常用的吧

^这个符号就表示只匹配那些以特定字符串开头的字符串,而相反的就是$
.就表示匹配任意单字符,但不包括\n,*就和Linux里面的通配符差不多一个意思
()和|这个就是一些关系运算符
反正耐心看完上面那个文章就会懂这些,我这里就是简单举几个例子,纯当水文了哈哈哈哈

2.理解一下正则匹配大致分为正向的正则匹配和反向的正则匹配(这个就是自己瞎创的名词,不必在意),其实这就是两种思考问题的逻辑,直观的从正则表达式来看,就是看一开始有没有加.*,一旦加了这个,一开始就会整个字符串被选中,然后一一排除

3.明白正则匹配的一个基本的匹配逻辑,如果没有一些额外的限制条件,一定是先查第一个字母匹不匹配,如果匹配的话就接着匹配下一个字母,就这样依次循环,如果一旦检测到不匹配,那么正向的正则匹配,第一个字母就往后推一个单位,相反的,反向的正则匹配会向前推一个单位

4.一定要学会 debug 正则表达式,就是调试它,这个我也是在网上找了好多款工具,最后觉得这个工具雀氏是有那么亿点好用,这篇文章用这个工具的目的是为了让大家去更好的理解正则匹配表达式,而不是让大家用这个工具去 FUZZ(大概就是无脑的一遍一遍的改 Payload,然后看无法匹配的情况对应的是哪种 Payload,然后就去这样打 CTF 了,还是多理解一下其中的道理吧)

https://hiregex.com/
这个使用起来也特别容易,这个工具单步调试的话需要用方向键即可,这个大家注意一下
\{\%0a"cmd":"ls%20/"%0a\}

有没有觉得很奇怪,如果单纯的像网上那些解释为%0a 是回车,而正则匹配只能匹配第一行,那直接这么写就可以了啊

\{\%0a"cmd":"ls%20/"\}

但事实证明这么写的确会被正则表达式检测到,自己又去下了源码,本地机测试了一遍,千真万确就是正则匹配检测到的,之后又去问一些师傅,有的师傅说是 Json 格式的问题,必须要对称才行,还有的师傅说应该就是正则匹配表达式底层逻辑的问题,我感觉前者有点扯哈,后者也是没有具体说出底层逻辑是怎么样的,欸,还是得靠自己啦

2.最简单粗暴的方式就是把它扔到那个 debug 的工具里面,就和沙箱一样,跑一遍,咱们分析分析这个正则匹配表达式的检测过程(我把这个正则表达式稍微简化了一下,道理是一样的)

以上就是执行的全部过程,大家也可以自己下去尝试一下,下面咱们来解释一下这个过程哈

1.首先^这个定义了从这里开始的
2..*这两个符号一般是连在一起用的,就是可以全选后面除了换行符所有的内容,但是这个字符串确实有换行符,所以基本没啥用,这个就是咱们上面说的那个反向正则匹配
3.然后逐个匹配,因为是或的关系,一旦匹配到一个就直接跳出,然后这里匹配到的是换行符,因为后面那一串奇怪的数字串里面有这个
4.那么下一个.*就可以正常发挥作用了,直接把第二行除了换行符的所有字符都选中
5.然后就进入了$这个符号,按常理来说就要结束了,但是你会发现又一个一个开始往前匹配了,当时我这里也不太理解,应该就是这个$本来就是代表字符串该结束了,但是它并没有结束,这个就是也与正向匹配与反向匹配有关,这一点在下面给大家单独说,咱们先接着看,暂时先记住,只要是反向正则匹配,那么在遇到这个$并不会就直接结束了,而是会先一步一步往前走(这就和逻辑有关了,下面详细说),然后仔细的同学会发现它又走到.*跳转的那一步了,中间走的过程不管有没有匹配到其他的字符,都会向前走,这一点咋去理解呢?我举个例子
因为是或的关系,而且是一个反向的正则表达式,那么在从后往前的过程中,肯定是匹配到谁(谁就是离得最近的那一个),谁就要被接着执行下一步(也就是.*),那么一旦后面$那里出现了换行符,导致匹配的不对,$首先就会找到是从哪里开始.*的,这个就是$的底层逻辑,这是我们改变不了的,这两个%0a也是利用这个逻辑绕过了检测(而且测验发现$的向前跳转还会吞掉换行符,如果不吞并就死循环了(因为正则表达式已经明知道再试一次也不行了,就是往前继续试,它也不确定能不能匹配到,但它试就完了),然后继续.*,继续循环,这两个%0a也利用了这点,反正就是很巧妙)
6.然后基本就没什么了,所以里用两个%0a是有说法的,而不是像网上的人说的那样
7.还有就是普通的反向正则表达式,没有到$这个地方,那么还是正常的情况,不会像$这样会首先找到
.*这个开始的地方,大家注意区分

3.接下来去解释一下上面我所说的问题–这个$本来就是代表字符串该结束了,但是它并没有结束,这个就是也与正向匹配与反向匹配有关

同样举个例子

这个是一个正向匹配的例子,我相信大家对正向的东西逻辑还是更清楚一点的,由于^和$两个连用表示的是强匹配,但是这里明显后面有个换行符,所以肯定成功不了,成功绕过了正则的检测,但你想它为啥没有向前匹配,因为它就是从前面来的啊,所以就是这两种不同的思考问题的方式,导致了不一样的底层代码逻辑,从而导致了同一个$面对不同的情况做出来不一样的表现

这个最早是P 神发现的方法

1.通过上面的讲解,大家应该理解了我所谓的那个反向正则匹配的说法,P 神这里把它定义为回溯(但和$那个情况不一样,就是正常情况)

preg_match('/<\?.*[(`;?>].*/is', $data);
这个是p神举的例子

2.假设匹配的输入是<?php phpinfo();//aaaaa,实际执行流程是这样的:

见上图,可见第 4 步的时候,因为第一个.*可以匹配任何字符,所以最终匹配到了输入串的结尾,也就是//aaaaa。但此时显然是不对的,因为正则显示.*后面还应该有一个字符[(`;?>]

所以 NFA 就开始回溯,先吐出一个a,输入变成第 5 步显示的//aaaa,但仍然匹配不上正则,继续吐出a,变成//aaa,仍然匹配不上……

最终直到吐出;,输入变成第 12 步显示的<?php phpinfo(),此时,.*匹配的是php phpinfo(),而后面的;则匹配上[(`;?>],这个结果满足正则表达式的要求,于是不再回溯。13 步开始向后匹配;,14 步匹配.*,第二个.*匹配到了字符串末尾,最后结束匹配

这里回溯了 8 次

3.然后再介绍一个东西就是 PHP 的 pcre.backtrack_limit 限制,也是咱们要利用的地方

PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限pcre.backtrack_limit。我们可以通过var_dump(ini_get('pcre.backtrack_limit'));的方式查看当前环境下的上限

可见,回溯次数上限默认是 100 万。那么,假设我们的回溯次数超过了 100 万,会出现什么现象呢?比如:

可见,preg_match返回的不是 1 或 0,而是 false

所以,这道题的答案就呼之欲出了,我们通过发送超长字符串的方式,使正则执行失败,最后绕过目标对 PHP 语言的限制,下面给个 POC

import requests
from io import BytesIO

files = {
  'file': BytesIO(b'aaa<?php eval($_POST[txt]);//' + b'a' * 1000000)
}

res = requests.post('http://51.158.75.42:8088/index.php', files=files, allow_redirects=False)
print(res.headers)

4.然后这里 P 神又给了一个骚操作,和上面那个理解就又不一样了,先给一个常见的防 SQL 的方法

if(preg_match('/UNION.+?SELECT/is', $input)) {
    die('SQL Injection');
}

这里涉及到了正则表达式的“非贪婪模式”。在 NFA 中,如果我输入UNION/*aaaaa*/SELECT,这个正则表达式执行流程如下:

.+?匹配到/
因为非贪婪模式,所以.+?停止匹配,而由S匹配*
S匹配*失败,回溯,再由.+?匹配*
因为非贪婪模式,所以.+?停止匹配,而由S匹配a
S匹配a失败,回溯,再由.+?匹配a
...

回溯次数随着 a 的数量增加而增加。所以,我们仍然可以通过发送大量 a,来使回溯次数超出pcre.backtrack_limit限制,进而绕过 WAF:

但这里好像和 PHP 版本有关,低一点容易成功吧

5.防御措施就是设置正则后为强等于来判断返回值,这样 Flase 就不等于 0 了,这个方法也就不会利用成功了,因为这个方法的返回值是 Flase,应该高版本就是修复了这个问题,真的是用===这个方法完全失效

基本上了解了这些,以后再看到正则匹配表达式就不会那么的束手无策了,要是打比赛的时候很紧张,那就先放到那个工具里面,手工 FUZZ 吧,之后下来可以再研究研究,OKOK,这篇文章就到这里结束啦,各位师傅继续加油哈!!!