正则表达式相关

文章目录

  • 正则表达式
    • 简介
    • 语法综述
      • 模式修正符
    • 元字符
    • 量词
    • 元组
      • 重复分组
    • 断言
    • 位置锚定
      • 行定位符
      • 单词边界
    • 贪婪、非贪婪与回溯
      • 回溯
  • 正则使用案例
    • 千分位
    • 限制用户密码
  • 正则与安全
    • 防止sql注入与绕过
    • 利用回溯绕过正则限制
      • 案例1
      • 案例2
      • 案例3
        • php类型比较

正则表达式

简介

  • 正则表达式是对字符串(包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为“元字符”))操作的一种逻辑公式,就是用事先定义好的一些特定字符及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。正则表达式是一种文本模式,该模式描述在搜索文本时要匹配的一个或多个字符串。
  • 构造正则表达式的方法和创建数学表达式的方法一样。也就是用多种元字符与运算符可以将小的表达式结合在一起来创建更大的表达式。正则表达式的组件可以是单个的字符、字符集合、字符范围、字符间的选择或者所有这些组件的任意组合

语法综述

元字符 描述
. 匹配单个任意除换行符之外的符号
\d 匹配单个数字
\D 匹配单个非数字,包括字母、标点符号、空格、换行等
\w 匹配单词字符,包括大小写字母和数字
\W 匹配非单词字符。包括标点符号、空格换行等
\s 匹配空格、换行符、换页符、制表符等空白字符
\S 匹配非空白字符
[…] 字符集,对应位置可是字符集内的任意某个字符;字符集中的字符可以单个列出如:[abcd],也可以给出范围如:[a-d];如果第一个字符是 ^ 表示取反,如:[^a] ,表示不是a的其他字符
量词(用在元字符或(…)之后的)
? 匹配前一个字符0次或1次
* 匹配前一个字符0次或无限次
+ 匹配前一个字符1次或无限次
{m} m 是一个非负整数,匹配前一个字符m次
{m,} m 是一个非负整数,匹配前一个字符至少m次
{m,n} m 、n是一个非负整数,匹配前一个字符最少m次,最多n次
*?、+?、{m.n}? 使 *、+、{m,n}变为非贪婪模式
\b 匹配单词边界,字符串的开始与结尾处称为单词边界,字符串可以包括字母和数字
\B 匹配非单词边界
^ 匹配输入字符串开头的位置
$ 匹配输入字符串结尾的位置
分组
(…) 将括起来的表达式称为分组,从表达式左边开始每遇一个分组的(,分组编号+1,分作作为一个整体,后面可接数量词
\num 可以引用分组,num为分组的编号
断言
x(?=y) 先行肯定断言,仅当x后面为y时匹配x
(?<=y)x 后行肯定断言,仅当x前面为y时匹配x
x(?!y) 先行否定断言,仅当x后面不为y时匹配x
(? 后行否定断言,仅当x前面不为y时匹配x

模式修正符

修饰符 说明
i 忽略大小写
m 多文本模式
s 单行文本模式
x 忽略空白字符
U 表示拒绝贪婪匹配
g 表示全局匹配

元字符

  • 正则表达式是由普通字符(例如字符 a 到 z)以及特殊字符(称为"元字符")组成的文字模式。普通字符包括没有显式指定为元字符的所有可打印和不可打印字符。这包括所有大写和小写字母、所有数字、所有标点符号和一些其他符号。
  • 所谓特殊字符,就是一些有特殊含义的字符,要求在试图匹配它们时特别对待。若要匹配这些特殊字符,必须首先使字符"转义"
    正则表达式相关_第1张图片

量词

  • 量词用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配
    正则表达式相关_第2张图片

元组

  • () 将一个或多个字符捆绑在一起,当作一个整体进行处理,分组括号中的模式匹配到的内容会被正则表达式引擎记录于内部的变量中,这些变量的命名方式为: \1, \2, \3。对于分组而言,整个表达式永远算作\0
  • 后向引用:引用前面的分组括号中的模式所匹配字符,而非模式本身,\1 表示从左侧起第一个左括号以及与之匹配右括号之间的模式所匹配到的字符,\2 表示从左侧起第2个左括号以及与之匹配右括号之间的模式所匹配到的字符,以此类推,& 表示前面的分组中所有字符。
    正则表达式相关_第3张图片
  • 非捕获组(?:Pattern);它的作用就是匹配Pattern字符,但不捕获文本,不将匹配到的字符存储到内存中,从而节省内存

重复分组

  • (\d\d\d){3}去匹配 1234567890,开始我以为分组小括号(\d\d\d)的最终结果是 123456789都能拿到,但结果却只有789
    在这里插入图片描述
  • 重复分组的匹配在每次引擎退出该分组的时候被捕获,并会覆盖该分组在之前匹配的任何文本

模拟一下引擎工作的步骤:

  1. 第一次匹配,捕获到 123,退出分组
  2. 第二次匹配,捕获到 456, 覆盖上一次捕获的123,退出分组
  3. 第三次匹配,捕获到 789,覆盖上一次捕获的456,退出分组
  4. 退出重复分组,结束
  • 如果想要获得所有结果,就要把重复匹配放进分组中 /((\d\d\d){3})/
    在这里插入图片描述

断言

  • 先行肯定断言:x(?=y),仅当x后面为y时匹配x,y不作为返回结果。
    正则表达式相关_第4张图片
  • 后行肯定断言:(?<=y)x,仅当x前面为y时匹配x,y不作为返回结果
    正则表达式相关_第5张图片
  • 先行否定断言:x(?!y),仅当x后不为y时匹配x,y不作为返回结果
    正则表达式相关_第6张图片
  • 后行否定断言:(?<!y)x,仅当x前面不为为y时匹配x,y不作为返回结果
    正则表达式相关_第7张图片

位置锚定

行定位符

  • ^$可以用来指定开头和结尾,^\d.*就是任意以数字开头的字符串,.*\d$是任意以数字结尾的字符串,^&表示空行
  • 在使用正则限制长度输入时,如果只使用{m,n},在输入的长度小于m时,匹配失败,但如果输入的长度大于n,由于其中包含m为,所以会匹配成功
    正则表达式相关_第8张图片
  • 为了限制长度可以结合^$使用,^\d{4,7}$可以限制输入4到7位数字
    正则表达式相关_第9张图片

单词边界

  • \b用来匹配单词的边界,这里的单词指定是\w+,至于边界是什么,如下图,
    在这里插入图片描述
  • \b属于匹配位置的元字符,一般作占位作用,而不被捕获,同属于匹配位置的还有匹配行起始位^和行结束位`$

贪婪、非贪婪与回溯

  • 当使用*+{m,n}量词时,就涉及到贪婪与非贪婪的匹配模式,例如*是匹配任意除换行符外的字符,如果贪婪模式下,它会一次性匹配所有可以匹配上的字符;而如果是非贪婪模式下,它会尽可能少的匹配。
  • 如对于pythonnnnnnnnnnn这样一个字符串,贪婪模式下使用python*进行匹配,使用量词时默认位贪婪模式,结果如下,可以看出所有n都被匹配
    正则表达式相关_第10张图片
  • 而在非贪婪模式下匹配,在量词后面添加改为非贪婪模式,匹配结果如下,由于*匹配0次或多次,最少是0次,非贪婪模式下一个n都没有被匹配。
    正则表达式相关_第11张图片

回溯

  • 当使用量词导致当前前面分支/重复匹配成功后,没有多余的文本可被正则后半部分匹配时,会产生回溯;如使用.*\d进行匹配,.*将所有字符匹配完成后,还有个\d需要匹配,此时就会产生回溯
  • 用一个简单的例子来解释一下贪婪匹配和惰性匹配的回溯,贪婪 : /\d+\b/,惰性 : /d+?\b/,文本 : 1234a

贪婪正则匹配 1234a 时的过程是这样的:

  1. \d+ 匹配得到 1234
  2. \b 却匹配失败(\b 是分词边界匹配,用来获取位置,而不是文本)
  3. 这个时候,\d+会尝试回吐一个字符,即匹配结果为 123 ,可\b还是匹配失败!
  4. 那就继续回吐,一直到 1,还是匹配失败,那么这个正则就整体匹配失败了
  5. 这个回吐匹配结果的过程就是回溯

惰性正则匹配 1234a 时的过程是这样的:

  1. \d+? 首先匹配,结果是1 ,紧接着 \b 匹配失败
  2. 那就 \d+? 继续匹配,结果是 12 ,紧接着 \b 还是匹配失败
  3. \d+? 一直匹配到1234,紧接着的 \b 依然匹配失败
  4. 结果整个正则匹配不成功

正则使用案例

千分位

  • 我们在一些场景里需要将7654321输出成7,654,321这样的格式,这就是千分位
  • 用正则表达式去处理的话,关键是获取位置,那么首先想到的就是要利用非单词边界\B
  • 这条正则是能成功取得千分位的位置的:"\B(?=(\d{3})+(?!\d))"g

先将这个正则分成三个部分:
/\B(?=\d)/ 这里·\B是为了防止出现,123起始位置被匹配的问题,(?=\d)是非单词边界后紧跟数字
尝试一下8位数的数字: ‘12345678’ 在 /\B(?=(\d{3})+)/匹配得到什么结果呢?
最后 (?!\d) 是前面匹配成功后跟的非数字

  • ‘12345678’ 在 /\B(?=(\d{3})+)/匹配得到什么结果呢?

首先符合非单词边界\B的有1,2,3,4,5,6,7的右边位置
然后在\B后还得有n个3位数
匹配结果为:
匹配\B第1个非单词边界 1的右边位置,则后面(\d{3})+的结果为:234、567,8后面无法补齐3位,匹配得到567
匹配\B第2个非单词边界 2的右边位置,则后面(\d{3})+的结果为:345、678,匹配得到678
匹配\B第3个非单词边界 3的右边位置,则后面(\d{3})+的结果为:456、78后面无法补齐3位,匹配得到456
匹配\B第4个非单词边界 4的右边位置,则后面(\d{3})+的结果为:567、8后面无法补齐3位,匹配得到567
匹配\B第5个非单词边界 5的右边位置,则后面(\d{3})+的结果为:678
匹配\B第6个非单词边界 6的右边位置,但78无法补齐3位,
匹配\B第6个非单词边界 7的右边位置,但8无法补齐3位,
最终小括号分组匹配得到的分别是:567,678,456,567,678

  • 最后 (?!\d) 是前面匹配成功后跟的非数字,那连起来就是:
  1. 匹配\B第1个非单词边界 1的右边位置,则后面(\d{3})+的结果为:234、567,后面跟着8,不匹配
  2. 匹配\B第2个非单词边界 2的右边位置,则后面(\d{3})+的结果为:345、678,后面跟着非数字,位置匹配成
  3. 匹配\B第3个非单词边界 3的右边位置,则后面(\d{3})+的结果为:456,后面跟着7、8不匹配
  4. 匹配\B第4个非单词边界 4的右边位置,则后面(\d{3})+的结果为:567,后面跟着8,不匹配
  5. 匹配\B第5个非单词边界 5的右边位置,则后面(\d{3})+的结果为:678,后面跟着非数字,位置匹配成功
  6. 最终得到得到可插入逗号的位置为2,5
    12,345,678

限制用户密码

  • 通常用户在注册时,我们为了提高安全性,会在用户设置密码时添加一些限制,比如密码中必须包含小写字母、大写字母、数字、特殊符号、长度限制等,通常这种限制都是通过正则表达式实现的,这里我们一起探究下正则是如何实现这种限制的
  • 首先我们要求用户密码中必须包含数字,这如何限制?通过(?=\d)可以限制字符中必须包含数字,该断言没有x部分即代表x是所有字符,包括边界;当某个字符或边界后面有数字时,匹配成功。
  • 也可以用\b(?=.*\d)限制字符中必须包含数字,该表示中\b匹配到字符的首单词边界时,当且仅当任意位字符后有数字匹配成功
  • 同理得(?=.*\d)(?=.*[a-z])(?=.*[A-Z]),可以限制密码必须包含数字、小写字母、大写字母,其他同理
  • 那如何限制用户长度呢?断言匹配完成后,可以通过[0-9a-zA-Z]{8,16}限制密码的长度为8到16位
  • 最终表达式为^(?=.*\d)(?=.*[a-z])[a-z0-9]{8,16}$

正则与安全

防止sql注入与绕过

  • 打开sqsllab的第一关,我们首先在第一关的php文件中添加过滤语句
# 对用户输入的id进行正则匹配,若匹配到说明存在sql注入
if(preg_match('/select\b[\s\S]*\bfrom/is',$id)){
	die('SQL INJECTION');
}
  • 在页面输入sql语句进行测试
    正则表达式相关_第12张图片
  • 分析绕过:正则对select\b[\s\S]\bfrom``进行匹配,可以看出表达式中的select的后面和from的前面必须有单词边界,可以从这里下手,使用科学技术法绕过,原理为如下图,将1e1加到from前破坏单词边界使匹配失败
    正则表达式相关_第13张图片
  • 测试发现依然报错
    正则表达式相关_第14张图片
  • 原因是添加了1e1后产生了两列,而这里期望的结果只有一列
    正则表达式相关_第15张图片
  • 修改表达式,绕过成功
    正则表达式相关_第16张图片
  • 回溯绕过,由于该正则采用了量词的贪婪匹配,我们可以利用回溯绕过限制,php的回溯次数不能大于一百万次,否则匹配失败,所以我在我需要注入的语句后加入一百万个被注释的a,由于是贪婪匹配,将一百万个a全部匹配,然后回溯匹配边界符,由于回溯次数大于一百万,匹配失败,所以注入成功

正则表达式相关_第17张图片

利用回溯绕过正则限制

案例1

  • 如下语句是为了过滤一句话木马的表达式,最简单的一句话木马为

function is_php($data){
	return preg_match('/<\?.*[(`;?>)].*/is', $data);
}

if(isset($_POST['input'])){
	$input=$_POST['input'];
}
if(!is_php($input)){
	echo 'flag';
}
  • 为了绕过该正则,我们可以利用回溯,php中限制正则回溯的次数为一百万次,超过这个次数则认为是匹配失败,可以看出代码中是利用了贪婪模式进行匹配,所以我们可以向php发送一段这样的输入:在后添加一百万个a
  • 通过python向后端发送post请求
    正则表达式相关_第18张图片
  • 运行后成功将一句话木马传到后端
    正则表达式相关_第19张图片

案例2

  • 依据是使用sqllab的第一关环境,添加下述语句,对接收到的参数进行正则匹配
if(preg_match('/union.+?select/is',$id)){
	die('SQL INJECTION');
}
  • 可以看出表达式是用来过滤联合查询的,由于它使用了+进行匹配,所以我依然利用回溯绕过,由于是非贪婪匹配,所以我们需要在union和select之间添加一百万个注释a,非贪婪模式下会对一百万个a一个一个的尝试,python代码如下:
    正则表达式相关_第20张图片

案例3

  • 后端语句如下:对Merry Christmas进行过滤,要求正则匹配失败且Merry Christmas仍存在输入中

function areyouok($greeting){
	retunr preg_match('/Merry.*Christmas/is',$greeting);
}
%greeting=$_POST['greeting'];
if(!areyouok($greeting)){
	if(strops($greeting,'Merry Christmas')!==false){
		echo 'flag'
	}else{
		echo 'die';
	}
}else{
	echo 'do you know php';
}
  • 由于是量词的贪婪模式进行匹配,同理在Merry Christmas后加一百万个a,测试结果如下:
    正则表达式相关_第21张图片
  • 本案例还有一个绕过方式,strpos()函数如果接受到一个数组作为参数,会返回null,null 和 false 严格不相等,所以绕过
    正则表达式相关_第22张图片
php类型比较

正则表达式相关_第23张图片

你可能感兴趣的:(正则表达式)