今天将正式踏入文件上传漏洞的战场。文件上传是 Web 应用最常见的功能之一,也是攻击者获取服务器权限的最直接途径。我们将从最基础的“上传流程”讲起,亲手绕过前端 JavaScript 验证,通过抓包修改后缀名完成攻击。这是文件上传漏洞利用的入门第一关。
第十一周·周一:上传流程与前端验证绕过——抓包改后缀
🎯 今日学习目标(达成效果)
- 能完整描述一次 Web 文件上传的 HTTP 交互流程:浏览器构建
multipart/form-data请求,服务端接收并保存文件; - 能区分客户端验证(前端 JS)与服务端验证的差异,并解释为何客户端验证“形同虚设”;
- 能独立搭建一个存在前端验证缺陷的文件上传靶场(或使用 Upload-labs Pass-1),通过浏览器上传正常图片;
- 能使用 BurpSuite 拦截上传请求,修改
filename参数的后缀名(如将.jpg改为.php),绕过前端 JavaScript 限制,成功上传恶意脚本; - 能通过浏览器访问上传的恶意文件,执行 PHP 代码,验证服务器端缺乏有效防护;
- 能从安全角度总结:前端验证仅提升用户体验,绝不能作为安全控制;服务端必须对文件类型、内容、权限进行严格校验。
📘 一、文件上传的完整流程
一个典型的文件上传功能涉及以下步骤:
- 表单构建:HTML 中使用
<input type="file">元素,并且表单的enctype属性必须设置为multipart/form-data,否则文件内容不会被正确编码传输。 - 浏览器提交:用户选择文件后点击上传,浏览器将文件数据打包进 HTTP 请求体。每个文件字段前会有边界分隔符(boundary),并附带
Content-Disposition头,包含文件名、字段名等信息。 - 服务器接收:Web 服务器(如 Apache、Nginx)接收到请求,交由后端脚本(PHP、Python 等)处理。
- 临时存储:PHP 等语言会先将上传文件保存在临时目录(如
/tmp/phpXXXXXX),然后通过全局变量(如$_FILES['file'])供脚本访问。 - 业务处理:开发者编写代码对文件进行校验(类型、大小、内容)、重命名、移动至目标目录。
- 响应返回:成功或失败信息返回给浏览器。
安全关键点:第 5 步的校验逻辑直接决定了是否存在文件上传漏洞。如果校验仅发生在客户端(第 1 步的 JavaScript),或者服务端校验不充分,攻击者即可上传恶意文件。
📘 二、客户端(前端)验证的原理与脆弱性
许多 Web 应用为了“即时反馈”,会在用户选择文件时用 JavaScript 检查文件后缀名:
<inputtype="file"onchange="checkFile(this)"><script>functioncheckFile(input){varallowed=['jpg','jpeg','png','gif'];varext=input.value.split('.').pop().toLowerCase();if(!allowed.includes(ext)){alert('仅允许上传图片文件!');input.value='';// 清空选择}}</script>或者通过表单的onsubmit事件拦截。这种验证完全在浏览器中执行,攻击者可以通过以下多种方式绕过:
- 禁用 JavaScript:浏览器设置中关闭 JS,验证直接失效。
- 修改 DOM:通过开发者工具删除事件处理函数,或修改
accept属性。 - 抓包修改:将合法后缀的文件用 BurpSuite 拦截,在请求中修改
filename为恶意后缀(今天重点)。 - 直接构造请求:使用 Burp Repeater 或 curl 手动构建上传请求,完全无视前端校验。
核心原则:任何客户端控制都不是安全控制。服务端必须独立进行所有验证。
📘 三、绕过前端验证:抓包改后缀实战
假设存在一个上传点,前端只允许.jpg文件。我们的目标是上传一个 PHP 一句话木马shell.php,并通过访问它来执行系统命令。
1. 准备恶意 PHP 文件
创建一个简单的 PHP 文件info.php(先保存为.jpg以通过前端):
<?phpphpinfo();?>或者更直接的 webshell:
<?php@eval($_POST['cmd']);?>将文件命名为info.jpg。注意,文件内容为 PHP 代码,但后缀是.jpg。
2. 使用 Burp 拦截上传请求
- 配置浏览器代理,打开 BurpSuite,开启 Intercept。
- 在靶场上选择
info.jpg文件,点击上传。 - Burp 截获 POST 请求,请求体类似:
-----------------------------1234567890 Content-Disposition: form-data; name="uploadFile"; filename="info.jpg" Content-Type: image/jpeg <?php phpinfo(); ?> -----------------------------1234567890--
3. 修改文件名后缀
在 Burp 拦截窗口中,将filename="info.jpg"改为filename="info.php",其他部分保持不变。然后点击Forward放行。
4. 检查上传结果
如果服务端没有对文件后缀进行有效校验,文件会以info.php成功保存到上传目录(如uploads/)。浏览器访问http://靶机/uploads/info.php,如果看到 PHP 配置信息页面,说明代码被执行,绕过成功。
如果使用一句话木马:上传后访问http://靶机/uploads/shell.php,虽然页面可能空白或报错,但代码已驻留,可通过蚁剑或 curl POST 命令执行任意代码。
✍️ 四、动手实践:搭建 Upload-labs Pass-1 并进行绕过
环境准备
- 靶场:Upload-labs 是一个专门练习文件上传漏洞的开源靶场,Pass-1 即为前端验证。
- 可以使用 Docker 快速部署:
docker pull c0ny1/upload-labs并运行。 - 或者直接在你的 PHP 环境中部署源码,将靶场目录放入 Web 根目录。
实践步骤
- 打开 Upload-labs 第一关(Pass-1)。页面提示“请选择要上传的图片”。
- 尝试直接上传
shell.php,前端弹窗提示“不允许上传该类型文件”,文件被清空。 - 将
shell.php改名为shell.jpg(内容仍为 PHP 代码)。 - 打开 Burp 代理,确保拦截开启。在靶场中选择
shell.jpg,点击上传。 - Burp 拦截到 POST 请求,找到
filename="shell.jpg",将其改为filename="shell.php"。 - 点击 Forward 放行。
- 页面返回上传成功,并显示文件路径
upload/shell.php。 - 在浏览器中访问该路径
http://靶机/upload-labs/upload/shell.php,若显示 phpinfo 或空白(一句话木马),则攻击成功。
记录:
- 原始请求中
Content-Type: image/jpeg可能为application/octet-stream,但不影响,因为我们重点修改文件名。 - 如果服务端同时校验了
Content-Type(MIME 类型),明天将专门绕过。今天仅演示前端绕过。
📝 五、课后测试题与解析
测试题 1:为什么前端 JavaScript 验证不能作为文件上传安全控制的唯一手段?请写出至少两种绕过方法。
参考答案:
前端 JS 验证只能作用于用户的浏览器,无法强制所有请求都遵守。绕过方法:
- 禁用浏览器 JavaScript:在设置中关闭 JS,验证逻辑完全不执行,可以直接上传任意文件。
- 抓包修改请求:在请求到达服务器之前拦截,修改文件名后缀(如
.jpg→.php),服务器无法判断原始客户端验证是否通过。 - 直接构造 HTTP 请求:使用 Burp Repeater、curl 等工具手动创建上传请求,不经过浏览器表单,完全忽略前端校验。
测试题 2:在 BurpSuite 中,你需要修改 HTTP 请求的哪个部分来绕过前端验证?请具体描述。
参考答案:
需要修改请求体中的Content-Disposition头部内的filename属性。例如,原请求为filename="evil.jpg",将其改为filename="evil.php"。这样,即使前端验证只允许.jpg文件,服务器接收到的文件名却以.php结尾,从而可能被当作脚本执行。同时,需确保请求的其余部分(如 boundary、文件内容)保持正确,避免破坏multipart/form-data格式。
✅ 今日学习效果自检清单
- 我能画出文件上传的 HTTP 请求结构,并指出
filename和Content-Type字段的位置 - 我理解客户端验证与服务端验证的区别,以及为什么“前端不可信”
- 我成功搭建或使用了 Upload-labs Pass-1,并通过抓包改后缀上传了 PHP 文件
- 我在浏览器中访问了上传的恶意文件,验证了 PHP 代码被执行
- 我知道除了抓包,还可以通过禁用 JS 或直接构造请求绕过前端验证
- 我意识到服务端必须对上传文件的类型、后缀、内容、权限进行全面检查
⚠️ 阶段避坑重点
- 不要混淆
Content-Type与文件后缀:今天修改的是filename后缀,而非Content-Type请求头。即使Content-Type为image/jpeg,文件仍可被 PHP 引擎解析(取决于服务器配置)。明天的课程将专门针对 MIME 类型绕过。 - 注意浏览器缓存:修改前端脚本后,可能需要强制刷新(Ctrl+F5)才能生效。但攻击者会直接无视前端,无需关心缓存。
- 靶场文件权限:确保上传目录具有执行 PHP 的权限。在某些配置下,即使后缀为
.php,若目录禁用了 PHP 解析(如通过.htaccess),攻击也无法执行代码。这属于高级防御,后续会学。 - 不要在公网未授权系统测试:文件上传漏洞危害极大,仅限本地靶场。
明天我们将学习MIME 类型绕过与黑名单绕过,届时你会看到即使修改了文件名后缀,服务器仍可能通过Content-Type头进行防御,而我们将用更巧妙的方法突破它。请保留今天的 Burp 环境和靶场。