
做Web安全的朋友都知道文件上传漏洞是学习Web渗透绕不开的一道坎。不管是CTF比赛、攻防演练还是日常工作里的授权测试文件上传都是高频入口。而我个人最推荐的练习方式就是用靶场。像Pikachu、DVWA、Upload-Labs这类本地靶场把漏洞点固定在一个可控环境里你可以在里面放心大胆地试错不用担心把线上系统搞挂也不用担心法律风险。这篇文章就围绕“文件上传”这一核心主题结合我平时用过的Pikachu、DVWA、Upload-Labs等靶场把搭建过程、绕过思路、常见坑点一次性讲清楚。无论你是刚入门的小白还是已经会点SQL注入想补一下上传这块的进阶学习者这篇文章都能给你一套可以照着做的完整练习路径。先说清楚所有内容都只能用于你自己搭的靶场或者拿到书面授权的测试环境这一点想明白再往下看。1. 文件上传漏洞到底是个什么东西1.1 从一张图片说起上传功能的本质与信任边界几乎每个Web应用里都有上传功能用户头像、商品图片、附件、富文本编辑器里的插图……这些功能本质上都是在做同一件事——让客户端把一个文件交给服务器然后服务器决定怎么处理它。风险就出在这个“处理”上。一个正常的文件上传流程服务器要做的判断有很多文件是不是图片、大小是否超限、后缀名是否允许、内容是否安全、存储路径怎么安排、访问的时候按什么类型返回。任何一个环节偷懒都可能出问题。我打个比方你家门口装了一个“任意包裹收取箱”任何人都能往里扔东西而你拆包裹之前完全不看发件人、不看内容也不管里面装的是日用品还是危险品。文件上传漏洞就是这样一个“信任边界”失控的场景。攻击者真正想干的事并不是单纯上传一个文件而是让服务器把上传的文件当作可执行脚本来运行。一旦PHP、JSP、ASP这些脚本文件被成功上传并以可执行方式被访问攻击者就拿到了一个WebShell相当于在服务器上开了一扇门后续的提权、内网渗透、数据窃取都有了立足点。1.2 一句话木马与WebShell漏洞被利用的典型路径文件上传攻击的最终目标绝大多数情况是落地一个WebShell。WebShell的形式很多最经典的是PHP一句话木马?php eval($_POST[cmd]); ?这行代码作用非常直接接收客户端POST请求中cmd参数的内容然后把它当作PHP代码执行。配合蚁剑、冰蝎这类客户端工具攻击者就能像操作本地终端一样操作服务器。这里需要理解一个关键点为什么一句代码就够了因为Web应用本身已经提供了代码执行的环境缺的只是一个“入口”。文件上传漏洞为攻击者提供了一个可能被服务端信任的入口而一句话木马把这种可能变成现实。后面我会详细讲怎么通过各种手段让服务端“信任”这个恶意文件。1.3 为什么靶场是最适合学习文件上传漏洞的地方直接拿真实网站练手第一是违法第二是不道德第三是容易被封IP甚至被溯源。靶场的价值就在于它是人为设计、专门用来练习漏洞的合法环境。以Pikachu靶场为例它把文件上传漏洞拆成了“客户端检查”“服务端检查”“MIME类型绕过”等好几个独立章节每一关都有清晰的过关目标和提示。DVWA则用Low、Medium、High、Impossible四个难度让你体会同样一个漏洞在不同防护强度下怎么绕过、怎么被防住。Upload-Labs更是专攻文件上传从Pass-01到Pass-21每一关都是一个小挑战全部打通之后你对上传漏洞的理解会彻底上一个台阶。我的建议是先看靶场的PHP源码搞清楚每一关到底校验了什么再动手绕。很多人上来就拿着工具乱打绕过了也不知道为什么过下次换个场景就抓瞎。后面所有章节我都会按“原理-源码-实操”这条线来讲。2. 常见靶场选型与搭建2.1 靶场横向对比Pikachu、DVWA、Upload-Labs各自的定位不夸张地说选对靶场能让你少走一周弯路。我这里列一个横向对比都是我自己实际用过的靶场侧重点难度分层适合人群Pikachu覆盖XSS、SQL注入、RCE、文件上传、CSRF等常见漏洞中文界面带原理说明关卡固定无难度分级刚接触Web安全的新手DVWA经典靶场漏洞类型全面有源码查看功能Low/Medium/High/Impossible想理解漏洞原理与防御的人Upload-Labs纯文件上传专项21道关卡逐级递增按关卡递进从前端校验到解析漏洞专攻文件上传漏洞的进阶学习者如果你时间有限我建议这样选第一次接触文件上传先从Pikachu的“文件上传”章节入手把客户端绕过、MIME绕过、扩展名绕过这几个基础动作练熟然后上Upload-Labs把防御手法逐层剥开你会看到同样一个上传点在不同校验逻辑下绕法差异有多大最后回到DVWA用High和Impossible难度验证自己对防御方案的理解是否到位。2.2 Pikachu靶场搭建过程从解压到访问Pikachu是中文靶场里对新手最友好的一个搭建步骤也不复杂。我在Windows和Linux上都部署过这里以Windows PHPStudy为例说一遍顺手把坑点标出来。第一步准备PHPStudy环境。PHPStudy自带Apache、Nginx、MySQL、PHP多个版本注意PHP版本别选太新的Pikachu有些老代码在PHP 8.x下会直接报错。我实测用PHP 5.6或7.0都挺稳7.4以上谨慎选择。第二步下载Pikachu源码并解压。把解压出来的pikachu目录整个放进PHPStudy的网站根目录默认是WWW目录目录名可以保持pikachu访问路径里带着它就够了。第三步配置数据库。Pikachu需要MySQL数据库支持默认配置在inc/config.inc.php这个文件里。打开看一下默认是root用户、空密码如果你本机MySQL设置了密码就需要改这里的配置define(DB_HOST, 127.0.0.1); define(DB_USER, root); define(DB_PASS, 你的数据库密码); define(DB_NAME, pikachu);第四步初始化数据库。浏览器访问http://127.0.0.1/pikachu/install.php按照提示创建数据库、导入数据。这一步如果提示连接失败90%是DB_PASS和实际MySQL密码不一致。第五步访问http://127.0.0.1/pikachu/能看到首页就说明搭建成功了。2.3 DVWA与Upload-Labs的部署要点DVWA的搭建比Pikachu多一个步骤配置文件。解压后将config/config.inc.php.dist复制一份重命名为config.inc.php然后修改数据库连接信息$_DVWA[ db_server ] 127.0.0.1; $_DVWA[ db_database ] dvwa; $_DVWA[ db_user ] root; $_DVWA[ db_password ] 你的数据库密码;访问http://127.0.0.1/dvwa/setup.php点击“Create/Reset Database”按钮再用默认账号admin密码password登录最后到DVWA Security页面把难度调整为Low开始练习。Upload-Labs更简单对环境几乎无要求。直接把源码放进网站根目录访问首页就能看到21个关卡入口。但要注意个别关卡需要特定中间件特性比如Apache解析漏洞如果你用的是Nginx可能某些关卡效果出不来。建议统一用Apache跑Upload-Labs。2.4 靶场启动失败的排查思路我自己搭建过程中翻过车也帮别人处理过不少问题最常见的坑是这几类端口冲突。Apache默认监听80端口如果电脑上装了IIS、其他Web服务或者某个程序占了80端口Apache会启动失败。解决方法是把Apache端口改成8080或者找到占用端口的进程结束掉。PHP版本不兼容。老靶场代码在新PHP版本下常出现函数报错比如mysqli连接方式差异、ereg这类已删除函数。优先换PHP 5.6或7.0版本问题基本能解决。数据库连接失败。这个最常见。靶场默认配置的数据库用户名密码和你本机不一致改配置文件就能修复。访问空白页。打开PHPStudy的日志文件看PHP错误信息。多半是缺少某个扩展或者文件权限不对。3. 文件上传漏洞的核心绕过手法与实操复现3.1 前端JavaScript校验的绕过很多靶场和真实系统的第一道防线是前端JavaScript校验。常见做法是在表单提交时检查文件扩展名或文件大小比如只允许.jpg、.png。这道防线对普通用户有效但对懂一点HTTP的人来说几乎等于不存在。绕过方式有两种。第一种最简单粗暴直接禁掉浏览器JavaScript。Chrome可以在设置里关掉JavaScript也可以按F12打开开发者工具在Sources面板临时修改校验函数或者直接把form表单的onsubmit事件删掉。第二种用Burp Suite拦截上传请求把文件改成恶意文件后再放行。以Pikachu的“客户端检查”关卡为例完整流程是这样先准备一个shell.php文件内容是一句话木马?php eval($_POST[cmd]); ?正常上传时前端会拦截提示“文件类型不正确”。这时候打开Burp Suite代理开启拦截再次选择shell.php上传Burp会抓到请求包。观察请求体中的文件名Content-Disposition: form-data; nameuploadfile; filenameshell.php Content-Type: application/octet-stream把Content-Type改成image/jpeg文件名改成shell.jpg如果服务端只看Content-Type不看扩展名或者保持shell.php如果只看Content-Type然后放行请求。如果页面提示“上传成功”说明前端校验就是纸老虎。这里有个关键认知前端校验只关乎用户体验不关乎安全。服务端接收到的HTTP请求客户端想怎么改就怎么改。你拦不拦截它取决于服务端有没有自己的校验逻辑。3.2 MIME类型与Content-Type绕过MIME类型校验是服务端最常见的一种做法。服务端读取上传请求中Content-Type头如果不在允许列表里比如image/jpeg、image/png直接拒绝。MIME绕过的本质就是“欺骗Content-Type头”。用Burp拦截把上传请求中的Content-Type从application/octet-stream改成image/png或image/jpegContent-Disposition: form-data; nameuploadfile; filenameshell.php Content-Type: image/png改完后放行如果服务端只检查类型不检查内容这关就过了。但要注意MIME校验不是只出现在“服务端检查”这类明显关卡里很多真实应用在上传头像时只做了这个检查。这也是为什么我在做授权测试时一定先抓包看一眼上传点到底校验了什么再决定用哪种绕过方式。3.3 扩展名绕过黑名单、大小写、双写、空格与点扩展名校验是文件上传里花样最多的部分也是Upload-Labs的核心主线。服务端通常维护一个黑名单或者白名单黑名单禁止.php、.asp、.jsp等可执行后缀白名单只允许.jpg、.png、.gif等图片后缀。黑名单绕过思路很多我按实战频率排一下第一大小写绕过。黑名单写了.php但服务端没有做strtolower转换上传shell.Php或shell.ASP在某些中间件下依然会被当作PHP解析。这里要看你搭的环境是否支持对应后缀的解析。第二空格和点绕过。Windows环境下文件名末尾的空格和点会被系统自动去除。上传shell.php后面带一个空格或shell.php.服务端判断后缀时看到的是“空格”或“点”不在黑名单里但文件落盘后Windows会把它还原成shell.php。上传shell.php.加上Burp改包在Linux靶机上不一定成功但在Windows环境经常有效。第三双写绕过。针对黑名单替换逻辑如果服务端把“php”替换成空字符串那上传shell.pphphp时替换后正好变回shell.php。第四%00截断绕过。这个比较老多见于PHP 5.2及更早版本。上传shell.php%00.jpg服务端看到后缀是.jpg就放行但在文件操作函数比如include里%00被视为字符串终止符导致实际处理的文件是shell.php。在Upload-Labs里有专门一关练这个。新一代PHP已经修复了该问题这里只做了解。白名单绕过则比黑名单更麻烦——单纯改后缀是绕不过白名单的。白名单场景下要么配合解析漏洞后面专门讲要么利用服务端对文件名的拼接逻辑漏洞。比如服务端保存文件名时用了类似$filename . . . $ext的拼接方式你可以尝试上传一个名为shell.php.的文件让拼接后的名字变成shell.php.然后由中间件特性把它当php解析。3.4 文件内容校验绕过图片马与文件头伪造很多你以为“万无一失”的上传点其实卡在内容校验这一步。服务端可能用getimagesize()、finfo_file、exif_imagetype这类函数检查文件内容确认它真是一张图片。这时候绕过思路就变成把恶意代码藏进一个真正的图片文件里。生成图片马的命令很经典copy 1.jpg/b shell.php/a shell.jpg/b表示二进制复制/a表示文本追加。执行后生成的shell.jpg打开还是一张正常图片但文件末尾带着PHP代码。如果服务端只做文件头校验不检查文件体这关就过了。实操时要注意一个细节如果一句话木马直接裸露在文件末尾某些中间件或WAF会拦截。更隐蔽的做法是把PHP代码拆散拼接或者用短标签。我通常在真实场景里用这种写法?php $c $_POST[cmd]; assert($c); ?但很多靶场对assert也有检测所以具体还是先看靶场源码用了什么过滤函数再决定用哪种姿势。3.5 解析漏洞的组合利用如果上传点做了严格的白名单加内容校验单点硬来越来越难这时候要考虑组合拳解析漏洞 上传。Apache解析漏洞是典型例子。Apache从右往左识别后缀遇到不认识的后缀会继续往左找。上传一个shell.php.xyz如果xyz不在识别列表里Apache会往左看最终把shell.php.xyz当PHP执行。前提是httpd.conf里启用了AddType或MultiView功能。Upload-Labs里有一关就是专门构造这个场景。Nginx解析漏洞常见于配置不当时比如location ~ \.php$ { include fastcgi_params; fastcgi_pass 127.0.0.1:9000; }如果把上传的图片命名为shell.jpg然后访问http://target/shell.jpg/.php在旧版本Nginx下可能被交给PHP-FPM解析。这个洞在真实环境已经很少见但靶场里依然能练到“上传一个合法图片 利用URL路径特性触发解析”的组合思维。还有IIS 6.0的分号截断漏洞上传shell.asp;.jpgIIS会截断分号后面的内容当作shell.asp执行。老系统还能遇到新版本IIS已经修复。解析漏洞的核心启发是单个校验点再严格服务端配置、中间件版本、文件操作逻辑都可能引入新问题。这也是为什么我练习时从不只看靶场某一关的“标准解法”而是每过一关都会思考如果校验再强一点我还能通过什么途径让代码执行4. 从靶场到实战上传漏洞的防御与自查清单4.1 服务端校验的正确姿势绕过手法学了一大堆真到了写代码的人那里到底怎么防才靠谱我的总结是不要信黑名单不要只信MIME不要只信文件头把防线叠起来。第一扩展名用白名单。只允许.jpg、.png、.gif这类明确的后缀不要用黑名单去排除php、jsp、asp。黑名单永远有遗漏白名单从根本上缩窄了攻击面。第二文件名服务端重命名。用户上传文件叫什么根本不重要保存时用随机字符串或时间戳重命名并把用户输入的文件名单独存到数据库里。这样即使用户传了shell.php.jpg落盘后的文件名也变成了无意义的字符串即使文件被上传成功也很难被定向访问到。第三校验位置必须在服务端。前端JavaScript校验只是用户体验不能作为安全措施。服务端至少要做扩展名白名单、MIME类型校验、文件头校验、文件大小限制四层检查。第四文件内容二次检测。对图片类上传用getimagesize()确认图像宽高数据合法必要时对文件内容做扫描拦截明显的一句话木马特征。4.2 目录权限与执行权限分离上传目录不要放在Web根目录下更不要放在可执行脚本的目录里。理想方案是把上传的文件存储到独立的静态文件服务器或对象存储OSSWeb服务器只负责转发读写请求不直接解析上传目录里的任何文件。如果必须把文件放在应用目录里至少做到两点上传目录禁用PHP执行权限。Apache下可以用.htaccess禁止解析FilesMatch \.(?i:php|php5|phtml)$ Require all denied /FilesMatchNginx下则通过location配置限制location /upload/ { location ~ \.php$ { deny all; } }这样你上传的图片马即使存在服务器上也不具备执行条件危害直接被腰斩。4.3 上传功能自查清单做授权测试或者代码审计时我习惯用下面这张清单过一遍上传功能检查点通过标准失败风险扩展名校验服务端白名单且存储时重命名攻击者直接上传可执行脚本MIME校验校验Content-Type且与扩展名一致攻击者改包绕过文件内容校验使用getimagesize等确认文件真实类型图片马直接上传成功文件大小限制有明确的大小上限校验磁盘被填满存储目录权限禁止执行脚本上传后文件被当作代码执行文件名处理随机化重命名不保留用户原始文件名路径穿越、覆盖已有文件访问控制上传文件不可被目录遍历获取攻击者无法定位WebShell日志记录记录上传者IP、时间、原始文件名被攻击后无法溯源按这张表逐项检查一遍你就知道一个上传功能是纸糊的还是铁打的。反过来做渗透测试的时候我也是用这张表反向推断目标可能在哪一环出了纰漏。4.4 把靶场经验迁移到真实授权测试中有几个习惯我是强烈建议从靶场练习阶段就养成的第一记录每一步的Payload和结果。很多人练习时payload一把梭过了就过了回头复盘什么也没留下。我在练习阶段就用一个表格维护自己的“绕过清单”记录目标校验方式、使用payload、是否成功、失败原因。这个清单后来在真实授权测试里价值巨大相当于你个人的漏洞检测字典。第二小步验证不要盲目尝试。每改一个参数观察服务端响应差异。比如上传返回“文件类型错误”和“上传成功”中间差的可能就是Content-Type的值这种观察能帮你快速判断目标服务器校验了什么。第三合规边界不能忘。授权测试范围要白纸黑字写清楚不能在未授权的域名、IP上做任何验证。哪怕你只是上传一个无害的txt只要越界就可能违法。5. 常见问题与排查技巧实录5.1 靶场搭建与通关高频问题速查表我把平时被问得最多的问题整理成一张表按“现象-原因-解决方案”的方式写现象原因解决方案Apache启动失败提示端口被占用80端口被其他服务占用查看占用进程并结束或把Apache端口改为8080访问靶场页面空白PHP版本过高导致函数不兼容换成PHP 5.6或7.0查看PHP错误日志定位安装数据库时报连接错误数据库密码和配置文件不一致修改靶场配置文件中的DB_PASS上传成功后返回404上传目录路径写错或文件被重命名检查靶场源码中保存路径访问实际落盘文件名上传成功但打开是一堆乱码脚本未被解析被当作静态文件返回确认中间件是否正确解析该后缀检查解析漏洞条件一句话木马被杀安全组件或WAF拦截敏感字符串使用混淆写法拆分特征字符串5.2 一句话木马被杀的排查思路很多人会遇到一个情况靶场明明提示上传成功访问文件却发现内容被删除或者页面提示“检测到恶意代码”。这是服务端做了内容过滤常见于开发者在保存文件后用正则匹配敏感函数比如eval、assert、system、base64_decode。解决办法是混淆变量和函数名。我常用的一种方式是把函数名拆开拼接?php $a ev.al; $a($_POST[cmd]); ?注意第二行的$a($_POST[cmd])实际上变成eval($_POST[cmd])。如果服务端只做简单的关键字替换这种拼接就能绕过去。但如果是深度内容检测还需要更复杂的变形比如把参数base64编码后解码执行。这类内容比较深靶场练习时点到为止即可千万不要把这些技巧用到未授权的系统上。5.3 上传成功但解析失败的原因分析这是新手最容易困惑的场景上传是成功的路径也看到了但访问就是不出代码执行效果。我踩过的坑主要有四个第一文件名被服务端重命名了。上传上去的shell.php被改名成随机字符串你拿着原文件名去访问当然找不到。这种情况要看靶场源码里保存时怎么处理文件名。第二上传目录禁止执行脚本。靶场有些关卡会在目录下放.htaccess明确禁掉php执行目的就是要你结合其他漏洞去打而不是直接访问。这时候可以尝试利用.htaccess本身的上传或者文件包含漏洞配合图片马。第三内容里代码被过滤掉了一部分。比如服务端把?php替换成空字符串你上传的shell.php里实际上没有有效代码。检查响应页面的源码看代码是否完整。第四中间件不支持你用的扩展名。Apache默认不会把.xyz当PHP解析除非配置了AddType或存在解析漏洞。先确认环境是不是支持对应解析条件。5.4 实测心得靶场练习的三个阶段最后分享一个我个人觉得非常有效的练习路径分三个阶段来走阶段一跟教程通杀。把Pikachu和Upload-Labs每道关卡的教程做一遍记录标准解法。这个阶段不用想太多重点是建立印象哦原来还有这种绕法。阶段二关掉提示看源码。把教程关掉直接打开靶场源码先读清楚每一关的校验逻辑再尝试不看提示独立过一遍。这个阶段你会开始真正理解校验代码的每一行也能理解为什么有些payload在这个关卡有效、换一关就失效。阶段三组合利用练习。不要满足于单点过关尝试组合漏洞链路。比如在一个靶场里先用文件上传拿到图片马再用文件包含漏洞执行它或者把上传漏洞和日志注入结合模拟一条完整的攻击链。这个阶段对你理解真实攻击场景的帮助远超死记几十种绕过姿势。我当年就是从Pikachu一路打到Upload-Labs最后一关中间无数次卡住、无数次翻源码看正则、无数次发现“原来这个过滤函数还能这么绕过”。踩的坑多了以后再去看真实系统里的上传功能基本一眼就能判断出哪些环节有搞头、哪些环节固若金汤。这种识别能力恰恰就是靶场练习最值钱的产出。最后再补一句所有绕过技巧请只在靶场和自己有权限的系统中使用。技术本身没有立场但使用技术的人必须守住边界。