复现文件上传四次绕过

代码

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>文件上传章节练习题</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <style type="text/css">
        .login-box{
            margin-top: 100px;
            height: 500px;
            border: 1px solid #000;
        }
        body{
            background: white;
        }
        .btn1{
            width: 200px;
        }
        .d1{
            display: block;
            height: 400px;
        }
    </style>
</head>
<body>
    <form method="post" action="upload.php" enctype="multipart/form-data">
        <input type="file" name="file" value=""/>
        <input type="submit" name="submit" value="upload"/>
    </form>
</body>
</html>
<?php
header("Content-Type:text/html; charset=utf-8");
require_once('pclzip.lib.php');

$name = $file['name'];
$dir = 'upload/';
$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') {                   
        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文件!');
}

解析代码
先上传文件,在建一个上传文件的目录upload,在解压文件,解压完文件后在对解压完的文件进行判断,判断文件里面是不是有除了zip、jpg、gif、png之外的文件,有的话就直接删除。只能上传压缩包,因为若上传png文件的话也会被压缩,然后在进行解压缩。

代码问题
1、存在时间竞争型漏洞:若上传的是php文件,是先被解压,再被删除。
2、没有进行递归删除。

第一次绕过

新建一个压缩包,压缩包里面有一张png图片和一个php文件。
在这里插入图片描述

web.php文件内容

aaaaaaa

开始上传
在这里插入图片描述
查看上传结果
在这里插入图片描述
发现php文件已经被删除,只有png文件保留了下来。

解决方案:
代码未进行递归删除,导致文件夹内的webshell得以保留。
只需要将上传的压缩文件中,递归创建一个文件夹,即可上传成功。

建一个新的文件aaa,在文件aaa里面放一张png图片和一个php文件,再对文件aaa进行压缩。
再次上传,上传压缩包aaa。

上传结果:
在这里插入图片描述
这就是phpcms最早的头像上传漏洞。这个漏洞影响的不只是phpcms,也包括抄袭其代码的finecms。

在phpcms出问题以后,finecms偷偷将漏洞修复了,当然修复方法就是直接拷贝了phpcms的补丁。

发现php文件并没有被删除,因为程序没有进行递归删除,导致该问题的存在。

修复方法:
增加递归删除,但是又造成了新的问题。

第二次绕过

针对第一次的绕过,进行了递归删除的补丁修复
得到以下PHP代码

<?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('.', '..'))){
            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') {

        $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文件,就能够成功在它不会进行检查和删除的上级目录中生成一个新的php文件。

在文件上传解压到被删除这个时间差里访问,就能在网站根目录下生成新的php文件,那么新生成的php文件是不会被删除的。

所以修改我们之前所搭建的环境,将php代码中的第一个function递归删除的函数替换成以下代码。

重写一个php文件
web.php

<?php fputs(fopen('../../../payload.php','w'),'<?php phpinfo(); ?>');?>

../可以让文件向上跳一层,../../可以让文件向上跳两层,依次类推。

使用burp进行抓包,然后将这个包投放到intruder模块中,反复进行发送,访问的数据包同样用intruder进行。

再次尝试上传一下
在这里插入图片描述因为这里使用了递归删除,所以上传文件夹不能绕过了。

上传后发现文件夹中只剩图片了,php代码被删除了。但是这里上传文件夹后代码名字未修改,并且这里是先上传再删除,所以存在一个时间竞争漏洞。

查看我们之前上传的路径,为

http://127.0.0.1/xss_location/upload.php/member/1/1/web.php

因此需要跳跃三次才能到达跟目录,则代码为

<?php fputs(fopen('../../../payload.php','w'),'<?php phpinfo(); ?>');

用时间竞争型漏洞直接在根目录下写入我们的一句话木马,这样他递归删除只是删除当前文件夹的,不会检测根目录。尝试一下

打开burpsuit,上传web.zip并重发到Intruder模块中,准备模拟多次上传。

用时间竞争型漏洞直接在根目录下写入我们的一句话木马,这样他递归删除只是删除当前文件夹的,不会检测根目录。尝试一下

使用burp进行抓包,然后将这个包投放到intruder模块中,反复进行发送,访问的数据包同样用intruder进行。

在这里插入图片描述

设置访问次数为5000次
在这里插入图片描述

在抓取的要访问的目录代码,上传到Intruder模块
在这里插入图片描述

payload设置成6000次
在这里插入图片描述

两个同时开始start attack。

在这里插入图片描述
可以看到,访问成功了,时间竞争漏洞利用成功

解决方案:
由于先解压,再删除,导致暴力getshell
时间竞争拿下webshell

第三次绕过

针对第二次的绕过,进行了递归删除的补丁修复
得到以下PHP代码

<?php
header("Content-Type:text/html; charset=utf-8");
require_once('pclzip.lib.php');

$name = $file['name'];
$dir = 'upload/';
$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.md5(time(). rand(1000,9999)).'/';
if (!is_dir($temp_dir)) {
    mkdir($temp_dir);
}

if ($ext == 'zip') {
        $zip = new ZipArchive;
        if(!$zip->open($file['tmp_name'])) {
            echo "fail";
            return false;
        }
        if(!$zip->extractTo($temp_dir)) {

            exit("fail to extract");
        }

        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文件解压出来,第二个正常文件不被解压出来就可以绕过了。

如何构造一个出错的压缩包

如果压缩包是.7z格式,而7zip的容忍度很低,只需要修改第二个文件的crc,就可以让它解压出错了。
在这里插入图片描述

使用010editor打开web.7z文件。

为了让ZipArchive出错,比如,Windows下不允许文件名中包含冒号(:)等特殊符号,
可以在010editor中将1.txt的deFileName属性的值改成“1.tx:”。
在这里插入图片描述
此时解压就会出错,但1.php被保留了下来。
尝试上传,提示fail to extract

在这里插入图片描述
查看服务端,发现php代码成功上传

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值