恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
文件上传漏洞基础:upload-labs 绕过前端校验与 MIME 类型限制
首页
资讯中心
/
文件上传漏洞基础:upload-labs 绕过前端校验与 MIME 类型限制
文件上传漏洞基础:upload-labs 绕过前端校验与 MIME 类型限制
发布时间:2026/10/15 21:56:50
做 Web 安全的人电脑里多半都装着一套 upload-labs。这个靶场从 Pass-1 开始一层一层把文件上传漏洞的常见绕过场景还原出来。我最早刷它的时候觉得 Pass-1、Pass-2 太简单随手就过了真正让我卡住的是后几关的服务端校验。但后来回头才发现前两关里藏着文件上传漏洞最基础的“信任边界”思维为什么前端校验不是校验为什么 MIME 类型不可信为什么攻击者总是能改掉你以为安全的字段。如果你刚开始接触文件上传漏洞或者做过几个 CTF 题目但对着上传逻辑还是有点含糊这篇内容就是给你准备的。我用本地环境完整跑一遍这两个关卡把每一步为什么这么做也一起讲清楚。1. 环境准备与靶场搭建1.1 文件上传漏洞的核心问题是什么文件上传功能几乎每个业务系统都有头像上传、附件上传、商品图片、文档协作、编辑器配图本质上都是“用户把一个文件从浏览器送到服务器”。如果服务器只关心“能不能收”却不去校验“这个文件合不合法、有没有威胁”那这个入口就可能变成攻击者投递恶意脚本的通道。最常见的威胁是什么是攻击者上传了一个脚本文件比如 PHP、JSP、ASP 文件然后通过地址直接访问它脚本就在服务器上执行了。攻击者可以读取配置、篡改页面、连接内网甚至拿到操作系统的命令执行权限。这个问题的危害程度很高但产生原因往往很基础很多系统把“用户传来的信息”当成了“可靠证据”。 upload-labs 就是用关卡的形式把这些被当成证据的变量一个个演示出来。1.2 本地搭建 upload-labs 靶场upload-labs 是一套开源的文件上传漏洞练习环境关卡难度是渐进式的。本地跑起来只需要准备一个 PHP 集成环境把源码放到网站根目录启动服务后访问入口文件就能看到关卡列表。大多数关卡不需要数据库但建议把 MySQL 服务也一并启动后面某些关卡可能会用到。部署过程我整理成几个步骤安装 PHP 集成环境选择 Apache PHP 组合。新手不要一上来就用 Nginx因为 Nginx 默认不会处理 PHP 解析还要额外配置 FastCGI容易在环境上劝退。把 upload-labs 的源码解压到网站根目录目录名最好不要带中文和空格比如直接叫upload-labs。启动 Apache 服务确认 PHP 版本能正常运行。PHP 5.x 到 7.x 常见版本都可以但后面部分关卡在高版本 PHP 下的表现会和文档描述不一致这个不用慌。浏览器访问http://127.0.0.1/upload-labs/如果页面显示了关卡导航就说明环境起来了。确认上传目录是否可写。Linux 环境下给上传目录设置可写权限Windows 下一般不需要额外处理。之所以强调用 Apache是因为 Pass-1 和 Pass-2 的核心是“上传姿势”不是“服务器解析姿势”。先把最简单的解析环境跑通后面再研究 Nginx 解析特性思路会清晰很多。1.3 需要准备的工具清单这套关卡不需要很复杂的工具基础三件套就够了浏览器Chrome 或者 Firefox用来操作页面和查看开发者工具。抓包改包工具可以用常见的 Burp Suite 社区版功能足够拦截 HTTP 请求、修改 multipart 数据包。如果你暂时没装浏览器开发者工具的“网络”面板也能看到请求但没法方便地改包再放出去强烈建议装一个抓包工具。文本编辑器用来写测试用的 PHP 文件。在本地 HTTP 环境下测试抓包工具不需要额外装 HTTPS 证书只要把浏览器代理指向抓包工具的监听端口就行。如果你用的是http://127.0.0.1还要注意抓包工具默认也能抓回环流量不需要额外配置。2. Pass-1 前端 JavaScript 校验绕过2.1 Pass-1 校验过程还原打开 Pass-1 页面选择一个.php文件点击上传浏览器会立刻弹窗提示只允许上传.jpg/.png/.gif图片类型。注意这个提示出现的时候请求根本没有发送到服务器整个拦截过程都发生在浏览器里。从页面源码可以看到表单绑定了onsubmit事件执行一个checkFile()函数。这个函数用文件名后缀做白名单判断常见代码长这样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; }这个函数就是全部的安全控制。它运行在谁的浏览器运行在攻击者自己的浏览器里。你希望攻击者用他自己的电脑检查他自己的行为这天然就不可靠。所以前端的任何校验都只能算“用户体验”不能算“安全边界”。2.2 最简单的姿势禁用 JavaScript 后直接上传既然校验是 JS 做的那让 JS 不执行就行了。Chrome 的开发者工具里可以针对某个站点暂时禁用 JavaScript打开开发者工具设置找到站点权限或者直接在地址栏左侧的站点设置里把 JavaScript 权限改成“禁止”然后刷新页面。刷新之后再选择shell.php文件点击上传。因为checkFile()没有执行表单就会正常提交。Pass-1 的后端代码没有做任何服务端校验所以文件直接保存成功上传过程没有任何阻力。不过这个方法有一个小缺点浏览器里所有 JS 都不跑了页面样式和弹层可能会有一些异常。但本地测试影响不大。遇到个别浏览器对127.0.0.1的 JS 禁用不生效可以改用开发者工具里的方式或者直接换一个思路。2.3 可定制的方法通过开发者工具改掉校验函数不想禁用 JS也可以直接修改 JS 逻辑。打开开发者工具切到“控制台”面板重新定义checkFile函数function checkFile() { return true; }这样无论选择什么后缀的文件最终onsubmit事件都会拿到true表单正常提交。如果你更喜欢改页面元素也可以在“元素”面板里找到表单标签把onsubmitreturn checkFile()这个属性整体删掉效果一样。这种操作的逻辑其实很简单前端代码就是一组文本攻击者可以随喜欢修改它。以后你在别的项目里看到前端限制文件类型、限制输入长度、限制提交频率第一反应都应该是这些限制在客户端都是可以绕过的。真正的安全判断必须放在后端。2.4 抓包改文件名的通用姿势还有一种更接近实际渗透的思路不用禁用 JS也不改页面逻辑而是把文件后缀临时改成允许的后缀绕过前端的白名单然后在请求到达服务器之前用抓包工具把文件名在数据包里改回来。具体操作过程如下把测试用的 PHP 文件保存为shell.jpg内容可以先简单写成?php phpinfo(); ?先用phpinfo()验证上传环境比直接用一句话木马更容易排错。开启抓包工具的代理浏览器配置好代理后访问 Pass-1 页面。选择shell.jpg点击上传。因为前端检查的后缀是.jpg所以弹窗不会出现浏览器直接发出请求。抓包工具拦截到这个 POST 请求后找到类似下面这一行Content-Disposition: form-data; nameupload; filenameshell.jpg把filenameshell.jpg改成filenameshell.php其他内容不动。放行请求页面提示上传成功。然后访问上传目录下的shell.php看到 PHP 信息输出就说明 Pass-1 被成功绕过了。这个方法比起前面两种多了一个好处你能亲眼看到 HTTP 里“文件上传”到底长什么样后面理解和修改 Content-Type 也会更顺畅。3. Pass-2 服务端 MIME 类型校验绕过3.1 Pass-2 到底在校验什么Pass-2 比 Pass-1 往前迈了一步校验不再是浏览器里的 JS而是到了服务端。直接上传一个shell.php页面会提示“文件类型不正确”。即使你按照 Pass-1 的姿势禁用 JS依然会收到这个提示。抓包看一下就明白了。浏览器在提交文件时会根据文件扩展名自动生成一个Content-Type字段上传.jpg时是image/jpeg上传.png时是image/png上传.php时往往是application/octet-stream。Pass-2 的后端代码大致是这样的逻辑$allowed array(image/jpeg, image/png, image/gif); if (!in_array($_FILES[upload][type], $allowed)) { exit(文件类型不正确请重新上传); }这里的$_FILES[upload][type]并不是服务端检测文件内容得到的它只是请求头里一个普通字段属于客户端提交的变量。你让它是什么它就可以是什么。3.2 抓包改 Content-Type 实操Pass-2 的绕过核心就一句话把请求文件块里的Content-Type改成白名单允许的类型同时保留.php后缀。操作步骤准备shell.php内容继续用?php phpinfo(); ?方便验证。打开抓包工具的拦截功能浏览器代理指向抓包工具。在 Pass-2 页面选择shell.php点击上传。这次请求会正常发出去被抓包工具拦截。在拦截到的请求里找到文件块部分。请求头最上面的Content-Type: multipart/form-data; boundary...不要改要改的是文件块里面、filename下一行的那个小Content-Type。把Content-Type: application/octet-stream修改为Content-Type: image/png改成image/jpeg或者image/gif也可以只要和允许列表匹配。放行请求页面显示上传成功。这时文件名仍然是shell.php访问上传目录下的该文件PHP 正常执行。这里有一个常见的坑有同学把请求最上面那行multipart/form-data的 Content-Type 改掉了结果请求解析失败。正确的位置一定是在Content-Disposition之后、文件内容之前的那一行。如果你在 Burp 的 Raw 面板里看不清切到 Params 面板找到上传文件的Content-Type参数直接改会更直观。3.3 两个 Pass 放在一起看规律已经浮出来了Pass-1 和 Pass-2 看起来一个是前端校验一个是后端校验但本质上的安全缺陷是一样的都在信任一个客户端可控的变量。我做一个简单的对比。对比项Pass-1Pass-2谁的校验浏览器 JS服务端 PHP校验依据文件扩展名白名单Content-Type 白名单数据来源用户在界面上选择的文件名客户端请求头字段绕过方式禁用JS、改JS、抓包改名抓包改 Content-Type信任错误信任了浏览器里的本地逻辑信任了请求头里可伪造的字段看完这张表你再看后面那些花哨的绕过手法会发现都是在“找这条信任链里还有哪一环节是客户端可以控制的”。4. 上传漏洞的影响范围与连锁危害4.1 一个 WebShell 意味着什么前两关上传成功的文件如果是一个 PHP 脚本本质上就是在服务器上留下了一个可执行代码的入口。这个入口通常叫 WebShell。它可能只有一句话?php eval($_POST[x]); ?但通过它可以动态执行任意 PHP 代码。攻击者可以先读取站点配置、数据库账号密码再通过数据库连接获取业务数据也可以在服务器上操作文件把页面改成钓鱼页面甚至反弹一个完整的系统 shell。危害等级不取决于文件大小而是取决于它能调用系统多少能力。4.2 从上传点到整个服务器的攻击路径我在本地复现时把完整路径拆成了几步找到一个存在上传功能的页面。通过 Pass-1/Pass-2 类似的方式绕过前端或 MIME 校验把可执行脚本传到服务器。通过路径访问这个脚本触发代码执行。从代码执行进一步拿到系统命令执行权限。数据库、内网其他主机、同一网络里的系统都会成为后续目标。所以文件上传漏洞的影响范围不是一个文件而是一台服务器、一个内网、一批业务系统。很多演练和安全评估里攻击者就是以一个头像上传点为突破口逐步拿下了整个环境。4.3 这类漏洞常在哪些功能里出现不是只有“上传”按钮才叫上传功能。用户头像、商品图片、文档附件、编辑器图片、导入模板、PDF 生成、文件预览接口都可能涉及文件上传。有些系统为了兼容格式允许上传各种扩展名结果把.php、.jsp这类可执行文件也放进来了。另外文件上传漏洞不只在 PHP 环境才有。Java 环境下的.jsp、.warWindows 下的.asp、.aspx都是同一个问题的不同表现。Pass-1 和 Pass-2 教的不是“绕过某一种语言接口”而是“上传校验的弱点在哪里”的通用思维。5. 防御思路从源头堵住这两类漏洞5.1 服务端扩展名白名单与重命名不要用黑名单防御。黑名单很难列全.php被禁了还有.php5、.phtml、.php7等一堆变种。正确做法是在服务端做扩展名白名单只允许指定的图片后缀并且不保留用户上传的原始文件名。保存时用系统生成的随机文件名比如$new_name date(YmdHis) . bin2hex(random_bytes(8)) . .jpg;这样即使别人上传了一个.php文件保存后的文件名也不是.php而且这个随机名不是用户可控的。这个方法能直接绕开很多保存路径可控、后缀可控的问题。5.2 文件内容校验与二次渲染Content-Type 只能作为参考不能作为安全依据。服务端需要对文件内容做真实检查。对于图片上传可以用 PHP 的getimagesize()或finfo_file()读取文件头部信息确认它确实是图片格式。更彻底一点的方案是对上传文件做二次渲染比如用图像库把原始图片重新生成一遍。攻击者在图片里夹带的恶意 PHP 代码会在渲染过程中被处理掉即使文件内容里原本有异常也会被“清洗”干净。当然二次渲染也有绕过场景但它已经能让大部分攻击者放弃你这种目标转投更容易的入口。防护不是靠某一条规则。扩展名白名单、文件内容检测、随机文件名、上传目录禁止执行脚本这些措施叠起来才是一条真正有效的防线。5.3 不止于上传点目录与解析权限上传目录最好和 Web 根目录隔离或者干脆放到独立对象存储里通过 CDN 访问。退一步说如果上传目录必须留在本地就要确保这个目录里的文件不会被视为可执行脚本。在 Apache 环境里可以通过配置禁止在该目录解析 PHP在 Nginx 环境里要避免把上传路径交给 FastCGI 处理。还有一个常见操作给上传目录设置写和执行权限分离进程以最小权限运行不要让 Web 服务用 admin 或者 root 权限管理文件。最后日志审计一定要做异常上传行为的记录可能在事发后帮你定位第一时间发生了什么。6. 常见问题与排查实录6.1 我在刷 Pass-1/Pass-2 时踩过的坑刷 Pass-1 的时候我绕过了 JS但上传后访问文件一直 404。后来才发现是没注意到后端把文件保存到了upload目录而我访问的路径写错了。建议上传前先看页面源码里的 action 和保存路径不要猜。刷 Pass-2 的时候遇到过修改 Content-Type 后依然提示“文件类型不正确”。原因是抓包工具拦截后我改成了请求头最上方的Content-Type而不是文件块内部的那个。这个问题非常典型直接看 Raw 报文里的文件块确认修改内容在Content-Disposition下面那行。还有个环境坑我在 Nginx 环境下怎么传都不执行后来换成 Apache 一次就过了。如果你只是学习上传逻辑不要在一个自己不熟悉的解析环境上浪费太多时间。6.2 问题速查表现象可能原因排查方向上传时浏览器弹窗请求没有发出前端 JS 校验拦截禁用 JS 或改前端逻辑上传后提示“文件类型不正确”后端校验 Content-Type 或扩展名抓包查看文件块里的 Content-Type上传成功但访问文件变成下载服务器没有解析 PHP换 Apache 环境或检查 PHP 配置上传成功但访问 404保存路径或文件名不对看后端保存逻辑确认路径抓包工具看不到请求浏览器代理没有生效检查代理设置和抓包工具拦截状态WebShell 连接不上木马语法不支持或路径不对先用phpinfo()验证是否执行这些坑其实都不是原理问题多半是环境或者对 HTTP 报文不熟悉造成的。把报文多看几遍把保存路径摸清楚基本就能顺下来。我自己的体会是Pass-1 和 Pass-2 刷完后脑子里会留下一个非常重要的印象所有客户端能控制的字段都不能作为安全决策的依据。浏览器里的 JS 是请求头里的 Content-Type 也是。后面你在真实项目里看到上传功能第一反应不再是“能不能绕过”而是“服务端到底校验了什么、信了谁”。这就是入门这两关真正的价值。最后再提醒一句这套靶场只适合在本地授权环境里折腾不要拿同样的手法去测试你没有权限的系统。安全测试的前提永远是授权技术能力越强越要守好这条线。