0x01 filemanager

0x01 分析

上传有黑名单,无法直接传php,在rename.php中存在这二次注入问题

1
2
result = $db->query("select * from `file` where `filename`='{$req['oldname']}'");		
$re = $db->query("update `file` set `filename`='{$req['newname']}', `oldname`='{$result['filename']}' where `fid`={$result['fid']}");

rename.php可以对文件进行重命名并且没有黑名单限制,但是最后会自动加上重命名文件的后缀extension,这个后缀是从数据库中取出来的肯定是在白名单中的

1
2
3
4
5
$oldname = UPLOAD_DIR . $result["filename"] . $result["extension"];
$newname = UPLOAD_DIR . $req["newname"] . $result["extension"];
if (file_exists($oldname)) {
rename($oldname, $newname);
}

这就利用到了update注入,通过这个注入我们可以修改filenameextension.

首先想到修改原文件在数据库中的后缀为php或者为空,这里不能修改为php是因为下面的file_exists要求原文件必须已经存在,显然php文件不可能已经存在(不然还费啥劲),那就只能修改为空

实际存在的文件肯定有后缀,而且是白名单中的,那我们修改后缀为空后,数据库中的文件名filename就应该含有后缀

1
2
3
4
5
源文件 a.jpg
update注入前数据库中的存储: filename="a" extension=".jpg"
update注入后: filename="a.jpg" extension=""
拼凑成文件名的方式是 filename+extension
所以注入前注入后拼凑的文件名相同

这就要求在上传文件的时候要含有两个后缀,例如:a.jpg.jpg,数据库中存储的数据为filename="a.jpg" extension=".jpg",实际存在的文件为a.jpg.jpg

上传这个文件后经过第一次update注入变成了filename="a.jpg" extension=""

然后经过file_exists检查,a.jpg实际是不存在的,所以我们重新上传一个新的文件a.jpg,来使实际存在的文件中包含a.jpg从而绕过file_exists的检查.

此时的extension已经成了空,所以下一步就准备修改文件名为a.php

0x03 官方wp

一、入口:二次注入漏洞

此题入口点是二次注入。 在common.inc.php中可以看到全局进行了转义,这样常规注入少了大部分。遍观代码,输入处没有任何反转义、反解压、数字型等特殊情况,基本可以确定不存在直接的注入漏洞。 看到上传处的代码upload.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
$name = basename($file["name"]);
$path_parts = pathinfo($name);
if(!in_array($path_parts["extension"], ["gif", "jpg", "png", "zip", "txt"])) {
exit("error extension");
}
$path_parts["extension"] = "." . $path_parts["extension"];
$name = $path_parts["filename"] . $path_parts["extension"];
$path_parts["filename"] = $db->quote($path_parts["filename"]);
$fetch = $db->query("select * from `file` where
`filename`={$path_parts['filename']}
and `extension`={$path_parts['extension']}");
if($fetch && $fetch->fetchAll()) {
exit("file is exists");
}
if(move_uploaded_file($file["tmp_name"], UPLOAD_DIR . $name)) {
$re = $db->exec("insert into `file`
( `filename`, `view`, `extension`) values
( {$path_parts['filename']}, 0, '{$path_parts['extension']}')");
if(!$re) {
print_r($db->errorInfo());
exit;
}

可见,上传的文件名走过的流程是: $file[‘name’] -> pathinfo() –> $path_parts["filename"] -> quote() -> insert

由于经过了pdo的quote方法转义,所以此处也不存在注入。 再看到rename.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$result = $db->query("select * from `file` where `filename`='{$req['oldname']}'");
if ($result) {
$result = $result->fetch();
}
if(!$result) {
exit("old file doesn't exists!");
} else {
$req['newname'] = basename($req['newname']);
$re = $db->exec("update `file` set
`filename`='{$req['newname']}',
`oldname`='{$result['filename']}'
where `fid`={$result['fid']}");

根据$req['filename']从数据库里查询到已存在的一行,并调用update语句进行修改。 但这里oldname='{$result['filename']}' 将从数据库里查出的$result['filename']再一次入库,结果造成一个二次注入。

二、利用二次操作进行getshell

那么注入有什么用? 这应该是大家拿到题目,想到的第一个问题。这题明显与getshell有关,源码里包含文件上传、文件改名、文件删除等函数。 我们来一个个分析。

首先upload.php是文件上传的操作,但可见上传处对文件进行了白名单验证:

1
2
3
4
<?php
if(!in_array($path_parts["extension"], ["gif", "jpg", "png", "zip", "txt"])) {
exit("error extension");
}

导致我们无法上传恶意文件。

其次是delete.php,这个文件其实是个烟雾弹,删除操作并不能利用。 再次是rename.php,这里明显是getshell的关键。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
$result = $db->query("select * from `file` where `filename`='{$req['oldname']}'");
if ($result) {
$result = $result->fetch();
}
if(!$result) {
exit("old file doesn't exists!");
} else {
$req['newname'] = basename($req['newname']);
$re = $db->exec("update `file` set
`filename`='{$req['newname']}',
`oldname`='{$result['filename']}'
where `fid`={$result['fid']}");
if(!$re) {
print_r($db->errorInfo());
exit;
}
$oldname = UPLOAD_DIR . $result["filename"] . $result["extension"];
$newname = UPLOAD_DIR . $req["newname"] . $result["extension"];
if(file_exists($oldname)) {
rename($oldname, $newname);
}

最重要的就是后面这5行。 Oldname和newname,有几个特点:

后缀相同,都是$result[‘extension’] oldname的文件名来自数据库,newname的文件名来自用户输入 首先后缀相同这个特点,就导致getshell似乎难以完成,如果要getshell那么一定要将“非.php”后缀的文件重命名成“.php”的文件。后缀相同怎么重命名? 除非后缀为空! 所以我们的update型注入就开始派上用场了。通过update型注入,我们可以将数据库中extension字段的值改为空,同时也可以控制filename的值,那么等于说我能控制rename函数的两个参数的值,这样getshell就近在咫尺了。

但还有个坑,这里改名的时候检查了文件是否存在:if(file_exists($oldname))我虽然通过注入修改了filename的值,但我upload目录下上传的文件名是没有改的。 因为我利用注入将extension改为空了,那么实际上数据库中的filename总比文件系统中真是的文件名少一个后缀。 那么这里的file_exists就验证不过。怎么办? 简单啊,再次上传一个新文件,这个文件名就等于数据库里的filename的值就好了。

所以最后整个getshell的流程,实际上是一个二次注入 + 二次操作getshell。

三、具体流程

1、 选择文件上传: img

2、rename造成注入:

img

3、上传真正包含webshell的文件x.jpg:

img

4、重命名进行getshell:

img

5、成功

img