1. 项目概述:文件上传漏洞攻防的实战演练场
如果你正在学习Web安全,尤其是想深入理解文件上传这个看似简单却暗藏玄机的漏洞点,那么CTFshow平台上的Web文件上传系列关卡(151-160关)绝对是一个不可多得的实战宝库。这十道题目,就像一位经验丰富的教练为你精心设计的阶梯训练,从最基础的客户端校验绕过,一路升级到需要综合运用多种技巧的高级利用。它不仅仅是让你上传一个Webshell那么简单,而是逼迫你去思考服务器端每一个过滤环节的逻辑,理解黑名单、白名单、内容校验、条件竞争等核心防御机制的运作方式,并学会如何见招拆招。
我花了几天时间,从头到尾通关了这十关,过程中有恍然大悟的畅快,也有卡壳时的反复尝试。今天这篇文章,我就以一名CTF选手和渗透测试从业者的双重身份,为你详细拆解这十关的解题思路、技术细节和那些容易踩的坑。我的目标不是直接给你答案,而是带你还原我的思考过程,让你明白“为什么要这么做”,以及“下次遇到类似问题该怎么想”。无论你是刚入门的新手,还是想巩固文件上传知识的老手,相信这篇实战解析都能给你带来实实在在的收获。我们将从最简单的前端绕过开始,逐步深入到解析漏洞、条件竞争、二次渲染等高级话题,完整覆盖文件上传漏洞的攻防全景。
2. 核心思路与关卡设计逻辑拆解
在深入每一关的具体操作之前,我们有必要先站在出题人的角度,理解这十关的整体设计脉络。CTFshow的题目设计往往具有连贯性和递进性,151-160关正是文件上传漏洞知识体系的一个微型缩影。
2.1 漏洞演进的四个阶段
这十关大致可以划分为四个难度递增的阶段,每个阶段重点考察一种或一类防御机制的绕过技巧:
-
阶段一(基础绕过,如151关) :聚焦于 客户端防御 。这是最古老的防御方式,通常通过JavaScript在文件被发送到服务器之前,检查文件扩展名或MIME类型。绕过它不需要任何复杂的服务器交互,只需要拦截并修改HTTP请求即可。这一关的目的是让你熟悉Burp Suite等抓包工具的基本操作,建立“所见非所得”的安全意识。
-
阶段二(服务器端校验绕过,如152-155关) :核心是 服务器端扩展名过滤 。这里又细分为黑名单、白名单、大小写、点号、空格、
::$DATA等技巧。出题人会模拟真实环境中开发人员编写的不严谨过滤代码,例如只过滤了php却没过滤pHp或php5。这部分考察你对Web服务器解析文件扩展名的规则是否有深入了解。 -
阶段三(内容校验绕过,如156-158关) :难度升级,服务器不仅看“文件名”,还要检查“文件内容”。常见手段包括 检测文件头(Magic Bytes) 、 检测
<?php ?>等PHP标签 ,甚至进行 图片二次渲染 。你需要学会制作图片马、在文件开头添加幻数、使用<script language=“php”>等特殊标签,或利用二次渲染中的残留数据。 -
阶段四(综合与高级利用,如159-160关) :这是终极挑战,往往需要结合其他漏洞或利用服务器特性。例如 条件竞争漏洞 (在文件被删除前访问它),或者利用 解析漏洞 (配合服务器配置不当,如
upload.php.jpg被解析为PHP)。这部分考察你的综合思维能力和对Web应用整体流程的理解。
2.2 解题的通用方法论
面对任何一关文件上传,我的思考路径通常是固定的,这形成了一个高效的排查框架:
- 信息收集 :首先,毫无保留地尝试上传各种文件(正常图片、Webshell、畸形文件名文件),观察前端的反应(JS弹窗)和后端的返回信息(错误提示)。这些提示是宝贵的线索。
- 定位过滤点 :根据错误信息,判断过滤发生在客户端还是服务器端?是检查扩展名、MIME类型、文件头,还是文件内容?
-
猜测过滤规则
:如果是黑名单,名单里有哪些?是否大小写敏感?是否过滤了空格、点、
::$DATA?如果是白名单,允许的后缀有哪些? - 尝试绕过 :根据猜测,系统性地尝试各种绕过技巧。从最简单的修改扩展名开始,到制作图片马、修改文件头、使用特殊标签。
- 利用与获取Shell :上传成功后,如何访问和执行我们的Webshell?文件被重命名了吗?存储路径是什么?是否需要结合文件包含漏洞?
注意 :在实际操作和CTF中,一个非常重要的习惯是 查看网页源代码 。前端JavaScript校验代码可能直接写在HTML里,这是理解第一关防御机制的最快方式。
3. 实战通关:从151到160关的详细拆解
接下来,我们一关一关地看,我会详细说明每一步的操作、背后的原理以及我踩过的坑。
3.1 第151关:客户端校验的“纸老虎”
这一关是热身。页面有一个上传表单,你尝试上传一个
.php
文件,可能会立刻弹窗提示“文件类型不正确”。
解题步骤与原理:
-
查看源码 :按F12打开开发者工具,查看上传表单附近的HTML和JavaScript代码。你很可能会发现一段类似下面的JS函数:
function checkFile() { var file = document.getElementById("upload").value; if (!file.match(/\.(jpg|png|gif)$/i)) { alert("文件类型不正确!"); return false; } return true; }这段代码在表单提交(
onsubmit)时被调用,它用正则表达式检查文件名是否以.jpg,.png,.gif结尾(/i表示不区分大小写)。如果不是,就弹出警告并阻止提交。 -
绕过方法 :既然校验发生在你的浏览器里(客户端),那么服务器在收到请求前对此一无所知。我们只需要让这个校验函数不生效即可。有两种最直接的方法:
-
方法A:禁用JavaScript
。在浏览器设置中临时禁用JS,然后直接上传
.php文件。简单粗暴。 -
方法B:拦截并修改请求(推荐)
。这是更通用的方法。先正常选择一个
.php文件,但在点击上传前,打开Burp Suite并设置好代理。点击上传后,请求会被Burp拦截。在Burp的Proxy -> Intercept标签页下,你可以看到被拦截的POST请求。其中Content-Disposition部分包含了文件名,如filename=“shell.php”。你 根本不需要修改它 ,因为客户端校验已经通过了(实际上拦截发生在校验之后)。直接点击“Forward”放行,请求就会带着.php文件发送到服务器。服务器端如果没有其他校验,就会成功上传。
-
方法A:禁用JavaScript
。在浏览器设置中临时禁用JS,然后直接上传
-
关键点 :这一关的服务器端通常没有任何过滤,它完全依赖前端。所以绕过前端后,上传的Webshell就能直接访问执行。
实操心得 :不要被前端的弹窗吓住。在Web安全中, 任何仅依赖客户端的防御都是不可信的 。这是一个基本原则。从此关开始,请养成使用Burp Suite拦截和修改HTTP请求的习惯。
3.2 第152关:黑名单的“漏网之鱼”
从这一关开始,防御转移到了服务器端。你上传
.php
文件,后端会返回一个明确的错误,比如“文件类型不允许!”
解题步骤与原理:
-
判断为黑名单
:错误提示“不允许”而非“只允许某几种”,通常暗示是黑名单机制。服务器端有一份列表(如
[‘php’, ‘php5’, ‘php4’, ‘php3’, ‘phtml’, ‘pht’, ‘html’, ‘htm’, ‘jsp’, ‘asp’, ‘aspx’]),上传的文件扩展名若在列表中则被拒绝。 -
尝试常见绕过
:黑名单永远可能不全。我们需要测试哪些“漏网之鱼”服务器既允许,又能被解析成PHP。
-
大小写绕过
:
Php,pHp,PHP。Apache服务器在Windows平台上通常不区分大小写,shell.PHP可能被成功解析。 -
特殊后缀
:
.phtml,.pht。在某些古老的PHP配置中(application/x-httpd-php的注册处理程序),这些后缀也会被当作PHP执行。 -
利用解析特性
:
.php7,.phps,.phpt等,但成功率较低。
-
大小写绕过
:
-
本关实测
:在这一关,尝试上传
shell.php被拒。尝试上传shell.phtml,成功上传并访问执行。说明出题人的黑名单里包含了.php但漏掉了.phtml。
注意事项 :黑名单绕过的成功率高度依赖于目标服务器的配置(操作系统、Web服务器、PHP版本)。在实战中,需要结合信息收集结果进行尝试。例如,知道对方是Windows+IIS,可以尝试
.asp或.asa;知道是Linux+Apache,可以尝试.php5,.php7(如果安装对应版本)。
3.3 第153关:点与空格的“障眼法”
延续黑名单机制,但过滤可能更严格一些。直接上传
.phtml
可能也会被拒绝。
解题步骤与原理:
-
分析过滤逻辑
:开发人员在写黑名单过滤时,常见的操作是获取文件名,然后查找最后一个点号
.的位置,截取点号后的字符串作为扩展名进行比对。这个逻辑有缺陷。 -
利用点号绕过
:如果文件名是
shell.php.(末尾多加一个点),在某些系统(特别是Windows)保存文件时,末尾的点号会被自动去除,最终文件名为shell.php。但过滤代码截取到的扩展名可能是空字符串或php.,从而绕过检查。在Burp中修改文件名为filename=“shell.php.”进行尝试。 -
利用空格绕过
:类似地,在Windows系统中,文件名末尾的空格也会被去除。可以尝试
filename=“shell.php ”(php后有一个空格)。过滤代码截取到的扩展名是php(带空格),不在黑名单中,从而绕过。 -
本关实测
:在这一关,上传
shell.php.(末尾带点)成功绕过过滤。上传后,访问的文件名就是shell.php,Webshell成功执行。
避坑技巧 :在Burp里修改文件名时,务必注意引号是英文引号,且不要破坏整个POST数据包的结构。一个字符的错误都可能导致上传失败。可以先在记事本里写好,再粘贴到Burp中。
3.4 第154关:
::$DATA
的NTFS流把戏
这一关依然针对Windows服务器平台,利用了一个非常经典的特性:NTFS文件流(Alternate Data Streams, ADS)。
解题步骤与原理:
-
NTFS流简介
:在Windows NTFS文件系统中,一个文件可以包含多个“数据流”。主数据流没有名字,而附加的数据流可以有名字。格式为
filename:streamname。例如,test.txt:secret.txt就是一个附加流。对于shell.php::$DATA,::$DATA是表示文件主数据流的一个特殊标识。 -
绕过原理
:当PHP在Windows上处理文件上传时,如果文件名包含
::$DATA,它在保存文件时 可能会 自动去除::$DATA部分。但是,一些简单的过滤函数(如pathinfo($filename, PATHINFO_EXTENSION))在获取扩展名时,可能会将shell.php::$DATA整体作为文件名,从而无法正确提取出php扩展名,导致黑名单检查失效。 -
操作方法
:在Burp中,将上传的文件名修改为
filename=“shell.php::$DATA”。发送请求。 -
结果验证
:如果上传成功,服务器上保存的文件名很可能就是
shell.php。直接访问/upload/shell.php即可。
重要提示 :
::$DATA绕过 仅对Windows服务器有效 。如果靶场环境是Linux,此方法无效。这提醒我们,在实战中,识别服务器操作系统是信息收集的关键一步。
3.5 第155关:双写扩展名的“李代桃僵”
这一关引入了新的过滤方式:
删除敏感字符串
。你上传
shell.php
,服务器可能返回“上传成功”,但文件名变成了
shell.
或者
shell.h
。
解题步骤与原理:
-
理解过滤代码
:服务器端的代码可能类似这样:
$filename = $_FILES[‘file’][‘name’]; $blacklist = array(‘php’, ‘phtml’, ‘pht’); foreach ($blacklist as $item) { $filename = str_replace($item, ‘’, $filename); // 删除所有匹配的黑名单字符串 } // 然后保存文件 -
双写绕过
:
str_replace函数会进行全局替换。如果我们上传的文件名是shell.pphphp,过滤过程如下:-
第一次循环,查找
php,找到中间的php并将其删除,文件名变为shell.pphp。 -
继续在
shell.pphp中查找php,又找到末尾的php,删除后文件名变为shell.p。 -
最终保存的文件名是
shell.p,这显然不是我们想要的。 -
正确的姿势是:
shell.pphphp。过滤过程:删除中间的php后变成shell.phpp。 注意 ,此时剩余的字符恰好组成了一个新的php。但str_replace在一次循环中只对原字符串操作一次。更常见的出题方式是,代码只执行一次str_replace(‘php’, ‘’, $filename)。那么对于shell.pphphp,删除第一个匹配的php(位置2-4的字符)后,得到shell.phpp。这个文件名不再包含php,于是被允许保存。而服务器在解析时,看到.phpp后缀可能不认识,但如果我们能确保最终文件名是.php呢?
-
第一次循环,查找
-
更可靠的双写
:实际上,更常见的利用方式是针对类似
$ext = str_replace(‘php’, ‘’, $ext)的代码,其中$ext是提取出的扩展名。那么我们可以上传filename=“shell.p.phphp”。提取的扩展名是phphp,删除php后剩下hp,最终扩展名变成.hp,这通常不在黑名单。但这种方法不稳定。 -
本关实测与另一种思路
:在这一关,更简单的方法是
利用
.和空格的绕过 。因为删除操作可能只针对扩展名字符串,而不是整个文件名。尝试shell.php.或shell.php(带空格),可能发现删除php后,文件名变成shell.或shell.,而点号或空格后的空扩展名可能被允许。经过测试,shell.php.(点号绕过)在本关有效。
排查技巧 :当遇到“删除”类过滤时,首先尝试上传一个包含明显黑名单词的文件,观察返回的文件名是什么。这能帮你快速反推出服务器的过滤逻辑。例如,上传
test_php_test.php,返回test__test.,你就知道它删除了所有php字符串。
3.6 第156关:文件内容校验与“图片马”
从这一关开始,防守方升级了。服务器不仅检查扩展名,还会检查文件内容。你上传一个纯文本的Webshell,即使扩展名是
.jpg
,也可能被拒绝,提示“文件内容不合法”。
解题步骤与原理:
-
内容校验类型
:常见的有两种:
-
检查文件头(Magic Bytes)
:每个文件类型在文件开头都有特定的字节序列,如图片
JPEG是FF D8 FF E0,PNG是89 50 4E 47。服务器会读取文件前几个字节进行比对。 -
检查PHP标签
:服务器会搜索文件中是否包含
<?php、<?=、<script language=“php”>等标签。
-
检查文件头(Magic Bytes)
:每个文件类型在文件开头都有特定的字节序列,如图片
-
制作图片马(Webshell Image)
:这是最常用的绕过方法。原理是将Webshell代码插入到一张正常图片的末尾。因为图片查看器只读取图片数据部分,忽略后面的额外内容;而PHP解析器会执行整个文件中的PHP代码。
-
准备材料
:一张正常的
jpg或png图片(例如test.jpg),一个Webshell文件(例如shell.php,内容为<?php @eval($_POST[‘cmd’]);?>)。 -
制作方法(Linux/Windows CMD)
:
这样生成的# 使用copy命令(Windows)或cat命令(Linux)进行二进制合并 copy /b test.jpg + shell.php webshell.jpg # 或 cat test.jpg shell.php > webshell.jpgwebshell.jpg,用图片软件打开正常显示,但用文本编辑器拉到末尾能看到PHP代码。
-
准备材料
:一张正常的
-
上传与利用
:将
webshell.jpg上传。如果服务器只检查文件头,那么它会通过校验。上传成功后,我们需要找到方式让服务器以PHP方式解析这个文件。通常有几种情况:-
情况A
:服务器配置错误,导致
.jpg文件也能被PHP解析(极少见)。 -
情况B(更常见)
:题目存在
文件包含漏洞
。例如,存在一个页面
index.php?file=upload/xxxx.jpg,它使用include或require包含了我们上传的图片。当文件被包含时,其中的PHP代码会被执行。这就是经典的“图片马+文件包含”组合拳。 -
情况C
:配合解析漏洞,如
webshell.jpg.php(见后续关卡)。
-
情况A
:服务器配置错误,导致
-
本关实测
:156关通常就是考察图片马的基本制作和上传。上传
webshell.jpg成功,但直接访问该图片URL不会执行代码。你需要结合题目其他部分(如提示、其他页面)寻找文件包含点。
实操心得 :制作图片马时,务必使用二进制模式合并,否则可能破坏图片的文件头导致校验失败。在实战中,如果遇到更严格的内容校验(如GD库二次渲染),普通的图片马会失效,需要更高级的技巧。
3.7 第157关:突破
<?php ?>
标签过滤
这一关在内容校验上更进一步。你上传普通的图片马(末尾加了
<?php @eval($_POST[‘cmd’]);?>
),可能会失败,提示“文件包含非法内容”。
解题步骤与原理:
-
分析过滤
:服务器很可能用
stristr()或preg_match()函数在文件内容中搜索<?php、<?=等字符串。一旦发现,就拒绝上传。 -
使用替代标签
:PHP支持多种标签来嵌入代码,除了标准的
<?php ?>,还有:-
短标签
:
<? ?>或<?=(用于快速输出)。需要服务器开启short_open_tag选项。 -
ASP风格标签
:
<% %>。需要服务器开启asp_tags选项(默认关闭,很少用)。 -
Script标签
:
<script language=“php”> </script>。这是一个长标签,但它是 始终可用 的,只要PHP在运行,无论配置如何,都能识别并执行其中的代码。这是绕过<?php过滤的利器。
-
短标签
:
-
制作新型图片马
:将Webshell代码改为:
然后同样用二进制方式追加到图片末尾。<script language=“php”>@eval($_POST[‘cmd’]);</script> - 上传测试 :使用新的图片马上传,成功绕过内容检查。后续利用方式(文件包含)与156关相同。
注意事项 :
<script language=“php”>标签在PHP7.0.0版本后已被移除。因此,如果目标服务器是PHP7+,此方法失效。但在CTFshow的题目环境中,为了考察此知识点,通常会使用PHP5版本。在实战中,需要先探测PHP版本。
3.8 第158关:巧用
.user.ini
与文件包含
这一关的绕过方式非常巧妙,它利用了PHP的一个配置文件——
.user.ini
。这关可能不再是一个单纯的上传点,而是需要你理解整个应用如何加载文件。
解题步骤与原理:
-
理解
.user.ini:在PHP中,除了主php.ini,每个目录下都可以有一个.user.ini文件,用于定义该目录及其子目录中PHP文件的运行配置。该文件必须被PHP运行在CGI/FastCGI模式,并且开启了user_ini.filename和user_ini.cache_ttl选项(很多默认环境是开启的)。 -
关键指令
auto_prepend_file:我们关注其中一个指令:auto_prepend_file = filename。它的作用是,在该目录下的 所有PHP文件 执行之前,自动包含(include)指定的filename文件。 -
攻击思路
:
-
假设我们找到了一个上传点,但只能上传图片(后缀白名单为
.jpg,.png,.gif),并且有严格的内容校验,无法制作有效的图片马。 -
同时,我们发现网站存在一个文件包含漏洞,可以包含
upload/目录下的文件,但包含图片文件不会执行PHP代码。 -
这时,我们可以尝试上传一个特殊的“图片”文件,其内容为Webshell代码,命名为
shell.jpg。 -
然后,我们再上传一个
.user.ini文件,内容为:auto_prepend_file = shell.jpg -
如果服务器允许上传
.ini文件(有时因为过滤不严可能允许),或者我们可以通过其他方法(如解析漏洞)让.user.ini生效,那么当任何人访问该目录下的任何一个PHP文件时(比如网站首页index.php),都会先自动包含并执行我们上传的shell.jpg中的代码。
-
假设我们找到了一个上传点,但只能上传图片(后缀白名单为
-
本关实战
:158关的环境通常设计为:你可以上传任意文件到某个目录,并且该目录下有一个可访问的PHP文件(比如
index.php)。你需要先上传一个包含Webshell代码的shell.jpg(内容需绕过校验,可能要用<script language=“php”>)。然后上传一个.user.ini文件,内容如上。之后,访问http://target/upload/index.php,你的Webshell就会被执行。
避坑技巧 :
.user.ini文件的作用范围是它所在的目录及其所有子目录。要确保你上传的.user.ini和你想影响的PHP文件在同一个目录下。另外,.user.ini的修改需要一定时间才能生效(取决于user_ini.cache_ttl设置),有时需要等待或触发PHP重新扫描。
3.9 第159关:条件竞争漏洞的“生死时速”
这一关引入了时间维度上的攻击——条件竞争(Race Condition)。你上传一个文件,服务器可能会提示“文件上传成功,但已被安全删除”或类似信息。
解题步骤与原理:
-
漏洞场景
:很多应用的上传逻辑是这样的:
问题在于, 第2步(安全检查)和第3步(移动文件)不是原子操作 。存在一个微小的时间窗口。1. 接收上传文件,生成一个临时随机文件名(如`/tmp/xxxx.tmp`)。 2. 对文件进行安全检查(病毒扫描、内容校验等)。 3. 如果检查通过,将文件移动到最终目录(如`/upload/`),并重命名为用户指定的名字。 4. 如果检查不通过,删除临时文件。 -
攻击原理
:我们上传一个Webshell文件。服务器开始安全检查(比如检查是否包含
<?php)。我们的文件是恶意文件,检查 注定会失败 。在服务器执行“检查-失败-删除”这个流程的同时,我们以极快的速度、高并发地请求访问这个临时文件。 -
利用条件
:如果在这个时间窗口内,我们成功访问了临时文件,而此刻安全检查尚未完成或刚完成但文件还未被删除,那么PHP解释器就会执行该文件中的代码。一旦代码执行,我们可以在服务器上创建一个永久的、不被删除的Webshell文件(例如,用PHP的
file_put_contents函数写入一个新文件)。 -
实战操作
:
-
编写Webshell
:创建一个内容如下的PHP文件
race.php:
这个脚本的作用是,一旦被执行,就在指定目录创建一个真正的Webshell文件。<?php file_put_contents(‘/var/www/html/upload/permanent_shell.php’, ‘<?php @eval($_POST[“cmd”]);?>’); ?> -
准备攻击脚本
:使用Python或Burp的Intruder模块进行并发攻击。
-
Burp Intruder方法
:
-
拦截上传
race.php的请求,发送到Intruder。 - 在Positions标签,清空所有载荷标记。
- 在Payloads标签,选择Payload类型为“Null payloads”。
- 在Options标签,找到“Request Engine”,将线程数(Number of threads)调高,如50或100。将“Number of requests”设置为一个较大的数,比如10000。
- 开始攻击。Intruder会以极高并发度重复发送这个上传请求。
-
拦截上传
-
Python脚本方法(示例)
:
import threading import requests def upload_and_access(): url = “http://target/upload.php” files = {‘file’: (‘race.php’, open(‘race.php’, ‘rb’), ‘image/jpeg’)} try: r = requests.post(url, files=files, timeout=2) # 同时尝试访问可能的临时文件路径,这里需要猜测或从响应中获取 access_url = “http://target/tmp/某个猜测的路径” requests.get(access_url, timeout=1) except: pass threads = [] for i in range(100): t = threading.Thread(target=upload_and_access) threads.append(t) t.start() for t in threads: t.join()
-
Burp Intruder方法
:
-
验证结果
:攻击运行一段时间后,尝试访问
http://target/upload/permanent_shell.php,如果存在并用蚁剑连接成功,则攻击成功。
-
编写Webshell
:创建一个内容如下的PHP文件
- 本关关键 :成功的关键在于高并发,以最大化命中那个短暂的时间窗口。同时,临时文件的路径有时可以预测(如包含时间戳、随机数),有时会在响应包中返回,需要仔细分析。
注意事项 :条件竞争攻击对网络环境和服务器性能敏感。本地靶场环境容易成功,真实网络可能存在延迟。如果失败,尝试增加并发线程数或延长攻击时间。
3.10 第160关:解析漏洞与后缀的“伪装”
这是最后一关,通常需要综合利用解析漏洞。你可能发现,上传纯
.php
文件被禁,上传图片马直接访问也不执行,
.user.ini
可能也行不通。
解题步骤与原理:
-
解析漏洞简介
:解析漏洞是指Web服务器在解析文件时,由于配置或逻辑错误,将本不该执行的文件当作脚本执行。最常见的有:
-
Apache解析漏洞
:古老版本的Apache(1.x, 2.x)在遇到不认识的后缀时,会从右向左尝试解析,直到遇到认识的后缀。例如,文件
shell.php.xxx,Apache不认识.xxx,于是向左看,认识.php,于是将其作为PHP文件执行。但现代Apache默认配置通常已修复此问题。 -
IIS 6.0解析漏洞
:有两个经典漏洞。一是
/test.asp;.jpg会被当作.asp执行;二是/test.asp/任意文件名(目录名是.asp)下的所有文件都会被当作ASP执行。但CTF中较少涉及IIS。 -
Nginx解析漏洞
:在特定配置下,如果PHP的
cgi.fix_pathinfo选项开启(默认就是开启的),Nginx会将/uploadfile/shell.jpg传递给PHP-FPM时,如果访问的URL是/uploadfile/shell.jpg/xxx.php,Nginx会认为xxx.php是路径的一部分,但PHP-FPM会误将shell.jpg当作PHP文件来解析,只要shell.jpg的开头是合法的PHP代码或图片马。即:http://target/shell.jpg/xxx.php会解析shell.jpg。 -
其他畸形解析
:
shell.php.jpg、shell.php.jpeg等。这依赖于服务器配置的解析顺序。
-
Apache解析漏洞
:古老版本的Apache(1.x, 2.x)在遇到不认识的后缀时,会从右向左尝试解析,直到遇到认识的后缀。例如,文件
-
本关实战思路
:对于160关,常见的解法是上传一个名为
shell.php.jpg的文件。服务器的白名单可能只检查最后一个后缀(.jpg),允许上传。但服务器的解析规则(可能是Apache的AddHandler配置错误,或是Nginx的畸形配置)导致它会寻找第一个可识别的后缀。于是shell.php.jpg被当作.php文件解析并执行。 -
操作过程
:
-
制作一个图片马
shell.php.jpg(内容需绕过内容检查,如使用<script language=“php”>)。 - 上传该文件。
-
直接访问上传后的文件链接,如
http://target/upload/shell.php.jpg。如果配置存在解析漏洞,该文件中的PHP代码将被执行。
-
制作一个图片马
-
另一种可能:配合文件包含
:如果直接访问不执行,可能还需要配合文件包含漏洞。例如,存在
index.php?file=upload/shell.php.jpg,通过包含来触发解析。
排查技巧 :当遇到后缀名过滤严格的白名单时(只允许
jpg, png, gif),解析漏洞是最后的希望。需要系统性地尝试各种畸形文件名:test.php.jpg,test.php.jpeg,test.php.png,test.jpg.php,test.php.(末尾多点),并观察服务器的响应和最终访问效果。同时,查看服务器类型(Apache/Nginx)和错误配置的线索至关重要。
4. 工具、脚本与实战心得总结
通关这十关,不仅需要知识,还需要顺手的工具和高效的流程。这里分享我常用的工具链和一些深度的实战心得。
4.1 必备工具链
- Burp Suite Community/Professional :核心中的核心。用于拦截、修改、重放HTTP/HTTPS请求。 Intruder 模块用于爆破、模糊测试和条件竞争攻击。 Repeater 模块用于手动调试和测试单个请求。 Scanner 模块(专业版)可以自动扫描一些常见漏洞。
- 浏览器开发者工具(F12) :用于快速查看前端JS代码、网络请求、Cookie信息,是分析客户端逻辑的第一现场。
-
文本编辑器/十六进制编辑器
:用于制作图片马、修改文件内容。Notepad++、VS Code、
hexedit(Linux)或WinHex(Windows)都很常用。制作图片马时,用copy /b或cat命令更可靠。 - 蚁剑(AntSword)或中国菜刀(Cknife) :Webshell管理工具。一旦上传成功,用于连接和管理服务器。蚁剑功能更强大,支持插件和目录管理。
- Python + Requests库 :用于编写自动化攻击脚本,特别是在条件竞争或需要复杂逻辑遍历时,比Burp的Intruder更灵活。
4.2 文件上传漏洞挖掘与利用的通用流程
结合这十关的经验,我总结出一个在实战中挖掘和利用文件上传漏洞的通用流程,它比CTF更复杂,但思路一致:
- 侦察与枚举 :寻找所有可能的上传点。不仅是明显的“上传头像”功能,还包括“导入数据”、“上传附件”、“富文本编辑器插入图片”、“客服反馈上传”等隐蔽入口。
-
试探过滤规则
:
- 前端 :上传非常规文件,看是否有JS弹窗。禁用JS或抓包修改后再试。
-
扩展名
:系统性地尝试各种后缀:
php, php5, phtml, php., php , Php, .php.jpg, php::$DATA等。观察返回信息。 -
MIME类型
:抓包修改
Content-Type为image/jpeg,image/png,text/plain等。 - 文件内容 :尝试上传纯文本Webshell、图片马、包含特殊标签的图片马。
-
文件头
:在文件开头添加正确的图片幻数(如
GIF89a)。
- 分析响应 :仔细阅读服务器返回的所有信息,包括HTTP状态码、响应头、响应体。错误信息、重定向路径、返回的文件名都可能泄露关键信息。
-
尝试高级绕过
:如果基本绕过失败,考虑:
-
.user.ini或.htaccess:尝试上传这些配置文件来改变服务器行为。 - 条件竞争 :如果上传后文件会消失,尝试高并发访问。
- 解析漏洞 :尝试各种畸形文件名,并探测服务器类型和版本。
-
结合其他漏洞
:如目录穿越(
../../../shell.php)、SQL注入(修改文件名字段)、XSS等。
-
- 获取Shell与后续行动 :上传成功后,确定文件的访问URL。使用Webshell管理工具连接。执行命令,进行内网探测、权限维持、数据窃取等(在合法授权范围内)。
4.3 常见问题与排查技巧实录
在实战和CTF中,你一定会遇到各种奇怪的问题。这里记录一些我踩过的坑和解决方法:
-
问题1:上传成功,但访问返回404或403?
- 排查 :首先确认完整路径。上传功能返回的路径可能是相对路径或经过处理的。查看页面源代码或网络响应,找到文件真正的存储位置和名称。可能是文件名被重命名了(如改为MD5值+后缀)。403错误可能是目录没有执行权限,或者服务器配置了禁止执行上传目录下的脚本。
-
问题2:图片马上传成功,但文件包含时不执行代码?
-
排查
:检查文件包含的姿势。是
include还是require?路径是否正确?更重要的是,检查图片马是否被破坏。有些严格的校验会使用GD库或ImageMagick对图片进行 二次渲染 ,这会重建图片,丢弃所有非图片数据,导致附加的Webshell代码丢失。绕过二次渲染需要更精细的技巧,例如在PNG的IDAT数据块或JPEG的APPn段中插入代码,这需要深入研究图片文件格式。
-
排查
:检查文件包含的姿势。是
-
问题3:条件竞争攻击总是失败?
- 排查 :首先,确保你的并发量足够大(线程数调到50以上)。其次,检查你访问的临时文件路径是否正确。有时路径不是完全随机的,可能包含时间戳或固定的前缀。可以尝试从服务器的错误信息或响应中寻找线索。最后,考虑网络延迟,如果靶场在远程,竞争窗口可能极短,成功率会下降。
-
问题4:
.user.ini文件上传了,但不生效?-
排查
:第一,确认PHP运行在CGI/FastCGI模式(
php_sapi_name()函数可查看)。第二,确认user_ini.filename和user_ini.cache_ttl在php.ini中是开启的。第三,.user.ini必须放在 可执行的PHP文件所在目录或其父目录 。第四,修改后需要等待user_ini.cache_ttl指定的时间(默认300秒)或重启PHP-FPM服务才能生效。在CTF中,出题人通常会确保环境是支持的。
-
排查
:第一,确认PHP运行在CGI/FastCGI模式(
-
问题5:Burp修改请求后上传失败,返回奇怪错误?
-
排查
:最常见的原因是
修改请求时破坏了数据包的格式
。特别是
multipart/form-data的边界(boundary)。在Burp中修改文件名或内容时,不要动Content-Type头里的boundary值,也不要动数据体中分隔符(--boundary)本身。最好在Raw标签下整体查看,确保格式完整。另一个常见问题是引号不匹配或多了空格。
-
排查
:最常见的原因是
修改请求时破坏了数据包的格式
。特别是
文件上传漏洞的攻防是一场永不停歇的猫鼠游戏。CTFshow的这十关,就像一套精心设计的组合拳,帮你建立了从基础到高级的完整知识框架。但真正的实战环境千变万化,需要你灵活运用这些知识,并保持不断学习新技巧的心态。记住核心原则: 永远不要信任客户端输入,服务器的每一个校验环节都可能存在逻辑缺陷 。多动手,多思考,多总结,你就能在文件上传这个经典漏洞领域里游刃有余。

1万+

被折叠的 条评论
为什么被折叠?



