先扫目录,得到源码
代码审计
此题入口点是二次注入。 在common.inc.php中可以看到全局进行了转义,这样常规注入少了大部分。遍观代码,输入处没有任何反转义、反解压、数字型等特殊情况,基本可以确定不存在直接的注入漏洞。 看到上传处的代码upload.php:
<?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:
<?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是文件上传的操作,但可见上传处对文件进行了白名单验证:
<?php if(!in_array($path_parts["extension"], ["gif", "jpg", "png", "zip", "txt"])) { exit("error extension"); }
导致我们无法上传恶意文件。
其次是delete.php,这个文件其实是个烟雾弹,删除操作并不能利用。 再次是rename.php,这里明显是getshell的关键。
<?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、 选择文件上传:
文件名改为
',extension='',filename='x.jpg.jpg
2、rename造成注入:
3、上传真正包含webshell的文件x.jpg:
4、重命名进行getshell:
5、成功
flag{bdda3c944a9e484eae50123afeeff56b}