1. 从零搭建本地文件上传演练环境
文件上传漏洞是Web安全领域最经典、也最容易被低估的一类问题。很多人觉得"不就是传个文件吗",但真正上手一套完整的上传靶场,从第一关到第十一关一路打下来,你会发现每一关背后都对应着一种真实的防御思路和绕过手法。这套靶场(业内通常叫 upload-labs)一共二十来关,我打算分上下两篇来写,上篇覆盖前十一关,下篇再啃后面那些更刁钻的。
这篇文章适合谁看?如果你刚接触Web安全,想找一个能系统练手的靶场;或者你已经会一些基础操作,但每次遇到上传点就只会传个图片马然后一脸茫然,那这篇内容应该能帮到你。我会把每一关的判断逻辑、绕过原理、实操步骤、以及我踩过的坑都讲清楚,尽量做到你跟着做就能复现。
1.1 环境准备:为什么推荐用集成环境而不是手动搭
搭建这套靶场,最省事的方式是用现成的集成环境。我试过手动配 Apache + PHP + MySQL,光是版本兼容就能折腾一下午——PHP 版本高了某些函数行为变了,版本低了又跑不起来新代码。所以我的建议是直接用 phpStudy 或者类似的集成面板,选一个 PHP 5.6 到 7.4 之间的版本,这个区间对靶场的兼容性最好。
具体步骤大致是这样:
- 下载并安装集成环境面板,启动 Apache 和 MySQL 服务。
- 把 upload-labs 的源码放到网站根目录下,比如
WWW/upload-labs。 - 浏览器访问
http://localhost/upload-labs,能看到关卡列表就说明环境通了。 - 如果遇到报错,优先检查 PHP 版本和
php.ini里的file_uploads、upload_max_filesize、post_max_size这几个配置项。
提示:靶场里有些关卡依赖特定的 PHP 配置(比如
magic_quotes_gpc在旧版本才有),如果你用的是 PHP 7 以上,个别关卡的表现可能和教程不一样。遇到这种情况,先确认自己的 PHP 版本,再对照关卡说明调整。
1.2 上传漏洞到底在防什么:先建立正确的认知框架
在动手之前,有必要把"文件上传漏洞"这件事的本质说清楚。服务器允许用户上传文件,本身是正常需求——头像、附件、导入数据都需要上传。问题出在:如果服务器对上传文件的类型、内容、存放位置、执行权限没有做严格限制,攻击者就能上传一个可执行脚本,然后通过访问这个脚本在服务器上执行任意代码。
所以防御的核心就三件事:不让传危险类型、传上去了也不能执行、执行了也拿不到高权限。后面十一关,本质上就是在反复演练这三件事的各种"做了但没做全"的情况。理解了这条主线,你再看每一关的代码,就会觉得思路特别清晰。
常见的绕过维度有这么几类,先有个印象:
| 绕过维度 | 典型手法 | 对应关卡思路 |
|---|---|---|
| 前端校验 | 改包、禁用JS | 第一关 |
| MIME类型校验 | 修改Content-Type | 第二关 |
| 黑名单后缀 | 大小写、特殊后缀、点空格 | 第三到五关 |
| 内容检测 | 图片马、绕过getimagesize | 部分关卡 |
| 解析漏洞 | 利用服务器解析特性 | 后续关卡 |
2. 前端校验与MIME类型:最基础的两道门
前两关是整套靶场里最简单的,但恰恰是新手最容易"知其然不知其所以然"的地方。很多人照着教程改个包就过了,但问他为什么能过,答不上来。我把这两关的原理拆开讲。
2.1 第一关:前端JS校验,改个包就完事
第一关的代码逻辑是在前端用 JavaScript 做校验,只允许上传.jpg、.png、.gif这类图片后缀。你在页面上选一个.php文件,它会直接弹窗告诉你"不允许上传该类型"。
这里的核心问题是:前端校验完全运行在浏览器里,而浏览器是用户可以完全控制的。校验代码在客户端执行,就意味着用户可以绕过它。绕过方式有两种:
- 方法一:禁用浏览器 JavaScript。在浏览器设置里关掉JS,或者用开发者工具临时禁用,前端校验直接失效,请求会原样发到服务器。
- 方法二:改包。先把文件名改成
.jpg让前端校验通过,然后用抓包工具(比如 Burp Suite)拦截请求,把文件名改回.php再放行。
我一般用第二种,因为更贴近真实场景——真实环境里你未必能随便关JS,但抓包改包是标配技能。具体操作:
- 准备一个内容为
<?php echo "test"; ?>的shell.php文件。 - 在页面上传时,先把文件名改成
shell.jpg,让它通过前端校验。 - 抓包,找到
filename="shell.jpg"这一行,改成filename="shell.php"。 - 放行请求,提示上传成功。
- 访问上传后的文件路径,如果页面输出
test,说明PHP代码被执行了。
注意:这一关能成功的前提是服务器端完全没有做任何校验,只靠前端挡着。现实中如果只做前端校验,等于没做。
2.2 第二关:MIME类型校验,改Content-Type即可
第二关在前端校验的基础上,服务器端加了一层 MIME 类型检查。它读取请求头里的Content-Type字段,判断是不是image/jpeg、image/png这类图片类型。
这里要理解一个关键点:Content-Type是客户端在请求里自己声明的,服务器如果只信这个字段,那它就是在信用户的"一面之词"。你完全可以在抓包工具里把它改成任意值。
绕过步骤:
- 上传
shell.php,抓包。 - 找到请求头里的
Content-Type: application/x-php(或者类似值)。 - 改成
Content-Type: image/jpeg。 - 放行,上传成功。
这一关和第一关的区别在于:第一关改的是文件名,第二关改的是请求头里的类型声明。两关都在告诉你同一个道理——凡是客户端能控制的东西,都不能作为安全判断的依据。
我见过不少初学者在这里卡住,原因是他们只改了文件名没改 Content-Type,或者反过来。记住:这一关文件名保持.php不变,只改 Content-Type。
3. 黑名单的千层套路:后缀绕过的花式玩法
从第三关开始,服务器端开始用黑名单来过滤后缀了。所谓黑名单,就是列出一堆"危险后缀"(比如.php、.asp、.jsp),只要上传的文件后缀不在黑名单里就放行。听起来挺合理,但黑名单这东西,只要漏掉一个变体,整个防御就形同虚设。第三到第五关,就是黑名单漏网之鱼的三种典型。
3.1 第三关:黑名单不全,换个后缀就过
第三关的黑名单里只写了.php、.asp、.jsp这些常见脚本后缀,但它漏掉了一些"冷门但同样能被解析"的后缀。比如在 Apache 的某些配置下,.php3、.php4、.php5、.phtml这些后缀也会被当作 PHP 来解析。
绕过方式就是直接把文件名改成shell.phtml或者shell.php3,如果服务器配置支持,就能解析执行。
这一关给我的启发是:黑名单永远不可能穷举完整。你列了十个危险后缀,可能还有第十一个你没见过。所以成熟的做法是用白名单——只允许明确安全的类型,其余一律拒绝。这也是后面几关会反复出现的对比。
3.2 第四关:大小写绕过,Windows下的经典坑
第四关的黑名单比第三关全了一些,把.php3、.phtml这些也加进去了。但它犯了一个低级错误:没有做大小写统一处理。
在 Windows 环境下,文件系统对大小写不敏感,shell.PHP和shell.php在系统看来是同一个文件。而黑名单里只写了小写的.php,所以你上传shell.PHP就能绕过。
操作很简单:把文件名改成shell.PHP,直接上传。
提示:这一关在 Linux 环境下不一定能成功,因为 Linux 文件系统区分大小写。靶场通常跑在 Windows 上,所以能过。这也提醒我们,防御代码要考虑跨平台差异,不能想当然。
3.3 第五关:点空格绕过,Windows文件名的特性
第五关的黑名单又全了一点,大小写也处理了。但它没考虑到 Windows 文件系统的一个特性:文件名末尾的点和空格会被自动去掉。
也就是说,你上传一个叫shell.php.(末尾带点)或者shell.php(末尾带空格)的文件,Windows 在保存时会自动把末尾的点和空格删掉,最终存成shell.php。但黑名单在检查时,看到的后缀是.或者空格,不在黑名单里,就放行了。
绕过方式:抓包,把文件名改成shell.php.(注意末尾有个点),放行。
这一关和第四关一样,都是利用了操作系统层面的特性来绕过应用层的检查。这类漏洞的隐蔽性很强,因为写代码的人往往只盯着"后缀字符串",忽略了文件系统本身的行为。
4. 从后缀到内容:检测维度的升级
前面几关都在跟"后缀"较劲,从第六关开始,服务器端的检测维度开始升级——有的开始检查文件内容,有的开始用更复杂的逻辑。这一部分我挑几个有代表性的关卡来讲。
4.1 第六关:黑名单加空格过滤,但还有漏网之鱼
第六关的黑名单把点空格、大小写这些都处理了,看起来严实了不少。但它依然有漏洞——它没有过滤::$DATA这种 Windows 特有的数据流后缀。
在 Windows 的 NTFS 文件系统里,shell.php::$DATA会被解析成shell.php,但黑名单看到的后缀是::$DATA,不在名单里。抓包把文件名改成shell.php::$DATA就能绕过。
这一关再次印证了那个观点:黑名单是在和攻击者玩"找漏网之鱼"的游戏,而攻击者只需要找到一个漏洞就够了。
4.2 第七关:空格被过滤,试试点号
第七关把空格也过滤了。这时候可以试试在末尾加点号,也就是shell.php.。原理和第五关类似,利用 Windows 自动去掉末尾点的特性。
如果点号也被过滤了,还可以尝试其他特殊字符组合。这一关的核心思路是:当一种绕过方式被封堵时,换一个操作系统层面的特性继续尝试。
4.3 第八关:::$DATA被过滤后的思路
第八关把::$DATA也加进了黑名单。这时候常规的后缀绕过基本都被堵死了。但别忘了,我们还有内容检测这条路可以走——也就是制作图片马。
图片马的原理是:把一个 PHP 脚本代码嵌入到一张正常的图片文件里。这样文件从后缀看是图片,从内容看也包含合法的图片头,能骗过getimagesize这类内容检测函数。但文件里同时藏着 PHP 代码,一旦被当作 PHP 解析,代码就会执行。
制作图片马的方法:
# 用 copy 命令把图片和PHP文件合并 copy normal.jpg /b + shell.php /a webshell.jpg在 Linux 下可以用:
cat normal.jpg shell.php > webshell.jpg上传这个webshell.jpg,如果服务器只检查内容不检查后缀,就能过。但要注意,图片马单独访问是不会执行 PHP 的,需要配合文件包含漏洞或者服务器解析漏洞才能触发。这一关如果只要求上传成功,那上传图片马即可。
5. 那些容易翻车的细节与排查思路
打到第十关、第十一关的时候,很多人会发现自己明明按照教程操作了,却总是失败。这一部分我专门讲讲排查思路和常见坑,这些是文档里不会写的。
5.1 上传成功但访问不执行:先确认这三件事
这是最常见的问题。你上传了shell.php,提示成功,但访问的时候要么下载文件,要么显示源码,就是不执行。排查顺序:
- 确认文件真的传上去了。有些关卡上传后会重命名文件,或者存到别的目录。去上传目录里看看文件到底叫什么、在哪。
- 确认服务器解析配置。如果上传的是
.phtml、.php3这类后缀,需要 Apache 配置里有对应的解析规则。默认配置可能不支持。 - 确认访问路径正确。靶场的上传目录通常是
upload/或者按日期分的子目录,路径错了自然访问不到。
我踩过最坑的一次是:文件传上去了,但被重命名成了一串随机字符加.jpg后缀,我还在那傻傻地访问shell.php。后来才发现要去上传目录里看实际文件名。
5.2 抓包工具的正确用法:别只盯着文件名
很多人用抓包工具只改文件名,其实请求里能改的地方多着呢。除了filename和Content-Type,还有:
- 文件内容:可以直接在抓包工具里修改上传文件的内容,不用在本地反复改文件。
- 请求路径:有些关卡的上传接口路径有讲究,改路径可能有意想不到的效果。
- 多个同名参数:有些服务器只检查第一个参数,但实际用的是最后一个,这种"参数污染"也是绕过思路。
提示:抓包工具里改内容后,记得更新
Content-Length,否则请求可能被截断。不过大多数现代抓包工具会自动帮你算,不用手动改。
5.3 第十关和第十一关:组合拳的味道
第十关和第十一关通常是前面几种绕过手法的组合。比如同时存在黑名单、内容检测、后缀处理,你需要把前面学到的技巧串起来用。这两关我没有单独展开讲,是因为它们本质上是前面知识的综合应用——先判断服务器做了哪几层检测,再逐层找绕过点。
我的建议是:遇到这种综合关卡,不要急着试,先把关卡源码读一遍(靶场一般会提供源码),看清楚每一层检测的逻辑,然后针对性地设计绕过方案。这比盲目尝试效率高得多。
6. 打完前十一关后,我对上传防御的几点体会
一路打下来,最大的感受是:文件上传防御没有"一招鲜",它是一个多层配合的体系。前端校验、MIME检查、后缀黑名单、内容检测,单独拿出来任何一个都不够,必须叠起来用,而且每一层都要做对。
具体来说,我现在做上传功能时会坚持几条原则:
- 用白名单而不是黑名单。只允许明确的几种后缀,其余全拒。黑名单永远有漏网之鱼。
- 重命名上传文件。不管用户传什么名字,服务器统一重命名成随机字符串加安全后缀,彻底断掉后缀绕过的念想。
- 上传目录禁止执行脚本。通过服务器配置,让上传目录里的文件只能被读取,不能被当作脚本执行。这样即使传了恶意文件,也执行不了。
- 内容检测要做,但不能只靠它。
getimagesize这类函数可以被图片马绕过,它只能作为辅助手段。
前十一关基本覆盖了"后缀绕过"和"基础内容检测"这两大类场景。后面还有更精彩的——比如利用服务器解析漏洞、竞争条件、二次渲染等等,那些我放在下篇再聊。如果你把前十一关的每一关都亲手复现一遍,并且能说清楚每一关的绕过原理,那你对文件上传漏洞的理解就已经超过大部分只会照抄教程的人了。