0x00 前言
整理CTF中web 的基础知识时发现很多都会与正则匹配相结合,这篇文章来学习一下正则表达式的相关知识。在读懂一个正则表达式的要求后,如何去饶过他也是很关键的,这里使用菜鸟教程的一个在线的正则匹配会比较方便:正则表达式在线测试。
0x01 什么是正则表达式
正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。当然实现什么功能,最后还是要看使用什么匹配模式,本质上所有的正则都只是为了匹配对应的字符串。
一条正则表达式中可能包含的元素有:
- 普通字符(不属于元字符的所有可打印和不可打印字符)
- 非打印字符(回车、换页、换行、制表符等等无法打印的字符)
- 特殊字符(有特殊含义的、需要转义的字符)
- 限定符(通配符*、?、和{}花括号内的数字,表示匹配的次数)
- 修饰符(除了表达式之外的附加策略,例如忽略大小写等策略)
- 元字符(构成表达式语法的字符。[]方括号、^号、$号等等)
- 运算符(转义\、圆、方括号、元字符等等需要组合的字符)
构造正则表达式的方法和创建数学表达式的方法一样。也就是用多种元字符与运算符可以将小的表达式结合在一起来创建更大的表达式。(这句话比较好理解,一条表达式就是由很多的元素构成的,就像一段代码,就是一堆函数组合实现不同的功能。)
正则表达式的组件可以是单个的字符、字符集合、字符范围、字符间的选择或者所有这些组件的任意组合。
正则表达式是由普通字符(例如字符 a 到 z)以及特殊字符(称为"元字符")组成的文字模式。模式描述在搜索文本时要匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。
0x02 元字符
按照读一个表达式的顺序来说,我认为首先应该认识的就是元字符,因为不论是普通、特殊、打印、非打印,还是限定、修饰符,都是由元字符中的单一元素组合而成的,这点在学习完元字符后就有很直观的感受,对于所有人来说学习正则表达式最关键的就是元字符的理解和语法的顺序。
在元字符中,必须要认识的是:
字符 | 描述 |
---|---|
\ | 将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符。例如,‘n’ 匹配字符 “n”。‘\n’ 匹配一个换行符。序列 ‘\\’ 匹配 “\” 而 “\(” 则匹配 “(”。 |
^ | 匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性(也就是是否以多行模式匹配),^ 也匹配 ‘\n’ 或 ‘\r’ 之后的位置。 |
$ | 匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性(也就是是否以多行模式匹配),$ 也匹配 ‘\n’ 或 ‘\r’ 之前的位置。 |
* | 匹配前面的子表达式零次或多次。例如,zo* 能匹配 “z” 以及 “zoo”。* 等价于{0,}。 |
? | 匹配前面的子表达式零次或一次。例如,“do(es)?” 可以匹配 “do” 或 “does” 。? 等价于 {0,1}。 |
{n} | n 是一个非负整数。匹配确定的 n 次。例如,‘o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o。 |
{n,} | n 是一个非负整数。至少匹配n 次。例如,‘o{2,}’ 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。‘o{1,}’ 等价于 ‘o+’。‘o{0,}’ 则等价于 ‘o*’。 |
{n,m} | m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,“o{1,3}” 将匹配 “fooooood” 中的前三个 o。‘o{0,1}’ 等价于 ‘o?’。请注意在逗号和两个数之间不能有空格。 |
? | 当该字符紧跟在任何一个其他限制符(*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 “oooo”,‘o+?’ 将匹配单个 “o”,而 ‘o+’ 将匹配所有 ‘o’。(也就是说?在限制符、通配中如果出现在最后,优先级最高。) |
. | 匹配除换行符(\n、\r)之外的任何单个字符。要匹配包括 ‘\n’ 在内的任何字符,请使用像"(.|\n)"的模式。 |
x|y | 匹配 x 或 y。 |
[xyz] | 字符集合。匹配所包含的任意一个字符。例如, ‘[abc]’ 可以匹配 “plain” 中的 ‘a’。 |
[^xyz] | 负值字符集合。匹配未包含的任意字符。例如, ‘[^abc]’ 可以匹配 “plain” 中的’p’、‘l’、‘i’、‘n’。 |
[a-z] | 字符范围。匹配指定范围内的任意字符。例如,‘[a-z]’ 可以匹配 ‘a’ 到 ‘z’ 范围内的任意小写字母字符。 |
[^a-z] | 负值字符范围。匹配任何不在指定范围内的任意字符。例如,‘[^a-z]’ 可以匹配任何不在 ‘a’ 到 ‘z’ 范围内的任意字符。 |
\b | 匹配一个单词边界,也就是指单词和空格间的位置。例如, ‘er\b’ 可以匹配"never" 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’。 |
\B | 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。 |
\d | 匹配一个数字字符。等价于 [0-9]。 |
\D | 匹配一个非数字字符。等价于 [^0-9]。 |
\cx | 匹配由 x 指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 ‘c’ 字符。 |
\f \n \r \t \v | 分别等价于换页符\x0c 和 \cL、 换行符\x0a 和 \cJ、回车符\x0d 和 \cM、制表符\x09 和 \cI、垂直制表符\x0b 和 \cK 。 |
\s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。 |
\S | 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。 |
\w | 匹配字母、数字、下划线。等价于’[A-Za-z0-9_]'。 |
\W | 匹配非字母、数字、下划线。等价于 ‘[^A-Za-z0-9_]’。 |
其余一些不常用的元字符我们可以在使用时查找定义。在上面这些常用的元字符中,基本包括了所有的可见、不可见字符和匹配模式。 |
0x03 修饰符
修饰符 | 含义 | 描述 |
---|---|---|
i | ignore - 不区分大小写 | 将匹配设置为不区分大小写,搜索时不区分大小写: A 和 a 没有区别。 |
g | global - 全局匹配 | 查找所有的匹配项。 |
m | multi line - 多行匹配 | 使边界字符 ^ 和 $ 匹配每一行的开头和结尾,记住是多行,而不是整个字符串的开头和结尾。 |
s | 特殊字符圆点 . 中包含换行符 \n | 默认情况下的圆点 . 是 匹配除换行符 \n 之外的任何字符,加上 s 修饰符之后, . 中包含换行符 \n。 |
修饰符一般跟在一条表达式的最后,用来限定前面表达式的最终结果,是一种附加的策略。
0x04 计算表达式
正则表达式中的元素基本都由上面的符号所组成,然后通过一定的优先关系,从左至右进行计算,计算时满足如下由高至低的优先关系。
- 转义符 \优先级最高
- ()圆括号子表达式和[]方括号
- * ? + { 等限定符
- | 替换符,“或”操作
如果一条表达式由^符开始,由$符结束,则表示精确匹配这个表达式的内容,下面计算几个实例。
1. 简单表达式
简单表达式没有任何限定和修饰符,单纯的匹配所有出现的字符。
例如/7MN/
这一表达式在匹配时只匹配出现7MN完整字符的匹配项,在线测试为:
可以看到在没有修饰
g
全局和大小写匹配i时,整个表达中虽然出现了两次7MN
,但匹配的结果只返回了第一次。
2.中括号表达式
中括号表达式也比较常见,表示匹配中括号中所有字符,有其一即可,不需要完整匹配,例如:Chapter [0-5]
匹配单词Chapter之后的0-5任意数字的组合,其他字符串都不满足条件。
3.括号表达式
括号表达式在从左至右计算的基础上优先计算,例如CTF练习中的:
ket.*key.{4,7}:\/.\/(.*key)
这个表达式也比较简单,首先从左至右计算ket
字符必须出现,后跟0或多个任意字符.*
,再跟key,4-7个任意字符.{4-7}
,跟冒号:
,两个优先级最高的转义符\
,匹配两次/
中间的任意字符:\/.\/
,再跟任意字符出现0或多次加key,.*key
。
那么结合起来就是
ket akey aaaa :/a/ akey
在线匹配的结果为:
0x05 PHP中的preg_match函数
不同的表达式有不同的效果,在实战中我们可以通过自己计算表达式与在线正则测试相结合的方式快速的绕过、满足题目给出的要求。
首先是preg_match
,他执行一个表达式的搜索,其函数原型为:
int preg_match ( string $pattern , string $subject [, array &$matches [, int $flags = 0 [, int $offset = 0 ]]] )
其中:
参数 | 用法 |
---|---|
$pattern | 要搜索的模式,字符串形式。(这部分通常就是正则表达式了) |
$subject | 输入字符串。(被搜索的字符串) |
$matches | 如果提供了参数matches,它将被填充为搜索结果。 $matches[0]将包含完整模式匹配到的文本, $matches[1] 将包含第一个捕获子组匹配到的文本,以此类推。 |
$flag | 可以被设置为PREG_OFFSET_CAPTURE: 如果传递了这个标记,对于每一个出现的匹配返回时会附加字符串偏移量(相对于目标字符串的)。 注意:这会改变填充到matches参数的数组,使其每个元素成为一个由 第0个元素是匹配到的字符串,第1个元素是该匹配字符串 在目标字符串subject中的偏移量。 |
$offset | 通常,搜索从目标字符串的开始位置开始。可选参数 offset 用于 指定从目标字符串的某个未知开始搜索(单位是字节)。 |
对我们来说常用的都是这样的: | |
preg_match("/php/i", "PHP is the web scripting language of choice.") | |
他返回的值只有0和1,0代表没有匹配到,1代表匹配到,但只会匹配一次,便会返回。 | |
与之相对的就是preg_match_all() ,它会一直搜索subject 直到到达结尾。 如果发生错误preg_match() 返回 FALSE 。他的返回值也是匹配的次数,但可能是0,1,2等各种数值。 |
0x06 PHP中的preg_replace函数
preg_replace
函数执行一个表达式的搜索和替换,其函数原型为:
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
其中:
参数 | 用法 |
---|---|
$pattern | 要搜索的模式,可以是字符串或一个字符串数组。 |
$replacement | 用于替换的字符串或字符串数组。 |
$subject | 要搜索替换的目标字符串或字符串数组。 |
$limit | 可选,对于每个模式用于每个 subject 字符串的最大可替换次数。 默认是-1(无限制)。 |
$count | 可选,为替换执行的次数。 |
我们常用的为: |
$str = 'runo o b';
$str = preg_replace('/\s+/', '', $str);
将匹配到的字串删除,以此来进行黑名单的过滤。掌握最基础的用法后,再结合具体的题目分析应用场景,对症下药。
0x07 小结
这部分知识更多的是参考了教程中的,把自己的一些内容提炼和想法记录下来,看别人的文章再多,也不如自己跟着实实在在学习一遍,把想要的知识记录下来。