原始漏洞

漏洞原理:我们上传一个zip的压缩包,它会解压然后删除其中不是.jpg .gig .png的文件

function check_dir($dir):这是一个PHP函数的定义,它接受一个参数 $dir,代表要检查的目录路径。

phpcms上传漏洞_上传dir);:使用 opendir 函数打开指定目录,并将返回的目录句柄赋值给变量 $handle。

while ((phpcms上传漏洞_上传_02handle)) !== false):使用 readdir 函数循环读取目录中的文件,每次循环将文件名赋给变量 $f。

if (!in_array($f, array('.', '..'))) { ... }:在每次循环中,检查当前文件名是否为当前目录(.)或父目录(..),如果不是,则执行下面的操作。

phpcms上传漏洞_上传_03f, '.'), 1));:使用 strrchr 函数找到文件名中的最后一个 .,然后使用 substr 截取文件扩展名,并用 strtolower 将其转换为小写。

if (!in_array(phpcms上传漏洞_php_04dir . $f); }:如果文件的扩展名不是jpg、gif或png,则使用 unlink 函数删除该文件。

<?php
header("Content-Type:text/html; charset=utf-8");
require_once('pclzip.lib.php');
 
$file = $_FILES['file'];
if ($file['size'] == 0) {
    exit("请勿上传空文件");
}
$name = $file['name'];
$dir = 'uploads/';
$ext = strtolower(substr(strrchr($name, '.'), 1));
 
 
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 . '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) {
            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文件单独放在一个文件夹中,然后将这个文件夹和图片一起压缩成zip,然后上传

phpcms上传漏洞_上传_05

phpcms上传漏洞_上传_06

这个bbb文件夹里面的111.php就没有被删除了,然后就可以连接蚁剑了

phpcms上传漏洞_上传_07

修复后版本

<?php
header("Content-Type:text/html; charset=utf-8");
require_once('pclzip.lib.php');
 
$file = $_FILES['file'];
if ($file['size'] == 0) {
    exit("请勿上传空文件");
}
$name = $file['name'];
$dir = 'uploads/';
$ext = strtolower(substr(strrchr($name, '.'), 1));
 
 
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);
                }
            }
        }
    }
}
 
 
if (!is_dir($dir)) {
    mkdir($dir);
}
 
$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 zip');
        // }
        $archive = new PclZip($file['tmp_name']);
        if ($archive->extract(PCLZIP_OPT_PATH, $temp_dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
            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文件!');
}

phpcms上传漏洞_上传_08

上传zip后我们发现,bbb中的111.php就被删除了

但是代码逻辑是先解压,后删除,我们就用时间竞争来绕过

因为我发现,同时使用burp上传和访问,经过多次尝试没有成功,我换了个操作,使用手动上传zip,burp访问php,多次手动上传之后,成功了!

phpcms上传漏洞_上传_09

在我们的uploads下面生成了一句话文件,直接连蚁剑

phpcms上传漏洞_php_10

再次修复版

<?php
header("Content-Type:text/html; charset=utf-8");
require_once('pclzip.lib.php');
 
$file = $_FILES['file'];
if ($file['size'] == 0) {
    exit("请勿上传空文件");
}
$name = $file['name'];
$dir = 'uploads/';
$ext = strtolower(substr(strrchr($name, '.'), 1));
 
 
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);
                }
            }
        }
    }
}
 
 
if (!is_dir($dir)) {
    mkdir($dir);
}
 
$temp_dir = $dir . 'member/1/';
if (!is_dir($temp_dir)) {
    mkdir($temp_dir);
}
 
$temp_dir = $dir.md5(time(). rand(1000,9999)).'/'; //随机数生成文件,无法确定文件路径及文件名
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 zip');
        }
        // $archive = new PclZip($file['tmp_name']);
        // if ($archive->extract(PCLZIP_OPT_PATH, $temp_dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
        //     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文件!');
}

他还是先解压报错,后删除文件,我们制作一个能报错的zip,内含一句话,然后直接上传错误的zip。

phpcms上传漏洞_php_11

在aaaaaaa中存在以上文件,生成aaaaaaa.zip压缩包,然后对压缩包进行制作

phpcms上传漏洞_php_12

保证最后解压要报错

将制作好的zip上传,同时使用burp连续访问

手动多次上传zip保证刚解压出来的111.php被访问到

phpcms上传漏洞_php_13

发现上传成功了,生成了随机名文件

phpcms上传漏洞_上传_14

查看一句话发现已经生成

phpcms上传漏洞_上传_15

连接蚁剑

phpcms上传漏洞_上传_16

造成以上漏洞的原因就是,解压完成的文件没有进行递归删除,导致包含其中的一句话绕过了。