内容简介:在这看到的, 看到这个的时候有点疑惑这玩意不应该就是个前端的上传么, 怎么还会造成任意文件上传漏洞。 就去下了个源码看了下。
在这看到的, 看到这个的时候有点疑惑这玩意不应该就是个前端的上传么, 怎么还会造成任意文件上传漏洞。 就去下了个源码看了下。
漏洞复现
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来正则验证上传的文件名, 所以修复后就只能上传图片文件了。
修复后再上传脚本文件就失败了。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
细节决定交互设计的成败
张亮 / 2009-3 / 49.00元
《细节决定交互设计的成败》是一本非常实用的有关软件界面的交互设计和可用性设计方面知识的书籍,通过采用一问一答的形式,你将会有针对性地学习到一些能够很快应用在自己软件开发工作中的细节知识和诀窍。例如,如何减轻用户的等待感,如何预防和减少用户的使用错误等。另外,你会发现阅读《细节决定交互设计的成败》时会非常轻松和愉悦;这是由于《细节决定交互设计的成败》写作上的两个特点:第一,采用较多日常生活中的例子来......一起来看看 《细节决定交互设计的成败》 这本书的介绍吧!