一.上传漏洞
定义
文件上传漏洞是指用户上传了一个可执行的脚本文件,并通过此脚本文件获得了执行服务器端命令的能力。这种攻击方式是最为直接和有效的,“文件上传” 本身没有问题,有问题的是文件上传后,服务器怎么处理、解释文件。如果服务器的处理逻辑做的不够安全,则会导致严重的后果。
危害
1.上传文件是web脚本语言,服务器的web容器解释并执行了用户上传的脚本,导致代码执行。
2.上传文件是病毒或者木马时,主要用于诱骗用户或者管理员下载执行或者直接 自劢运行;
3.上传文件是Flash的策略文件 crossdomain.xml,黑客用以控制Flash在该域 下的行为(其他通过类似方式控制策略文件的情况类似);
4.上传文件是病毒、木马文件,黑客用以诱骗用户或者管理员下载执行;
5.上传文件是钓鱼图片或为包含了脚本的图片,在某些版本的浏览器中会被作为脚本执行,被用于钓鱼和欺诈。 除此之外,还有一些不常见的利用方法,比如将上传文件作为一个入口,溢 出服务器的后台处理程序,如图片解析模块;或者上传一个合法的文本文件,其内容包含了PHP脚本,再通过"本地文件包含漏洞(Local File Include)"执行此脚本。
原理
大部分的网站和应用系统都有上传功能,一些文件上传功能实现代码没有严格限制用户上传的文件后缀以及文件类型,导致允许攻击者向某个可通过web访问的目录上传任意PHP文件,并能够将这些文件传递给PHP解释器,就可以在 进程服务器上执行任意PHP脚本。
当系统存在文件上传漏洞时攻击者可以将病毒,木马,WebShell,其他恶意脚本或者是包含了脚本的图片上传到服务器,这些文件将对攻击者后续攻击提供便利。根据具体漏洞的差异,此处上传的脚本可以是正常后缀的PHP,ASP以及JSP脚本,也可以是篡改后缀后的这几类脚本。
二.案例
搭建环境
第一次漏洞
<?php
header("Content-Type:text/html; charset=utf-8");
require_once('pclzip.lib.php');
$file = $_FILES['file'];
if (!$file) {
exit("请勿上传空文件");
}
$name = $file['name'];
$dir = 'upload/';
$ext = strtolower(substr(strrchr($name, '.'), 1));
//递归删除 zip 1 web.php
function check_dir($dir)
{
$handle = opendir($dir);
while (($f = readdir($handle)) !== false) {
if (!in_array($f, array('.', '..'))) {
$ext = strtolower(substr(strrchr($f, '.'), 1));
if (!in_array($ext, array('jpg', 'gif', 'png'))) {
unlink($dir . $f);
}
}
}
}
if (!is_dir($dir)) {
mkdir($dir);
}
// $temp_dir = $dir.md5(time(). rand(1000,9999)).'/';
$temp_dir = $dir . 'member/1/';
if (!is_dir($temp_dir)) {
mkdir($temp_dir);
}
if (in_array($ext, array('zip', 'jpg', 'gif', 'png'))) {
if ($ext == 'zip') {
if ($archive->extract(PCLZIP_OPT_PATH, $temp_dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
check_dir($dir);
exit("解压失败");
}
check_dir($temp_dir);
exit('上传成功!');
} else {
move_uploaded_file($file['tmp_name'], $temp_dir . '/' . $file['name']);
check_dir($temp_dir);
exit('上传成功!');
}
} else {
exit('仅允许上传zip、jpg、gif、png文件!');
}
可以看到这个是直接删除第一个文件夹里面的内容,但是删除不了第二个文件夹里面的东西。
我们将这两个文件打包压缩
上传成功后可以看到只有一个图片,php被删了
但是你在桌面创建一个新的文件夹,把照片和php放入文件夹中,再把这个文件夹压缩,上传这个被压缩的文件夹之后,发现php并没有被删除
小结
未进行递归删除,导致文件夹内的webshell得以保留
修复方法(递归删除,又造成了新的问题)
第二次漏洞
<?php
header("Content-Type:text/html; charset=utf-8");
require_once('pclzip.lib.php');
// echo "<pre>";
// var_dump($_POST['username']);exit;
// // var_dump(file_get_contents("php://input"));exit;
// // var_dump($GLOBALS['HTTP_RAW_POST_DATA']);exit;
// if (!isset($GLOBALS['HTTP_RAW_POST_DATA'])) {
// exit('环境不支持');
// }
// // 创建图片存储文件夹
// $dir = 'upload/';
// if (!file_exists($dir)) {
// mkdir($dir);
// }
function check_dir($dir){
$handle = opendir($dir);
while(($f = readdir($handle)) !== false){
if(!in_array($f, array('.', '..'))){
if(is_dir($dir.$f)){
check_dir($dir.$f.'/');
}else{
$ext = strtolower(substr(strrchr($f, '.'), 1));
if(!in_array($ext, array('jpg', 'gif', 'png'))){
unlink($dir.$f);
}
}
}
}
}
// // 创建图片存储的临时文件夹
// $temp = $dir.'member/1/';
// if (!file_exists($temp)) {
// mkdir($temp);
// }
// $filename = $temp.'avatar.zip'; // 存储flashpost图片
// file_put_contents($filename, $GLOBALS['HTTP_RAW_POST_DATA']);
// //第三次绕过
// // $zip=new ZipArchive;//新建一个ZipArchive的对象
// // if(!$zip->open($filename)){
// // //check_dir($dir);
// // exit("fail to open zip file");
// // }
// // if(!$zip->extractTo($temp)) {
// // exit("fail to extract zip file");
// // }
// $archive = new PclZip($filename);
// if ($archive->extract(PCLZIP_OPT_PATH, $temp, PCLZIP_OPT_REPLACE_NEWER) == 0) {
// check_dir($dir);
// exit("解压失败");
// }
// check_dir($dir);
// exit('success');
$file = $_FILES['file'];
if (!$file) {
exit("请勿上传空文件");
}
$name = $file['name'];
$dir = 'upload/';
$ext = strtolower(substr(strrchr($name, '.'), 1));
//递归删除 zip 1 web.php
// function check_dir($dir)
// {
// $handle = opendir($dir);
// while (($f = readdir($handle)) !== false) {
// if (!in_array($f, array('.', '..'))) {
// $ext = strtolower(substr(strrchr($f, '.'), 1));
// if (!in_array($ext, array('jpg', 'gif', 'png'))) {
// unlink($dir . $f);
// }
// }
// }
// }
// mkdir($dir);
if (!is_dir($dir)) {
mkdir($dir);
}
/**
* 1.未进行递归删除,导致文件夹内的webshell得以保留
* 修复方法(递归删除,又造成了新的问题)
* 2.由于先解压,再删除,导致暴力getshell
* (时间竞争拿下webshell)
* 3.将上传目录改为随机数,不能访问,自然不能getshell
* (可以创造一个加压出错的zip包,虽然解压出错,但其他webshell,以及被解压出来
* 同时,出错直接exit结束程序,不会执行后续递归删除操作
* )
* 4.解压即便失败,依然使用递归删除,然后退出,这样一来,可以将解压出的文件删除
* (制造一个特殊名称的webshell,../../../../web.php,当解压时,直接解压到根目录
* 随后,无论如何删除,也不能影响我们getshell
* )
* 可以将压缩解压缩操作放在非web目录下执行,将需要的文件移动到web目录下,这样可以从
* 根本解决以上问题
*
* 找到问题的关键,才可以解决,不然无异于盲人摸象。
*/
// $temp_dir = $dir.md5(time(). rand(1000,9999)).'/';
$temp_dir = $dir . 'member/1/';
if (!is_dir($temp_dir)) {
mkdir($temp_dir);
}
if (in_array($ext, array('zip', 'jpg', 'gif', 'png'))) {
if ($ext == 'zip') {
// $zip = new ZipArchive;
// if(!$zip->open($file['tmp_name'])) {
// echo "fail";
// return false;
// }
// if(!$zip->extractTo($temp_dir)) {
// check_dir($temp_dir);
// exit("fail to extract");
// }
$archive = new PclZip($file['tmp_name']);
// foreach($archive->listContent() as $value){
// $filename = $value["filename"];
// if(preg_match('/\.php$/', $filename)){
// exit("压缩包内不允许含有php文件!");
// }
// }
if ($archive->extract(PCLZIP_OPT_PATH, $temp_dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
check_dir($dir);
exit("解压失败");
}
check_dir($temp_dir);
exit('上传成功!');
} else {
move_uploaded_file($file['tmp_name'], $temp_dir . '/' . $file['name']);
check_dir($temp_dir);
exit('上传成功!');
}
} else {
exit('仅允许上传zip、jpg、gif、png文件!');
}
这里会判断有没有新的文件夹,如果有的话会进入再删一次
这里有一个逻辑问题,是先判断有没有文件夹,有的话他才会进入删除,所以我们可以直接在他删除直接,在上级或者上上级目录生成一句话木马,利用时间竞争漏洞来写入文件。
<?php
fputs(fopen('../../../pauload.php','w'),
'phpinfo(); ?>')
?>
然后利用burp suite抓包时间竞争
全选右键放到intruder,先清除,再随意找到一个数字添加。
运行结束后在upload\member\1下去找是否成功
小结
由于先解压,再删除,导致暴力getshell
- (时间竞争拿下webshell)
第三次漏洞
<?php
header("Content-Type:text/html; charset=utf-8");
require_once('pclzip.lib.php');
function check_dir($dir){
$handle = opendir($dir);
while(($f = readdir($handle)) !== false){
if(!in_array($f, array('.', '..'))){
if(is_dir($dir.$f)){
check_dir($dir.$f.'/');
}else{
$ext = strtolower(substr(strrchr($f, '.'), 1));
if(!in_array($ext, array('jpg', 'gif', 'png'))){
unlink($dir.$f);
}
}
}
}
}
$file = $_FILES['file'];
if (!$file) {
exit("请勿上传空文件");
}
$name = $file['name'];
$dir = 'upload/';
$ext = strtolower(substr(strrchr($name, '.'), 1));
if (!is_dir($dir)) {
mkdir($dir);
}
$temp_dir = $dir.md5(time(). rand(1000,9999)).'/';
// $temp_dir = $dir . 'member/1/';
if (!is_dir($temp_dir)) {
mkdir($temp_dir);
}
if (in_array($ext, array('zip', 'jpg', 'gif', 'png'))) {
if ($ext == 'zip') {
$archive = new PclZip($file['tmp_name']);
if ($archive->extract(PCLZIP_OPT_PATH, $temp_dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
check_dir($dir);
exit("解压失败");
}
check_dir($temp_dir);
exit('上传成功!');
} else {
move_uploaded_file($file['tmp_name'], $temp_dir . '/' . $file['name']);
check_dir($temp_dir);
exit('上传成功!');
}
} else {
exit('仅允许上传zip、jpg、gif、png文件!');
}
这次是名称用一段随机数来替代,文件夹的名称是随机的。那样的话就没有办法进行时间竞争,但是,这里也是存在一个判断,让他解压出错,那么不就可以直接退出判断,可以进行解压,但是因为解压失败,所以就直接跳过了删除部分,php也就不会被删除
这里利用010 Editor来进行修改压缩包,让他解压报错
出错但是依旧解压完成
我们再用PHP自带的ZipArchive库(代码如图4)测试这个zip,发现解压并没有出错,这也说明ZipArchive的容忍度比较高。
那么我们又让ZipArchive出错最简单的方法,我们可以修改文件名
比如,Windows下不允许文件名中包含冒号(:),
我们就可以在010editor中将2.txt的deFileName属性的值改成“2.tx:”
此时解压就会出错,但1.php被保留了下来
在Linux下也有类似的方法,我们可以将文件名改成5个斜杠(/),此时Linux下解压也会出错,但1.php被保留了下来
小结
将上传目录改为随机数,不能访问,自然不能getshell可以创造一个加压出错的zip包,虽然解压出错,但其他webshell,以及被解压出来同时,出错直接exit结束程序,不会执行后续递归删除操作)
第四次漏洞
<?php
header("Content-Type:text/html; charset=utf-8");
require_once('pclzip.lib.php');
$dir = 'upload/';
if (!file_exists($dir)) {
mkdir($dir);
}
function check_dir($dir){
$handle = opendir($dir);
while(($f = readdir($handle)) !== false){
if(!in_array($f, array('.', '..'))){
if(is_dir($dir.$f)){
check_dir($dir.$f.'/');
}else{
$ext = strtolower(substr(strrchr($f, '.'), 1));
if(!in_array($ext, array('jpg', 'gif', 'png'))){
unlink($dir.$f);
}
}
}
}
}
$file = $_FILES['file'];
if (!$file) {
exit("请勿上传空文件");
}
$name = $file['name'];
$dir = 'upload/';
$ext = strtolower(substr(strrchr($name, '.'), 1));
if (!is_dir($dir)) {
mkdir($dir);
}
$temp_dir = $dir.md5(time(). rand(1000,9999)).'/';
// $temp_dir = $dir . 'member/1/';
if (!is_dir($temp_dir)) {
mkdir($temp_dir);
}
if (in_array($ext, array('zip', 'jpg', 'gif', 'png'))) {
if ($ext == 'zip') {
$zip = new ZipArchive;
if(!$zip->open($file['tmp_name'])) {
echo "fail";
return false;
}
if(!$zip->extractTo($temp_dir)) {
check_dir($temp_dir);
exit("fail to extract");
}
$archive = new PclZip($file['tmp_name']);
if ($archive->extract(PCLZIP_OPT_PATH, $temp_dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
check_dir($dir);
exit("解压失败");
}
check_dir($temp_dir);
exit('上传成功!');
} else {
move_uploaded_file($file['tmp_name'], $temp_dir . '/' . $file['name']);
check_dir($temp_dir);
exit('上传成功!');
}
} else {
exit('仅允许上传zip、jpg、gif、png文件!');
}
这里我们利用一个技巧:修改特殊名称,…/…/…/…/web.php,当解压时,直接解压到根目录 随后,无论如何删除,也不能影响我们getshell。继续利用上一个漏洞的特性,让他解压失败,然后进行删除,而留下来的就是php文件,将php留到上一个目录
依旧利用010打开,修改名字
成功 解压到了上上级目录
小结
解压即便失败,依然使用递归删除,然后退出,这样一来,可以将解压出的文件删除制造一个特殊名称的webshell,…/…/…/…/web.php,当解压时,直接解压到根目录随后,无论如何删除,也不能影响我们getshell)可以将压缩解压缩操作放在非web目录下执行,将需要的文件移动到web目录下,这样可以从根本解决以上问题找到问题的关键,才可以解决,不然无异于盲人摸象。