php 绕过 正则,数字验证正则绕过

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

5if (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

ba4fa76228ca893e5094adc1f4791201.png

输入小于12个,看到结果返回0

1166ac183a3a1dd8b89edda07394caf6.png

这里需要注意,输入’+’ 会被当做是空格处理,要先将’+’进行URL编码%2b

2e70c18a920f22b4bc001958aac20cd5.png

5bd8cdf75184d720f4676091a8257c80.png

综上,第一个输入要求,输入除空格,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."
";

print_r($arr);

}

?>

f1dada6c2f2fa2c9fc9afcc8270c1425.png

这里输入1aB.

匹配的过程是这样的:先检测到1,符合[:digit:]出现1次,继续检测,检测到a,符合[:lower:]出现1次,继续检测,以此类推,所以最后检测成功次数为4

同时也可以看出返回的数组有两个元素,第一个元素是整个匹配结果,第二个元素是子模式的匹配结果

这题因为不牵涉到数组,就不详细研究

重点关注成功匹配的次数

这里特别说明什么时候是一次匹配的结束,就是检测到不是属于同一种特殊字符为止,因为这里每种特殊字符可以出现1次或者多次,举个例子,我们输入

21f801ccd5b3207dae3ba4511615add4.png

第一次匹配结束是检测到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';

}

?>

结果如下:

33b4a4d68a9b654e925fa91f044327ba.png

再来看第三个条件:

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';

}

?>

54f32d48f99d7998fd9c99660d3f07f7.png

再看最后一个关键条件:

1if("42" == $password) echo $flag;

执行了这个语句我们才可以得到想要的flag,也就是password值等于’42’,因为类型都是字符串,所以password中的值必须等于42,结合上面的三个条件

成功的输入可以有:

42.000e%2b000000000

420.000000000000e-1

ec5fdf9746754b63e53a0bb6f35f8d77.png

9ffb51c40a3835d8c208b100cfa86329.png

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值