一、文件上传漏洞原理

文件上传漏洞是指 Web 应用程序在对用户上传的文件进行校验时存在缺陷,导致攻击者可以上传恶意文件(如 WebShell、恶意脚本、可执行文件等)到服务器,从而获取服务器控制权限或造成其他危害。

1.1 漏洞成因

  • 未对上传文件类型做充分校验

  • 仅在前端做校验,后端未做二次验证

  • 校验逻辑存在缺陷(如仅检查 Content-Type,未检查文件内容)

  • 文件存储路径可预测或未做安全处理

  • 文件重命名逻辑被绕过

1.2 漏洞危害

  1. 获取 WebShell,完全控制服务器

  2. 植入恶意程序进行横向移动

  3. 上传恶意文件作为钓鱼或恶意软件分发点

  4. 上传 HTML 文件实现 XSS 攻击

  5. 覆盖系统文件导致服务器异常

二、前端绕过

许多 Web 应用在前端使用 JavaScript 对文件扩展名或 MIME 类型进行检查,但后端未做相同的验证。前端绕过是最基础也是最常见的绕过方式。

// 前端常见校验代码
function checkFile() {
    var file = document.getElementById('upload').value;
    var ext = file.substring(file.lastIndexOf('.') + 1).toLowerCase();
    if (ext !== 'jpg' && ext !== 'png' && ext !== 'gif') {
        alert('仅允许上传图片文件!');
        return false;
    }
    return true;
}

绕过方法

  1. 直接修改请求:使用 Burp Suite 截断上传请求,修改文件扩展名

  2. 禁用 JavaScript:在浏览器中禁用 JS,直接提交表单

  3. 修改响应:删除或修改前端验证函数

  4. 使用浏览器开发者工具:直接删除 onsubmit 事件或修改验证逻辑

三、MIME 类型绕过

部分后端代码仅检查 HTTP 请求中的 Content-Type 字段来判断文件类型。

// 后端仅检查 MIME 类型的示例
$mime = $_FILES['file']['type'];
if ($mime !== 'image/jpeg' && $mime !== 'image/png') {
    die('仅允许上传图片!');
}

绕过方法

使用 Burp Suite 等工具截断上传请求,将 Content-Type 修改为允许的类型:

原始请求:Content-Type: application/x-php
修改后:Content-Type: image/jpeg

四、文件头伪造(Magic Number 绕过)

当后端使用 getimagesize()finfo_file()exif_imagetype() 等函数检查文件头(Magic Number)时,需要在 payload 前加上合法的文件头。

4.1 常见的文件头

文件类型

Magic Number(十六进制)

文件头内容

JPEG

FF D8 FF E0

ÿØÿà

PNG

89 50 4E 47

.PNG

GIF

47 49 46 38

GIF8

BMP

42 4D

BM

ZIP

50 4B 03 04

PK

4.2 图片马制作

将 WebShell payload 附加到合法图片文件末尾,或者直接在 payload 前添加文件头:

// GIF 文件头 + WebShell
GIF89a
[WEBSHELL_PAYLOAD_REMOVED]

// 使用命令合并图片和脚本
copy normal.jpg + shell.jpg webshell.jpg

五、双扩展名绕过

利用服务器或中间件对文件名解析的特性,通过双扩展名绕过后端校验。

5.1 常见双扩展名技巧

文件名

绕过原理

shell.php.jpg

Apache 从右向左解析,若无法识别 .jpg 则尝试 .php

shell.php%00.jpg

利用 %00 截断,.jpg 被丢弃(PHP < 5.3.4)

shell.asp;.jpg

IIS 分号截断,将 .jpg 忽略

shell.asp:.jpg

Windows ADS 特性,.jpg 被识别为附加数据流

shell.pHp

大小写混淆绕过不区分大小写的校验

六、解析漏洞

不同 Web 服务器和中间件存在不同的文件解析规则,利用这些规则可以绕过上传限制。

6.1 Apache 解析漏洞

  • 多后缀解析shell.php.rarshell.php.xxx 若扩展名无法识别,Apache 会向前寻找可识别的扩展名

  • 配置漏洞AddHandler 指令将某一类文件以 PHP 方式解析

6.2 IIS 解析漏洞

  • 目录解析:创建 shell.asp/ 目录,目录下所有文件按 ASP 解析

  • 分号截断shell.asp;.jpg 被解析为 ASP 文件

  • PUT 请求:WebDAV 开启时可 PUT 任意文件

6.3 Nginx 解析漏洞

  • PHP CGI 解析shell.jpg/xx.php 将图片文件以 PHP 解析(cgi.fix_pathinfo 缺陷)

  • %00 截断:在部分低版本 Nginx + PHP FastCGI 组合中生效

七、.htaccess 与 .user.ini 利用

7.1 .htaccess 绕过

Apache 允许通过 .htaccess 文件进行目录级别的配置。如果服务器允许上传 .htaccess 文件,可以将任意扩展名映射为可执行脚本:

# 将 .jpg 文件以 PHP 方式解析
AddType application/x-httpd-php .jpg

# 或者在当前目录启用 PHP 解析
SetHandler application/x-httpd-php

7.2 .user.ini 利用

PHP 5.3.0 以后支持 .user.ini 文件,可以在每个目录下覆盖 PHP 配置。如果允许上传 .user.ini,可以通过 auto_prepend_fileauto_append_file 在请求其他 PHP 文件时自动执行指定文件:

; 在每次 PHP 请求前自动执行 shell.jpg
auto_prepend_file = shell.jpg

八、条件竞争绕过

某些上传功能先将文件保存到服务器,再进行安全性检查,检查不通过则删除。攻击者可以在文件存活的时间窗口内访问并执行该文件。

8.1 利用原理

攻击流程:上传恶意文件(服务器保存)→ 在文件被删除前快速并发访问 → 执行成功。

8.2 自动化脚本示例

# 使用 Python 多线程同时进行上传和访问
import requests
import threading

def upload():
    while True:
        files = {'file': ('evil.php', payload)}
        requests.post('http://target/upload.php', files=files)

def access():
    while True:
        r = requests.get('http://target/uploads/evil.php')
        if r.status_code == 200:
            print('竞态条件利用成功!')
            break

# 启动多个线程
for i in range(20):
    threading.Thread(target=upload).start()
    threading.Thread(target=access).start()

九、黑名单绕过技巧

很多 Web 应用使用黑名单方式禁止特定扩展名,但由于黑名单不可能穷尽所有可执行扩展名,因此存在多种绕过方式。

9.1 可执行扩展名列表

平台

可执行扩展名

PHP

.php, .php3, .php4, .php5, .phtml, .pht, .php7, .shtml

ASP/ASPX

.asp, .aspx, .asa, .cer, .cdx, .ashx, .asmx

JSP

.jsp, .jspx, .jsw, .jsv, .jspf

CGI

.cgi, .pl

ColdFusion

.cfm, .cfc

9.2 黑名单绕过方式总结

  1. 扩展名变体:使用未被列入黑名单的可执行扩展名(如 .phtml 代替 .php

  2. 大小写混淆.PhP.pHp.ASP

  3. 特殊字符shell.php.(末尾点号 Windows 自动去除)、shell.php (末尾空格)

  4. 双扩展名shell.php.jpg

  5. 参数截断shell.php%00.jpg

  6. 解析漏洞:利用服务器解析特性

  7. 配合文件头:图片马 + 图片扩展名

  8. 配置文件上传:上传 .htaccess.user.ini 配合绕过

  9. 换行截断:在文件名中插入换行符(%0a%0d%0a

  10. Unicode 编码:利用操作系统 Unicode 转换特性(如 shell.php%FF

十、各种绕过方式对比表

绕过方式

难度

适用场景

防御要点

前端 JS 绕过

前端校验场景

后端必须做独立校验

MIME 类型绕过

仅检查 Content-Type 的场景

结合文件内容检测

文件头伪造

使用 finfo/getimagesize 检测

二次渲染 + 内容检测

双扩展名

Apache 默认配置

白名单 + 重命名

解析漏洞

特定服务器版本

更新补丁 + 安全配置

.htaccess 利用

Apache + 允许上传配置文件的场景

禁止上传 .htaccess 文件

.user.ini 利用

PHP 5.3+ 环境

禁止上传 .user.ini 文件

条件竞争

先保存后检查的逻辑

先检查后保存 + 不可预测文件名

黑名单绕过(扩展名变体)

低-中

黑名单过滤不完善的场景

使用白名单策略

十一、通用防御方案

  1. 白名单策略:只允许特定扩展名,而非禁止特定扩展名

  2. 文件内容检测:使用 MIME Magic Number 检测 + 二次渲染图片

  3. 文件重命名:上传后使用不可预测的文件名(如 UUID)

  4. 文件存储分离:文件存储与 Web 根目录分离,通过专门脚本访问

  5. 权限控制:上传目录禁止执行脚本(取消执行权限)

  6. 禁用危险功能:关闭不必要的文件解析功能(如 cgi.fix_pathinfo = 0)

  7. WAF 部署:使用 Web 应用防火墙检测恶意文件内容