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注入,通过这个注入我们可以修改filename
和extension
.
首先想到修改原文件在数据库中的后缀为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、 选择文件上传:
2、rename造成注入:
3、上传真正包含webshell的文件x.jpg:
4、重命名进行getshell:
5、成功