jQuery-File-Upload-arbitrarily-file-upload-Vuln

栏目: jQuery · 发布时间: 6年前

内容简介:在这看到的, 看到这个的时候有点疑惑这玩意不应该就是个前端的上传么, 怎么还会造成任意文件上传漏洞。 就去下了个源码看了下。

jQuery-File-Upload-arbitrarily-file-upload-Vuln

在这看到的, 看到这个的时候有点疑惑这玩意不应该就是个前端的上传么, 怎么还会造成任意文件上传漏洞。 就去下了个源码看了下。

漏洞复现

https://github.com/blueimp/jQuery-File-Upload/releases/tag/v9.22.0

下载了代码后, 发现原来也带了后端处理上传的脚本。在server目录下。

在server/index.php中

require('UploadHandler.php');
$upload_handler = new UploadHandler();

实例化了UploadHandler类。

在UploadHandler的构造方法中

构造方法中, 定义了一些配置信息。

然后

public function __construct($options = null, $initialize = true, $error_messages = null) {
    $this->response = array();
    $this->options = array(
        'script_url' => $this->get_full_url().'/'.$this->basename($this->get_server_var('SCRIPT_NAME')),
        'upload_dir' => dirname($this->get_server_var('SCRIPT_FILENAME')).'/files/',
        'upload_url' => $this->get_full_url().'/files/',
        'input_stream' => 'php://input',
        'user_dirs' => false,
        'mkdir_mode' => 0755,
        'param_name' => 'files',
        .................
        .................
    if ($initialize) {
        $this->initialize();
    }

然后就调用了类中的initialize方法。

protected function initialize() {
        switch ($this->get_server_var('REQUEST_METHOD')) {
            case 'OPTIONS':
            case 'HEAD':
                $this->head();
                break;
            case 'GET':
                $this->get($this->options['print_response']);
                break;
            case 'PATCH':
            case 'PUT':
            case 'POST':
                $this->post($this->options['print_response']);
                break;
            case 'DELETE':
                $this->delete($this->options['print_response']);
                break;
            default:
                $this->header('HTTP/1.1 405 Method Not Allowed');
        }

根据对应的请求方式调用不同的方法。

大概get对应的是下载, post对应的是上传, delete对应的是删除操作。

但是在get和delete方法中, 由于都经过了basename方法的处理

所以只能删除files目录下的文件。

在post方法中

$files[] = $this->handle_file_upload(
    isset($upload['tmp_name']) ? $upload['tmp_name'] : null,
    $file_name ? $file_name : (isset($upload['name']) ?
            $upload['name'] : null),
    $size ? $size : (isset($upload['size']) ?
            $upload['size'] : $this->get_server_var('CONTENT_LENGTH')),
    isset($upload['type']) ?
            $upload['type'] : $this->get_server_var('CONTENT_TYPE'),
    isset($upload['error']) ? $upload['error'] : null,
    null,
    $content_range
);

获取到FILES变量中的数据后, 直接就调用了handle_file_upload方法

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);
            }

在handle_file_upload方法中, 只要通过了validate方法, 那么就直接执行move_upload_file了。

protected function validate($uploaded_file, $file, $error, $index) {
    if ($error) {
        $file->error = $this->get_error_message($error);
        return false;
    }
    $content_length = $this->fix_integer_overflow(
        (int)$this->get_server_var('CONTENT_LENGTH')
    );
    $post_max_size = $this->get_config_bytes(ini_get('post_max_size'));
    if ($post_max_size && ($content_length > $post_max_size)) {
        $file->error = $this->get_error_message('post_max_size');
        return false;
    }
    if (!preg_match($this->options['accept_file_types'], $file->name)) {
        $file->error = $this->get_error_message('accept_file_types');
        return false;
    }

这里通过或者配置变量数组中的accept_file_types来正则验证上传的文件名,

‘accept_file_types’ => ‘/.+$/i’,

配置文件中的accept_file_types为.+,

.代表着匹配任意字符 +1到多个字符, 所以是根本没有验证后缀的。

造成了任意文件上传。

$ curl -F "files=@yu.php" http://localhost/jQuery-File-Upload-9.22.0/server/php/index.php
{"files":[{"name":"yu.php","size":20,"type":"application\/octet-stream","url":"http:\/\/localhost\/jQuery-File-Upload-9.22.0\/server\/php\/files\/yu.php","deleteUrl":"http:\/\/localhost\/jQuery-File-Upload-9.22.0\/server\/php\/index.php?file=yu.php","deleteType":"DELETE"}]}

就能直接上传成功了,上传到了files目录中。 但是访问后发现脚本没有执行。

在files目录下 有着一个.htaccess

# To enable the Headers module, execute the following command and reload Apache:
# sudo a2enmod headers

# The following directives prevent the execution of script files
# in the context of the website.
# They also force the content-type application/octet-stream and
# force browsers to display a download dialog for non-image files.
SetHandler default-handler
ForceType application/octet-stream
Header set Content-Disposition attachment

# The following unsets the forced type and Content-Disposition headers
# for known image files:
<FilesMatch "(?i)\.(gif|jpe?g|png)$">
	ForceType none
	Header unset Content-Disposition
</FilesMatch>

# The following directive prevents browsers from MIME-sniffing the content-type.
# This is an important complement to the ForceType directive above:
Header set X-Content-Type-Options nosniff

# Uncomment the following lines to prevent unauthorized download of files:
#AuthName "Authorization required"
#AuthType Basic
#require valid-user
The directives ForceType and SetHandler are used to associated all the files in a given location (e.g., a particular directory) onto a particular MIME type or handler.

.htaccess配置了 在files目录下 强制由default-handler来处理所有文件m, 并且强制mime type为application/octet-stream, 使files目录下的脚本不会被执行。开发者也是因为配置了.htaccess的情况下, 以为是绝对的安全了, 所以才没有进行验证后缀。

这里虽然配置了.htaccess 但仍然有两个安全隐患

1: .htaccess只对apache有效, 而在纯nginx(非反向代理)中是无效的, 在jquery-upload的readme中 好像也并没有看到有说明使用nginx时的安全隐患。

jQuery-File-Upload-arbitrarily-file-upload-Vuln

2: 在apache 2.3.9以后, allowoverride默认为none

https://httpd.apache.org/docs/current/mod/core.html#allowoverride

AllowOverride Directive Description: Types of directives that are allowed in .htaccess files Syntax: AllowOverride All|None|directive-type [directive-type] … Default: AllowOverride None (2.3.9 and later), AllowOverride All (2.3.8 and earlier) Context: directory Status: Core Module: core

可以看到很清楚的说明了, 在apache 2.3.9及以后版本 allowoverride默认为none,

在2.3.8及之前版本allowoverride默认为all。

allowoverride指定了在.htaccess配置文件中, 可以覆盖掉主配置文件的指令。 当allowoverride为none时, .htaccess文件就失去了它的作用, 所以jquery-upload中对files目录所设置的拒绝脚本文件执行也就失效了。 导致了getshell。

修复

Jquery-file-upload在9.22.1版本中修复了该漏洞。

jQuery-File-Upload-arbitrarily-file-upload-Vuln

在实例化UploadHandler类的时候, 传递了一个数组进去。数组里面带了一个accept_file_types来验证后缀。

在UploadHandler类的构造方法中,

if ($options) {
    $this->options = $options + $this->options;
}

把传递进来的数组和自身的配置变量数组用加号进行合并,

使用加号合并时 出现key冲突时 前面的数组对应的value会覆盖掉后面的。

所以这时

$this->options['accept_file_types']'/\.(gif|jpe?g|png)$/i'

在上传之前的validate方法中有通过获取accept_file_types来正则验证上传的文件名, 所以修复后就只能上传图片文件了。

jQuery-File-Upload-arbitrarily-file-upload-Vuln

修复后再上传脚本文件就失败了。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

ASP.NET 4高级程序设计(第4版)

ASP.NET 4高级程序设计(第4版)

Matthew MacDonald / 博思工作室 / 人民邮电出版社 / 2011-6 / 148.00元

《ASP.NET 4高级程序设计(第4版)》,本书是ASP.NET领域的鸿篇巨制,全面讲解了ASP.NET4的各种特性及其背后的工作原理,并给出了许多针对如何构建复杂、可扩展的网站从实践中得出的建议。一起来看看 《ASP.NET 4高级程序设计(第4版)》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试