Bugkuctf题库中的一道代码审计题,通过利用各种正则匹配函数特性最终得到flag
源代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <?php error_reporting(0); $flag = 'flag{test}'; if ("POST" == $_SERVER['REQUEST_METHOD']) { $password = $_POST['password']; if (0 >= preg_match('/^[[:graph:]]{12,}$/', $password)) //preg_match — 执行一个正则表达式匹配 { echo 'flag'; exit; } while (TRUE) { $reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/'; if (6 > preg_match_all($reg, $password, $arr)) break; $c = 0; $ps = array('punct', 'digit', 'upper', 'lower'); //[[:punct:]] 任何标点符号 [[:digit:]] 任何数字 [[:upper:]] 任何大写字母 [[:lower:]] 任何小写字母 foreach ($ps as $pt) { if (preg_match("/[[:$pt:]]+/", $password)) $c += 1; } if ($c < 3) break; //>=3,必须包含四种类型三种与三种以上 if ("42" == $password) echo $flag; else echo 'Wrong password'; exit; } } ?>
|
请求方法必须为POST
首先弄明白正则匹配函数:
preg_match:执行一个正则表达式匹配,匹配到则返回1,匹配不到则返回0
preg_match_all:执行一个全局正则表达式匹配,返回成功模式匹配的次数,并将匹配结果存储到一个数组中
两个函数的区别是preg_match第一次匹配成功后就停止匹配,而preg_match_all是匹配到字符串结束为止
再弄明白几个正则匹配的特殊字符:
[:graph:] : 除空格,TAB外的所有字符
[:punct:] : 任何标点符号
[:digit:] : 任何数字
[:upper:] : 任何大写字母
[:lower:] : 任何小写字母
接下来来到第一个判断地方:
1 2 3 4 5
| if (0 >= preg_match('/^[[:graph:]]{12,}$/', $password)) //preg_match — 执行一个正则表达式匹配 { echo 'flag'; exit; }
|
如果这个if语句执行成功,那么我们就获取不到后面的flag值,所以不能让if执行,也就是preg_match必须返回1,即正则匹配成功
在看正则表达式’/^[[:graph:]]{12,}$/’
必须以任意一个除空格,TAB外的标点符号开头和结尾,且出现至少12次
验证如下:
1 2 3 4 5 6 7 8 9
| <?php
if(isset($_GET['password'])){ $password=$_GET['password']; $a=preg_match('/^[[:graph:]]{12,}$/',$password); echo '$a='.$a; }
?>
|
输入12个1,看到结果返回1
输入小于12个,看到结果返回0
这里需要注意,输入’+’ 会被当做是空格处理,要先将’+’进行URL编码%2b
综上,第一个输入要求,输入除空格,TAB外的字符至少十二次
在看下一个判断条件:
1 2 3
| $reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/'; if (6 > preg_match_all($reg, $password, $arr)) break;
|
如果if语句成功执行,break退出循环,我们就得不到flag值
所以我们不能让if语句成功执行,也就是说要让全局匹配成功次数大于6次
在看正则表达式’/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/’
因为是全局匹配,所以匹配成功的条件是检测到任何符号出现1次以上或者任何数字出现1次以上或者任何大写字母出现1次以上或者任何小写字母出现1次以上,一旦匹配成功一次,就开始检测下一次的匹配,这么说有点难理解,直接上代码验证:
1 2 3 4 5 6 7 8 9 10 11
| <?php
if(isset($_GET['password'])){ $password=$_GET['password']; $reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/'; $a=preg_match_all($reg,$password,$arr); echo '$a='.$a."<br>"; print_r($arr); }
?>
|
这里输入1aB.
匹配的过程是这样的:先检测到1,符合[:digit:]出现1次,继续检测,检测到a,符合[:lower:]出现1次,继续检测,以此类推,所以最后检测成功次数为4
同时也可以看出返回的数组有两个元素,第一个元素是整个匹配结果,第二个元素是子模式的匹配结果
关于返回数组的详情可以参考:https://blog.csdn.net/qq_27988539/article/details/77366329
这题因为不牵涉到数组,就不详细研究
重点关注成功匹配的次数
这里特别说明什么时候是一次匹配的结束,就是检测到不是属于同一种特殊字符为止,因为这里每种特殊字符可以出现1次或者多次,举个例子,我们输入
第一次匹配结束是检测到a字符,不符合[:digit:],所以开始第二次匹配
因为这里要成功匹配6次以上,所以每种类型的字符必须间隔出现6次以上,结合第一个条件,字符出现12个以上,于是我们可以输入11aaBB..22cc
验证一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?php
if(isset($_GET['password'])){ $password=$_GET['password']; if(0>=preg_match('/^[[:graph:]]{12,}$/',$password)){ echo 'Wrong Format 1'; exit; } $reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/'; if(6>preg_match_all($reg,$password,$arr)){ echo 'Wrong Format 2'; exit; } echo 'success'; }
?>
|
结果如下:
再来看第三个条件:
1 2 3 4 5 6 7 8
| $c = 0; $ps = array('punct', 'digit', 'upper', 'lower'); //[[:punct:]] 任何标点符号 [[:digit:]] 任何数字 [[:upper:]] 任何大写字母 [[:lower:]] 任何小写字母 foreach ($ps as $pt) { if (preg_match("/[[:$pt:]]+/", $password)) $c += 1; } if ($c < 3) break;
|
如果$c<3,那么我们就得不到flag值,也就是说要让$c>=3,即成功匹配三次或以上
即出现三种类型字符或以上,按上面的输入:11aaBB..22cc,就行了,验证一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?php
if(isset($_GET['password'])){ $password=$_GET['password']; if(0>=preg_match('/^[[:graph:]]{12,}$/',$password)){ echo 'Wrong Format 1'; exit; } $reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/'; if(6>preg_match_all($reg,$password,$arr)){ echo 'Wrong Format 2'; exit; } $c=0; $ps=array('punct','digit','upper','lower'); foreach($ps as $pt){ if(preg_match("/[[:$pt:]]+/",$password)){ $c += 1; } } if($c<3){ echo 'Wrong Format 3'; exit; } echo 'success'; }
?>
|
再看最后一个关键条件:
1
| if("42" == $password) echo $flag;
|
执行了这个语句我们才可以得到想要的flag,也就是password值等于’42’,因为类型都是字符串,所以password中的值必须等于42,结合上面的三个条件
成功的输入可以有:
42.000e%2b000000000
420.000000000000e-1