HCTF2018 Warmup1 代码审计

第一步:查看源码

一张大脸,先检查源码:发现:

直接访问这个文件,发现一堆代码,需要代码审计:

第二步,开始审计

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
44
45
46
47
48
49
<?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}

if (in_array($page, $whitelist)) {
return true;
}

$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}

$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}

if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>

这道题总结出来的经验:
先不管前面的类,直接看后面的怎么包含file。
因为我们的逻辑很清楚:
要想得到下一步的线索,就必须利用输入的payloads得到更多的回显,而要得到回显,就是要成功通过前面checkFile()的检查,使得我们传入的被$_REQUEST参数能够被include包含,从而取得回显。
在类后面要被include的条件是:
file的内容是非空的,
file要是字符串
在emm类里面继承过来的file文件需要经过处理之后返回为true
关键是checkFile要为true。那我们就来看checkFile里面怎么让file检查为true.

第一步,参数传入,以获得线索

这里我们就先看了前几个代码块,先看看要满足他们的条件,我们需要传入怎样的参数。
这也算是做题的经验了,因为我们不可能一步到位直接得到flag所在文件的名称,先要对前面的函数进行试探输入payloads得到线索。

1
2
3
4
5
6
7
8
9
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}

if (in_array($page, $whitelist)) {
return true;
}

($page在这里指的是checkFile()这个函数里面的参数)
首先定义了一个数组,其中有source.php和hint.php,如果没有检查到page或者page不为字符串,那么就会返回为false:
有返回,那这个checkFile函数也就不会再继续下去了,并且也会返回false,导致我们的$file参数无法被include读取。
这里有个重要的知识点:
即if括号内部的布尔值对程序整体运行的影响,以及return对整体程序的影响,具体见我分类分在php类别里的博客。
由于hint.php在白名单中,完全符合整个checkFile()返回true的条件。
那我们先试一下hint.php,显示:

第二步,思考如何利用后续代码块特性绕过。

这里我们肯定不方便直接抓取这个文件,(这里的直接抓取就是指直接把ffffllllaaaagggg引入REQUEST里面的file参数内,因为这样肯定会被checkFile过滤掉并且返回false,无法被include读取)
并且根据这道题后面的条件来看,我们要读取这个文件就要使用上include函数,所以我们要继续往后看,看还需要绕过哪些条件。

1
2
3
4
5
6
7
8
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}

解读代码:
这里进行了一个代码的截取,操作,是我们绕过checkFile()函数的关键
这里substr截取代码时的第三个参数是截取代码的长度,第二个参数是开始的长度
而巧妙之处就在于:

1
mb_strpos($_page,'?','?')

这段代码将page手动加了一个?然后探测?的位置,而在主流编程语言中,字符串位置的参数都是从0开始的,例如我要探测:

1
mmk&nina?

这里?的位置在参数8的位置,而截取的时候,长度是8,所以刚好把?前面的截取走了,并没有包含?

第三步,配置payloads,开始绕过

所以利用这里$_page截取?前面的字符,并只将这部分截取的字符给函数检查的特性,我们可以在payloads的?前写为:

1
/?file=hint.php?

这样$_page就会只截取到hint.php,并且它确实在白名单内,我们得以成功绕过
重点又来了:
include有个特性:
在它读取的文件部分,我们可以用“/”符号把文件分成几个部分:

1
include(example1.php\/example2.php)

include会挨个读取文件,即使第一个文件不存在,它的读取也不会就此停止
所以:我们可以在hint.php?后面先用”/“符号进行分=分割,再输入我们想读取的文件,flag
这里太坑爹了,flag长成:ffffllllaaaagggg,这居然是在暗示我们,flag在上数四级文件夹的目录下,难绷。
所以最终的payloads为:

1
/?file=hint.php?/../../../../ffffllllaaaagggg

?前面是为了绕过checkFile()的检查,后面则是我们的文件目标
而include在读取的时候因为前面hint.php?没法读取,就会读取”/“后的文件,从而顺利得到flag。
大功告成,若有不足,希望dalao指出(