1. 靶场搭建与工具准备:你的第一个“黑客”实验室

如果你对网络安全感兴趣,想亲手试试那些听起来很酷的“文件上传漏洞”,但又不知道从何下手,那今天这篇文章就是为你准备的。咱们不搞那些虚头巴脑的理论,直接上手实战。我会带你从零开始,搭建一个完全属于你自己的、安全的漏洞实验环境,然后一步步攻克21种经典的防御姿势。

首先,你得有个“靶子”来练手。这里我们选择 upload-labs,一个在安全圈内非常经典的文件上传漏洞靶场。它用PHP编写,模拟了各种真实网站在上传功能上可能犯的错误。你不需要一台独立的服务器,在你自己电脑上就能跑起来。

第一步:准备“地基” 你需要一个能运行PHP的Web环境。最简单的方法就是安装一个集成软件包,比如 PHPStudy 或者 XAMPP。我习惯用PHPStudy,因为它对Windows用户特别友好,一键安装,自带Apache、MySQL和PHP。记得选择PHP 5.x的版本(比如5.4.45),因为有些古老的漏洞特性在新版本里已经被修复了,为了复现所有关卡,老版本环境更合适。

第二步:下载“靶场” 打开GitHub,搜索 c0ny1/upload-labs,把这个项目下载到本地。解压后,你会看到一个名为 upload-labs 的文件夹。把这个文件夹整个丢到你PHPStudy的 WWW 目录下。比如,我的路径是 D:\phpstudy_pro\WWW\upload-labs。

第三步:启动服务 打开PHPStudy,启动Apache和MySQL服务。然后在浏览器里访问 http://127.0.0.1/upload-labs。如果看到一个写着“文件上传漏洞练习平台”的页面,恭喜你,靶场搭建成功!

第四步:装备你的“武器库” 光有靶场不够,我们还需要趁手的工具来发起“攻击”(当然是针对我们自己搭建的靶场)。

  1. Burp Suite Community:这是我们的“拦截器”和“改装车间”。绝大部分的绕过操作都需要用它来拦截浏览器发送的HTTP请求,然后修改其中的数据。社区版免费,功能对于学习来说完全够用。记得配置好浏览器的代理(通常是127.0.0.1:8080),并安装Burp签发的CA证书,这样才能拦截HTTPS流量。
  2. 蚁剑(AntSword):这是我们的“后门连接器”。当我们成功上传了一个Webshell(一句话木马)文件后,就需要用蚁剑来连接它,从而在靶场上执行命令,模拟攻击者获取服务器权限的过程。它是一个图形化工具,比古老的“中国菜刀”更现代、更好用。
  3. 浏览器:推荐使用Firefox或Chrome,并安装好用于配合Burp Suite的代理插件,比如FoxyProxy,方便切换代理模式。
  4. 文本编辑器:用来编写我们的一句话木马文件。一句话木马本质上就是一个特殊的PHP文件,内容极其简单,比如:<?php @eval($_POST['pass']);?>。这行代码的意思是,接收一个名为 pass 的POST参数,并将其中的内容当作PHP代码来执行。这就是我们获取Webshell的钥匙。

好了,实验室搭建完毕,工具也齐活了。别担心,这一切操作都在你自己的电脑上进行,绝对安全,也绝对合法。接下来,我们就正式进入闯关模式,看看那些看似牢固的文件上传检查,是如何被我们一一绕过的。

2. 前端到后端的初阶绕过:与检查机制“捉迷藏”

很多网站的上传功能会设置多层检查,我们从最外面一层开始。

2.1 绕过前端JavaScript验证(Pass-01)

这是最简单、也是最常见的一种防护。你选择了一个 shell.php 文件点击上传,页面立刻弹窗提示“只允许上传.jpg/.png/.gif格式!”。注意,这个弹窗出现时,你的文件其实根本没有离开你的浏览器,更没有发送到服务器。 这纯粹是前端JavaScript代码在“多管闲事”。

绕过方法简直像撕掉门口的告示一样直接:

  1. 先把你的 shell.php 改名为 shell.png。这下前端JS检查通过了,文件开始上传。
  2. 此时,立刻打开Burp Suite的代理拦截功能。你会看到一个HTTP请求正在被Burp捕获。
  3. 在这个请求里,找到 filename="shell.png" 这一部分,把它改回 filename="shell.php"。
  4. 点击“Forward”放行这个被修改过的请求。

原理与实战: 前端验证就像超市门口的保安,只凭你穿的衣服(文件名)判断能不能进。我们穿了件“png”马甲混过保安,进去之后再把马甲脱掉(Burp改包),恢复“php”的真身。服务器后端如果没有其他检查,就会乖乖收下这个PHP文件。上传成功后,用蚁剑连接 http://你的靶场地址/upload-labs/upload/shell.php,密码填你木马里设定的(比如上面例子里的 pass),就能成功获得Webshell。

2.2 伪造MIME类型(Pass-02)

过了第一关,第二关的页面可能没有弹窗了,但上传PHP文件还是会失败。查看页面源码或者用Burp观察响应,可能会提示“文件类型不正确”。这说明服务器端开始检查了,但它检查的是什么呢?是 Content-Type 这个HTTP头。

当你上传文件时,浏览器会告诉服务器:“我传的这个东西是什么类型”。对于图片,通常是 image/jpeg 或 image/png;对于PHP脚本,则是 application/octet-stream(二进制流)。第二关的服务器只认前面那几种图片类型。

绕过方法就是“说谎”:

  1. 正常上传你的 shell.php 文件,并用Burp拦截请求。
  2. 在Burp的拦截视图里,找到 Content-Type: application/octet-stream 这一行。
  3. 把它改成 Content-Type: image/jpeg。
  4. 放行请求。

原理与实战: 这关的检查者像个“形式主义者”,它不关心文件实际内容是什么,只认包装盒上的标签。我们给一个PHP文件贴上了“image/jpeg”的标签,它就以为这是个图片,爽快放行。同样,上传成功后用蚁剑连接即可。这种方法对付只做MIME类型检查的后端非常有效。

2.3 黑名单的漏网之鱼:特殊后缀(Pass-03)

从这一关开始,防御升级到了后端黑名单。服务器有一份“坏人名单”,上面写着 .php, .asp, .jsp 等危险后缀,上传这些文件直接拒绝。但这份名单可能不全。

绕过思路:寻找“别名” Apache等Web服务器可以通过配置,将其他后缀的文件也当作PHP来解析。常见的“PHP别名”后缀有:.phtml, .php3, .php4, .php5, .pht, .phps 等。如果服务器配置了 AddType application/x-httpd-php .php .phtml .php3,那么 .phtml 文件就会被当作PHP执行。

实战操作:

  1. 将你的木马文件 shell.php 重命名为 shell.phtml。
  2. 直接上传。如果靶场环境配置了解析这些后缀,就会成功。
  3. 用蚁剑连接时,地址要相应改为 .../shell.phtml。

踩坑提醒: 这个能否成功,完全取决于服务器(靶场)的配置。upload-labs的Pass-03关卡模拟了这种配置环境,所以可以成功。但在真实渗透测试中,这需要你通过信息收集去猜测或验证目标服务器是否存在此类配置。

3. 黑名单绕过进阶:利用系统与协议特性

当黑名单看起来越来越全,我们就需要利用一些系统和协议层面的“特性”或“漏洞”来钻空子了。

3.1 掌控解析规则:.htaccess文件攻击(Pass-04)

这是Apache服务器特有的一种强大攻击方式。.htaccess 是一个分布式配置文件,它可以覆盖其所在目录及其子目录的Apache配置。如果我们能上传一个 .htaccess 文件,就能“命令”服务器:“把这个目录里所有.jpg文件都当成PHP来执行!”

实战步骤:

  1. 制作.htaccess文件:新建一个文本文件,改名为 .htaccess(注意前面有个点)。用记事本打开,写入一行代码:AddType application/x-httpd-php .jpg。这行代码的意思是,将.jpg后缀的文件解析为PHP。
  2. 上传.htaccess文件:在靶场页面,先上传这个 .htaccess 文件。如果服务器没有禁止上传此类配置文件,通常会成功。
  3. 上传图片马:制作一个图片Webshell。用命令行 copy normal.jpg/b + shell.php/a shell_with_webshell.jpg(Windows)或者用文本编辑器在图片文件末尾追加PHP代码。这个文件实际内容是图片,但末尾藏了一句话木马。
  4. 触发执行:上传这个 shell_with_webshell.jpg 文件。此时,由于 .htaccess 文件的作用,服务器会把这个“图片”当作PHP脚本来解析,执行隐藏在图片末尾的代码。访问这个图片的URL,就能看到代码执行效果。

原理深度: 这招相当于在敌人的军营里,偷偷修改了他们的武器使用手册,把“扫帚”的用途改成了“机枪”。只要手册(.htaccess)生效,你递过去一把看起来像扫帚的机枪(图片马),他们也会按机枪来用。防御这种攻击的方法就是在服务器端禁止上传 .htaccess 这类配置文件。

3.2 大小写、空格与点的游戏(Pass-05, 06, 07)

Windows系统有一个“特性”:它对文件名的大小写不敏感。shell.php, shell.PHP, shell.Php 在它看来是同一个文件。但很多用PHP写的检查逻辑,是大小写敏感的。

  • Pass-05 大小写绕过:如果黑名单里只有小写的 .php,那么上传 shell.PHP 就可能绕过检查。因为 in_array(‘.PHP’, [‘.php‘]) 会返回 false。在Burp里把 filename 改成 shell.PHP 即可。

  • Pass-06 空格绕过:有些检查函数在比较后缀名后,没有用 trim() 函数去除文件名首尾的空格。你可以在Burp里将文件名改为 shell.php (注意php后面有个空格)。当这个带空格的文件名被保存到Windows系统时,系统会自动去掉末尾的空格,最终存储为 shell.php。

  • Pass-07 点号绕过:和空格类似,Windows系统也会自动去除文件名末尾的“.”。你可以上传 shell.php.。后端检查时,strrchr(‘shell.php.‘, ‘.’) 取到的后缀是 ‘.‘,不在黑名单 [‘.php‘] 里,于是通过。保存到系统时,点被去除,文件变回 shell.php。

核心思想: 这些方法都利用了 “检查逻辑” 和 “最终存储系统” 之间的差异。检查时是一个样子(如 .PHP, php , php.),存储时变成了另一个样子(.php)。这种差异就是安全漏洞。

3.3 Windows的“数据流”把戏:::$DATA绕过(Pass-08)

这是Windows NTFS文件系统的一个特性,叫“交替数据流”(Alternate Data Streams)。在文件名后加上 ::$DATA,Windows会将其视为文件的主数据流,但在保存时,::$DATA 及其之后的内容会被忽略。

操作很简单:

  1. 在Burp中,将上传的文件名改为 shell.php::$DATA。
  2. 服务器端的检查逻辑可能只匹配 .php,看到 ::$DATA 觉得不是 .php,就放行了。
  3. Windows系统在保存文件时,会忽略 ::$DATA,最终在磁盘上创建的文件就是 shell.php。

注意: 这个方法仅对Windows服务器有效。如果靶场运行在Linux上,这招就不灵了。

3.4 双写绕过(Pass-11)

这一关的过滤逻辑非常“耿直”:它发现文件名里有黑名单里的词(如php),就直接把它删掉,而且只删一次。

绕过方法: 既然你删一次,那我就给你两个,你删掉一个,还剩一个。 将文件名改为 shell.pphphp。过滤逻辑发现里面有 php,把它删掉,字符串变成了 shell.php,正好是我们想要的。

这种过滤方式在编写时显然考虑不周,但恰恰是这种“想当然”的逻辑,在早期的很多程序中真实存在。

4. 白名单与更精巧的绕过:截断、竞争与渲染

白名单机制(只允许.jpg, .png, .gif等)比黑名单严格得多。要绕过它,需要更精巧的技术。

4.1 截断攻击:让路径“戛然而止”(Pass-12, 13)

这是一种利用旧版本PHP字符串处理漏洞的攻击。核心是 空字符(%00,URL编码形式)。在C语言等中,空字符表示字符串结束。PHP在5.3.4之前的版本,在某些情况下也会把 %00 之后的字符截断。

场景:假设上传代码是这样处理路径的:$file_path = $_GET[‘save_path’] . ‘/’ . $file_name;。其中 save_path 我们从URL参数可控。

  1. 我们上传一个名为 shell.jpg 的文件。
  2. 在Burp中,我们修改上传请求,或者构造一个GET参数,使得最终的保存路径变成:upload/shell.php%00/20231011.jpg。
  3. 旧版本PHP在处理 shell.php%00/20231011.jpg 时,遇到 %00 就认为字符串结束了,实际保存路径变成了 upload/shell.php。后面白名单检查的 .jpg 被截断丢弃了。

实战要点:

  • Pass-12 (GET型截断):参数在URL中,直接使用 %00。
  • Pass-13 (POST型截断):参数在POST数据体中,%00 不会被自动解码。需要在Burp的Hex视图里,找到对应位置,将字符的16进制值改为 00。
  • 重要:这个漏洞在PHP >= 5.3.4 后已被修复。现在的靶场为了复现,通常需要特意配置低版本的PHP环境。

4.2 条件竞争:与时间赛跑(Pass-18)

这是一种非常经典的“竞态条件”漏洞。它的逻辑是:服务器先允许文件上传到临时目录,然后再去检查文件内容是否合法(比如是否是真正的图片),如果不合法就删除。

攻击窗口就出现在“上传完成”到“检查删除”之间这短短的几毫秒或几百毫秒里。

  1. 我们编写一个特殊的PHP木马,它一被访问,就会在服务器上生成一个永久的Webshell文件。例如:<?php file_put_contents(‘shell.php‘, ‘<?php @eval($_POST[cmd]);?>‘); ?>
  2. 我们利用Burp Suite的 Intruder 模块,以极快的速度(比如每秒数百次)重复发送上传这个木马文件的请求。
  3. 同时,我们用浏览器或脚本,以同样快的速度去访问那些可能已经上传成功的临时文件地址。
  4. 只要在某个瞬间,我们访问文件的速度快过了服务器删除它的速度,我们的木马就会被执行,从而在服务器上生成一个真正的 shell.php。

这个过程就像一场赌博,赌的就是“我访问到你”发生在“你删除我”之前。通过自动化工具发起海量请求,这个概率可以被大大提升。

4.3 图片马与二次渲染绕过(Pass-14, 15, 16, 17)

这是更高级的绕过,针对的是那些不仅检查后缀、MIME,还会检查文件内容的严格防御。

基础图片马:将PHP代码附加到一个正常图片文件的末尾。用 getimagesize() 或 exif_imagetype() 函数检查时,它能通过,因为文件头确实是合法的图片格式。但如果服务器只是简单地“包含”了这个图片文件(存在文件包含漏洞),附加的PHP代码就会被执行。

二次渲染绕过:这是最难的关卡之一。服务器不仅检查,还会对上传的图片进行“二次渲染”——即用GD库或ImageMagick等函数,将图片打开、再重新保存。这个过程会剥离所有非图片数据,普通的图片马会失效。

绕过二次渲染的思路:

  1. 研究渲染算法:你需要研究目标服务器使用的图片处理库(如GD库)对特定格式(GIF/PNG/JPG)的渲染细节。例如,GD库处理GIF时,可能会保留帧与帧之间的数据区。
  2. 制作特制图片马:找到一个图片,在二次渲染后,其某些数据块的位置和内容保持不变。将你的PHP代码精心编码后,插入到这些“不变”的数据区中。这需要对图片文件格式和库函数有很深的理解,通常需要借助专门的工具或脚本来生成。
  3. 上传验证:上传制作好的特制图片马,下载被服务器二次渲染后的图片,用16进制编辑器对比,确认你的代码是否幸存。

这个过程更像是在制作一个“数字寄生虫”,让它能寄生在图片的合法结构里,躲过系统的“清洗”流程。虽然难度高,但一旦成功,防御效果极佳的文件内容检查也会被穿透。

5. 防御之道:从攻击者视角构建安全

一口气看了这么多绕过技巧,是不是觉得文件上传功能处处是坑?没错,构建一个安全的文件上传功能,必须进行“纵深防御”。

  1. 使用白名单,而非黑名单:这是最重要的原则。只允许业务明确需要的后缀,如 .jpg, .png, .pdf。任何不在名单上的,一律拒绝。
  2. 文件内容检查:使用 getimagesize(), exif_imagetype() 或更复杂的文件头检查,确保文件内容与其后缀匹配。但要注意,这可以被图片马绕过。
  3. 重命名文件:上传后,立即用随机字符串(如UUID)重命名文件,并保留原始后缀。这样即使攻击者上传了恶意文件,也无法直接访问或预测其路径。$new_filename = md5(uniqid()) . ‘.‘ . $ext;
  4. 设置正确的权限:上传目录应禁止脚本执行。在Apache中,可以在 .htaccess 里设置 php_flag engine off。将上传目录放到Web根目录之外,通过脚本代理访问,是更彻底的方法。
  5. 使用专用存储服务:对于大型应用,可以考虑使用OSS、COS等对象存储服务。它们通常提供了更完善的上传策略和安全检查。
  6. 扫描与监控:对上传的文件进行病毒/木马扫描。记录所有上传日志,监控异常访问模式(如一个图片文件被频繁以POST方式访问)。

upload-labs的21关,几乎涵盖了文件上传漏洞的所有经典场景。我建议你不要仅仅满足于“通关”,而是每过一关,都去仔细读一读靶场提供的源代码,理解它为什么会被绕过。然后思考,如果让你来修复这个漏洞,代码应该怎么写。只有这样,你才能从“知道怎么攻”真正进阶到“懂得怎么防”,成为一名合格的网络安全实践者。实战中遇到的情况可能更复杂,但万变不离其宗,理解这些核心原理和思维模式,就是你最有力的工具。

更多推荐