2024全网最全面及最新且最为详细的网络安全技巧 十之CMS文件上传漏洞典例分析POC;EXP以及 如何防御和修复[含PHP;Pyhton,C源码和CTF精题及WP详解]

  • 目录

     

    十、文件上传漏洞

    10.1 当php邂逅windows通用上传缺陷

    代码poc实现

    细说故事

    冒号+特性成功利用

    特性二

    漏洞证明

    10.2 回忆phpcms头像上传漏洞以及后续影响

    10.2.1 最初的phpcms头像上传getshell漏洞

    10.2.2破程序员的小聪明,phpcms补丁的继续绕过

     10.2.3 加了行代码就真的安全了吗?终极手段上!

    10.2.4 未完待续以及究竟怎么修复这个安全问题

    10.3 怎么制造一个只能解压一半的压缩包(即解压到一半出错的)


     

  • 十、文件上传漏洞

  • 10.1 当php邂逅windows通用上传缺陷

  • U-Mail邮件系统文件上传的地方代码是这样的
  • <?php
    if(ACTION == "attach-upload") {  // 如果 ACTION 变量的值为 "attach-upload"
        if($_FILES) {  // 检查是否有文件上传
            // 获取上传文件的相关信息
            $file_name = $_FILES['Filedata']['name'];  // 文件名
            $file_type = $_FILES['Filedata']['type'];  // 文件类型
            $file_size = $_FILES['Filedata']['size'];  // 文件大小
            $file_source = $_FILES['Filedata']['tmp_name'];  // 临时文件路径
            $file_suffix = getfilenamesuffix($file_name);  // 获取文件后缀名
            
            // 不允许上传的文件扩展名
            $not_allow_ext = array("php", "phps", "php3", "exe", "bat"); 
            
            // 如果文件后缀名在不允许的扩展名数组中
            if (in_array($file_suffix, $not_allow_ext)) {
                // 返回 JSON 格式的错误信息
                dump_json(array("status" => 0, "message" => el("不支持该扩展名文件上传", "")));
            }
    
            // 获取用户缓存目录路径
            $path_target = getusercachepath();
    
            do {
                // 生成随机文件名
                $file_id = makerandomname();
                // 构造目标文件路径
                $file_target = $path_target . $file_id . "." . $file_suffix;
            } while (file_exists($file_target));  // 如果目标文件路径已存在,则继续生成新的文件名
    
            // 移动上传的文件到目标路径
            if (move_uploaded_file($file_source, $file_target)) {
                // 如果移动文件失败,返回 JSON 格式的错误信息
                dump_json(array("status" => 0, "message" => el("写入文件出错,请与管理员联系!", "")));
            }
    
            // 将上传文件的相关信息存储到会话的 'attach_cache' 数组中
            $_SESSION[SESSION_ID]['attach_cache'][] = array(
                "id" => $file_id,
                "name" => $file_name,
                "type" => "1",
                "path" => $file_target,
                "size" => $file_size
            );
    
            // 返回 JSON 格式的成功信息
            dump_json(array("status" => "1", "filename" => $file_name, "filesize" => $file_size, "file_id" => $file_id));
        } else {
            // 如果没有文件上传,返回 JSON 格式的错误信息
            dump_json(array("status" => "0", "message" => el("无法找到需要上传的文件!", "")));
        }
    }
    ?>
    
  • 我们注意到如下的代码
  • $not_allow_ext = array( "php", "phps", "php3", "exe", "bat" );
    if (in_array($file_suffix, $not_allow_ext )){
        dump_json( array( "status" => 0, "message" => el( "不支持该扩展名文件上传", "" ) ) );
    }
  • 非常明显,采用的是黑名单验证,虽然我们可以采用类似这样的文件后缀绕过程序的检测,如:bypass.phpX(这里的X代表空格%20或其他特殊字符{%80-%99}),但这并是今天我想要讲的内容。
  • 今天,通过这个例子给大家讲解一种新型的文件上传方式,且听我娓娓道来..
  • 代码poc实现

  • 为了在本地测试方便,我们对上述代码进行简化,如下
  • <?php
    // U-Mail demo ...
    if(isset($_POST['submit'])) {  // 检查表单是否已提交
        $filename = $_POST['filename'];  // 获取表单中 'filename' 的值
        $filename = preg_replace("/[^\w]/i", "", $filename);  // 去除 'filename' 中的非字母、数字字符
    
        $upfile = $_FILES['file']['name'];  // 获取上传文件的原始名称
        $upfile = str_replace(';', "", $upfile);  // 去除文件名中的分号
        $upfile = preg_replace("/[^(\w|\:|\$|\.|\<|\>)]/i", "", $upfile);  // 仅允许字母、数字、冒号、美元符号、点号、尖括号
    
        $tempfile = $_FILES['file']['tmp_name'];  // 获取上传文件的临时路径
        $ext = trim(get_extension($upfile));  // 获取文件扩展名
        if(in_array($ext, array('php', 'php3', 'php5'))) {  // 检查文件扩展名是否在禁止列表中
            die('Warning ! File type error..');  // 如果是禁止的扩展名,输出警告信息并终止执行
        }
        // 如果文件扩展名属于以下类型,则将扩展名设置为 'file'
        if($ext == 'asp' || $ext == 'asa' || $ext == 'cer' || $ext == 'cdx' || $ext == 'aspx' || $ext == 'htaccess') {
            $ext = 'file';
        }
        
        // 构造文件保存路径
        $savefile = 'upload/' . $filename . "." . $ext;
    
        // 移动上传的文件到目标路径
        if(move_uploaded_file($tempfile, $savefile)) {
            die('Success upload..path :' . $savefile);  // 如果成功,输出成功信息和文件保存路径
        } else {
            die('Upload failed..');  // 如果失败,输出失败信息
        }
    }
    
    // 获取文件扩展名的函数
    function get_extension($file) {
        return strtolower(substr($file, strrpos($file, '.') + 1));  // 提取文件扩展名并转为小写
    }
    ?>
    
    <html>
     <body>
      <form method="post" action="upfile.php" enctype="multipart/form-data">
       <input type="file" name="file" value=""/>  <!-- 文件上传控件 -->
       <input type="hidden" name="filename" value="file"/>  <!-- 隐藏字段,指定默认文件名 -->
       <input type="submit" name="submit" value="upload"/>  <!-- 提交按钮 -->
      </form>
     </body>
    </html>
    
  • 对于上述代码,虽然是通过黑名单进行文件名检测,但通过目前已知的上传方法,是没有办法成功上传php文件的(不考虑程序的Bug),因此可以说这段文件上传的代码是"安全"的,
  • 细说故事

  • 关于利用系统特性进行文件上传的知识点
  • img

  • 这几行英文的意思大致是,在php+window+iis环境下:
  • 双引号("“") <==> 点号(".")';
    
    大于符号(">") <==> 问号("?")';
    
    小于符号("<") <==> 星号("*")';
  • 有这么好玩的东西,那不就可以做太多的事了?但事实并不是这样,通过一系列的测试发现,该特性只能用于文件上传时覆盖已知的文件,于是这个特性便略显鸡肋..
  • 原因有二:
  • 1)上传文件的目录一般我们都不可控;
  • 2)同时,一般文件上传的目录不可能存在我们想要的任何php文件,因此没办法覆盖;
  • 后来,经过反反复复的思考,终于找到了可以完美利用的办法..
  • 思路如下:
  • 首先我们先利用特殊办法生成一个php文件,然后再利用这个特性将文件覆盖..
  • 可问题又来了,怎样生成php文件呢?如果可以直接生成php文件的话,干嘛还要利用那什么特性?
  • 别急,办法总是有的..
  • 我们都知道在文件上传时,我们往往会考虑到文件名截断,如%00 等..
  • 对!有的人可能还会用冒号(":")去截断,如:bypass.php:jpg
  • 但是冒号截断产生的文件是空白的,里面并不会有任何的内容,呵呵 说到这里 明白了没有? 虽然生成的php文件里面没有内容,但是php文件总生成了吧,所以 我们可以结合上面所说的特性完美成功利用..
  • 冒号+特性成功利用

  • 按照#3提供的思路,实现..
  • 本地测试地址:http://localhost:8090/upfile.php 环境:Windows+IIS7.5
  • 1)首先利用冒号生成我们将要覆盖的php文件,这里为:bypass.php,
  • 但明显可以看出,php文件为空,我们需要覆盖。
  • 2)利用上面的系统特性覆盖该文件
  • 从上面已经知道"<" 就等于 "",而""代码任意字符,于是乎.. 我们可以这样修改上传的文件名,如下:
  • ------WebKitFormBoundaryaaRARrn2LBvpvcwK
    
    Content-Disposition: form-data; name="file"; filename="bypass.<<<"
    
    Content-Type: image/jpeg
    
    //注意!文件名为:bypass.<<<
  • 点击go..,即可成功覆盖bypass.php文件,如图
  • 对比上面的两个图,bypass.php被我们成功的写入了内容..
  • 特性二

  • 首先来看看微软MSDN上面的一段话,如图
  • img

  • 注意红色圈起来的英文
    • The default data stream has no name. That is, the fully qualified name for the default stream for a file called "sample.txt" is "sample.txt::$DATA" since "sample.txt" is the name of the file and "$DATA" is the stream type

  • 看不去不错哟,试试吧..
  • 同样,我们可以这样修改上传的文件名,如下:
  • ------WebKitFormBoundaryaaRARrn2LBvpvcwK
    
    Content-Disposition: form-data; name="file"; filename='DataStreamTest.php::$DATA'
    
    Content-Type: image/jpeg
    
    //注意!文件名为:DataStreamTest.php::$DATA
  • 点击GO,奇迹出现了..
  • 访问之...
  • 漏洞证明

  • U-Mail,具体利用方法,同上述的方法一样,为了简单快捷的话,可直接抓包修改文件名为:
  • shell.php::$DATA 即可成功上传,这里不再演示

  • 10.2 回忆phpcms头像上传漏洞以及后续影响

  • 10.2.1 最初的phpcms头像上传getshell漏洞

  • 不知道大家还记得phpcms曾经火极一时的头像上传漏洞不,因为这个漏洞,互联网上大量站点被黑,影响极为恶劣。简单来说phpcms对头像上传是这么处理:上传上去的zip文件,它先解压好,然后删除非图片文件。
  • 在文件上传解压到被删除这个时间差里访问,就能在网站根目录下生成新的php文件,那么新生成的php文件是不会被删除的。
  • 这就是一个竞争性上传漏洞,需要我们抓住这个时间差,在上传的php文件还没被删除前访问到它,就能够暴力getshell了。
  • 10.2.2破程序员的小聪明,phpcms补丁的继续绕过

  • 于是finecms意识到自己的问题,偷偷修补了这个安全问题。当时的他们是这样修复的:
  • <?php
    // 创建图片存储的临时文件夹
    $temp = FCPATH.'cache/attach/'.md5(uniqid().rand(0, 9999)).'/';  // 生成一个唯一的临时目录路径
    
    if (!file_exists($temp)) {  // 检查临时目录是否已存在
        mkdir($temp, 0777);  // 如果不存在,则创建该目录,权限为0777
    }
    
    $filename = $temp.'avatar.zip'; // 定义存储上传的 zip 文件的路径
    
    file_put_contents($filename, $GLOBALS['HTTP_RAW_POST_DATA']);  // 将上传的原始数据写入到指定的 zip 文件中
    
    // 解压缩文件
    $this->load->library('Pclzip');  // 加载 Pclzip 库,用于处理 zip 文件
    
    $this->pclzip->PclFile($filename);  // 初始化 Pclzip 对象,指定要处理的 zip 文件
    
    if ($this->pclzip->extract(PCLZIP_OPT_PATH, $temp, PCLZIP_OPT_REPLACE_NEWER) == 0) {  // 解压 zip 文件到临时目录中,覆盖更新的文件
        exit($this->pclzip->zip(true));  // 如果解压失败,输出错误信息并退出
    }
    
    @unlink($filename);  // 删除原始的 zip 文件
    
  • 说起来这也是phpcms曾经的修复方法,就是将压缩包放在一个随机命名的文件夹中再解压缩,这样你猜不到访问地址也就没法去暴力getshell了。
  • 但是实质上这也只是解决了一个芝麻小的问题,而真正出现漏洞的点他们并未进行修复。
  • 我们看到这段代码:
  • <?php
    if ($this->pclzip->extract(PCLZIP_OPT_PATH, $dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
    
        exit($this->pclzip->zip(true));
    
    }
  • 当解压发生失败时,就退出解压缩过程。
  • 这也是一个很平常的思路,失败了肯定要报错并退出,因为后面的代码没法运行了。但是,程序员不会想到,有些压缩包能在解压到一半的时候出错。
  • 什么意思,也就说我可以构造一个“出错”的压缩包,它可以解压出部分文件,但绝对会在解压未完成时出错。这是造成了一个状况:我上传的压缩包被解压了一半,webshell被解压出来了,但因为解压失败这里exit($this->pclzip->zip(true));退出了程序执行,后面一切的删除操作都没有了作用。
  • 首先构造一个解压会出错的压缩包,大家看下图,1-7.php都已经被成功解压了,但6.php解压出错,WinRAR弹出了出错信息:
  •  发包的时候,将这个压缩包带上,会发现返回了500,出错信息:
  • 但你的webshell已经解压完毕了。这个漏洞造成了finecms官网的沦陷
  •  10.2.3 加了行代码就真的安全了吗?终极手段上!

  • 过了半个月我看到了他们最新的代码:
  • <?php
    if (!isset($GLOBALS['HTTP_RAW_POST_DATA'])) {
        exit('环境不支持');
    }
    
    // 创建用户上传文件夹
    $dir = FCPATH . 'member/uploadfile/member/' . $this->uid . '/';
    if (!file_exists($dir)) {
        mkdir($dir, 0777, true);
    }
    
    // 创建临时文件夹
    $temp = FCPATH . 'cache/attach/' . md5(uniqid() . rand(0, 9999)) . '/';
    if (!file_exists($temp)) {
        mkdir($temp, 0777, true);
    }
    
    $filename = $temp . 'avatar.zip'; // 临时存储 zip 文件
    file_put_contents($filename, $GLOBALS['HTTP_RAW_POST_DATA']); // 保存上传的 zip 文件
    
    // 解压缩文件
    $this->load->library('Pclzip');
    $this->pclzip->PclFile($filename);
    
    if ($this->pclzip->extract(PCLZIP_OPT_PATH, $temp, PCLZIP_OPT_REPLACE_NEWER) == 0) {
        @dr_dir_delete($temp); // 删除临时文件夹
        exit($this->pclzip->zip(true)); // 输出错误信息并退出
    }
    
    @unlink($filename); // 删除原始 zip 文件
    ?>
    
  • 加了行代码:@dr_dir_delete($temp);解压出错后,在exit前将已经解压出来的内容删除了。确实避免了我在0×03中说到的安全问题。
  • 但finecms的开发者依旧是没有能看到真正造成这个漏洞的原因。
  • 原因就出在解压压缩包的这个操作上。这个类你就把别人的代码拿来一抄就觉得完毕了,你知道这个类真正的用法么?大家猜猜我这次怎么绕过上诉补丁的。
  • 压缩包中通常是不含有诸如“../”、“..”这种文件名的,但通常不含有不代表不能含有。我如果把压缩包中某文件名改成../../../../../index.php,是不是就能直接把你首页变成我的webshell呀?
  • 这就是因为抄袭者并没有真正领悟zip这个类的使用方法,导致了这个安全问题。我在本地用notepad++即可修改、构造一个压缩包。
  • 先把自己的shell改名字成aaaaaaaaaaaaaaaaaaaa.php
  • 之所以起这个名字,就是预留一些空间,方便我之后将文件名改成../../../aaaaaaaaaaa.php而不用怕字符串长度不对。
  • 把文件直接打包成zip,用notepad++打开:
  • 将我画框的俩文件名的前9个字符改成../../../ 
  • 然后就大功告成。
  • 上传头像时抓包将刚才构造的压缩包贴进去:
  •  然后,网站根目录下就会有你的shell了:aaaaaaaaaaa.php
  •  通过这个方法,就能无限制地getshell
  • 10.2.4 未完待续以及究竟怎么修复这个安全问题

  • 究竟是什么原因造成了这个漏洞,究其根本还是以为你将用户不安全的POST数据写入了文件,并解压到web目录下了。
  • 世界上有无数种方法可以避免这个问题,web目录下随便写文件真的好吗?为何你不把压缩包放进tmp目录里,如果上传、解压缩的操作都能在tmp目录里完成,再把我们需要的头像文件拷贝到web目录中,还会有这么麻烦的安全问题吗?
  • phpcms已经彻底抛弃了解压缩的方式直接在前端将图片处理完成后进行上传。但愚昧的finecms开发者还是抱着自己无知的思路,去用近乎“黑名单”的方式去解决这个问题,那就是黑客怎么日,他就怎么补,永远不知道下一步黑客会从哪里进入。这样的人永远只能落后挨打,这样的cms迟早会成为一个打满补丁的破布,每一个补丁都将付出无数速度与效率的代价。

10.3 怎么制造一个只能解压一半的压缩包(即解压到一半出错的)

这个问题其实需要看具体情况,看解压的那个程序的容忍程度,我这里就以两个解压的程序作为例子:

  1. Windows下的7zip

  2. PHP自带的ZipArchive库

  3. 先说7zip。7zip的容忍度很低,只要压缩包中某一个文件的CRC校验码出错,就会报错退出。 如何修改压缩包里文件的CRC校验码呢?可以使用010editor。

  4. 我们先准备两个文件,一个PHP文件1.php,一个文本文件2.txt,其中1.php是webshell。然后将这两个文件压缩成shell.zip。 然后我们用010editor打开shell.zip,可以看到右下角有这个文件的格式信息,它被分成5部分,如图1。我们打开第4部分,其中有个deCrc,我们随便把值改成其他的值,然后保存。

  5. 图2。 此时用7zip解压就会出错,解压出的1.php是完好的,2.txt是一个空文件。

  6. 如图3。 我们再用PHP自带的ZipArchive库(代码如图4)测试这个zip,发现解压并没有出错,这也说明ZipArchive的容忍度比较高。 那么我们又如何让ZipArchive出错呢?最简单的方法,我们可以在文件名上下功夫。 比如,Windows下不允许文件名中包含冒号(:),我们就可以在010editor中将2.txt的deFileName属性的值改成“2.tx:”,如图5。此时解压就会出错,但1.php被保留了下来。

  7. 如图6。 在Linux下也有类似的方法,我们可以将文件名改成5个斜杠(/)

  8. 如图7,此时Linux下解压也会出错,但1.php被保留了下来,如图8。

\tan \sinh \csc \sec \cot \cot \cosh 

......

  • 25
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值