1. CISP-PTE机考里的文件上传,到底在考什么?
1.1 考题形态与判分逻辑
CISP-PTE实操机考里,文件上传经常以"基础题目二:文件上传突破"的形式出现。页面上往往只有一个朴素的上传表单,有的题目会好心提示上传目录,有的完全不给任何线索。机考评分的核心是"能否利用漏洞获得命令执行权限,或读取到指定的敏感文件",而不是"你是否绕过了某一层校验"。换句话说:上传了一个PHP文件但访问不到,或者访问了但服务器根本不把它当脚本执行,都拿不到分。
这一点特别容易被忽略。很多人在练靶场时养成了"上传成功=题做完了"的惯性,到了考试才发现最后一步的"验证执行"才是真正的分水岭。文件上传题从来不是一道"上传题",而是一道"从上传点出发,走通执行链路"的综合题。
1.2 一条完整的利用链路
我习惯把文件上传题拆成四步:定位入口、摸清环境、探测校验、绕过执行。
定位入口通常是首页或后台里的一个上传表单;摸清环境包括确认Web服务器类型与版本、脚本语言、操作系统平台;探测校验是指用无害的文件探明前端和后端分别卡了什么条件;绕过执行则是构造最终的攻击文件并访问验证。
四步里面,前两步最容易被压缩,但恰恰是决定后两步能否成功的关键。Apache和Nginx对同一个文件名的解析结果可能完全不同,Windows和Linux对文件名尾部空格、点号的处理也不同。不先把环境指纹摸清楚,后面所有绕过尝试都是盲猜,考场里时间有限,盲猜的代价很高。
1.3 为什么说它是整套试卷的"分水岭"
文件上传在CISP-PTE试卷里的位置通常在SQL注入之后,难度上属于"原理不难、细节极多"的题型。会的人几分钟就能打通整条链,不会的人可能已经绕过了三层校验,最后卡在路径探测上。
我的判断是:这道题考察的不是某个单一技术点,而是"在受限环境里做工程化决策"的能力。你面对的是一个看不见校验规则的黑盒,需要在有限时间里用最小代价搞清楚规则,然后选择最高成功率的绕过方案。把这个能力练好,后面遇到RCE、文件包含相关题目都会顺很多,因为它们的核心思路是相通的。
2. 漏洞的本质:上传校验为什么总有缝可钻?
2.1 一个上传请求在服务端经历了什么
在讨论绕过之前,先理清正常流程。浏览器用multipart/form-data格式把文件名、Content-Type、文件内容发到服务端;服务端代码拿到请求后,通常会依次做几件事:检查文件扩展名、检查MIME类型、检查文件内容头(魔数)、把文件从临时目录移动到指定存储目录、用原名或重新生成的名字保存、返回路径。
注意一个关键点:校验和存储是两个独立环节,而攻击者控制的是校验环节的"输入"。开发者写校验规则时,通常只写他知道的几种情况,那些他没考虑到的畸形输入,就是绕过点。
2.2 常见校验手段与优先级
| 校验手段 | 判断依据 | 典型绕过思路 | 主要防住谁 |
|---|---|---|---|
| 前端JS校验 | 浏览器里的JS检查扩展名 | 禁用JS、抓包改包、修改源码 | 只会点鼠标上传的人 |
| Content-Type校验 | 请求头的MIME值 | 抓包改成image/jpeg等 | 不抓包的人 |
| 扩展名黑名单 | 拦截常见脚本后缀 | 大小写、空格、点、双写、特殊后缀 | 不了解文件系统特性的人 |
| 扩展名白名单 | 只允许jpg/png/gif | 解析漏洞、%00截断、包含漏洞 | 不了解中间件的人 |
| 文件头校验 | 检查文件开头魔数 | 图片马加GIF89a等前缀 | 不了解文件结构的人 |
| 内容检测 | 检查是否含PHP标签 | 短标签、编码、混淆 | 不了解PHP解析规则的人 |
| 随机重命名 | 存储时生成随机文件名 | 配合文件包含、目录列举 | 不熟悉业务功能的人 |
这张表要反过来用:看到一道题,先在脑子里给它的校验规则"分级",确定它卡在哪几层,再决定用哪一层的绕过思路,而不是上来就一股脑试payload。
2.3 多层校验时的突破顺序
真实考题很少只有一层校验,常见组合是"前端JS + 后端扩展名黑名单 + Content-Type校验",或者"扩展名白名单 + 文件头校验"。组合校验看起来难,其实有个规律:组合里总有一层的判断逻辑是弱的。
比如"前端JS + Content-Type"这种组合,前端拦得再严也只是浏览器层面,抓包一发,后端如果只检查Content-Type,改个值就过了;如果后端检查的是扩展名黑名单,那就收窄思路去试扩展名变体。关键点在于不要被第一层校验吓住,每一层都可以独立绕过,组合校验的难度是加法而不是乘法。
2.4 为什么背Payload没用
我见过太多人把"php、php3、php5、phtml、pht"这些后缀背得滚瓜烂熟,一换环境就不会用了。原因是:同一个后缀在不同服务器版本里的处理方式完全不同,比如.phtml在旧版Apache里可能被当作PHP执行,在新版默认配置里就不会。真正的能力是"知道去测试哪些后缀、怎么判断服务器是否把它当脚本执行了",这依赖于对文件系统和中间件特性的理解。
还有一个常见盲区:操作系统差异。Windows会自动去除文件名末尾的点和空格,Linux不会;Windows文件系统不区分大小写,Linux区分。这些差异靠背是背不出来的,得靠亲手做实验形成手感,这也是我后面建议你刷靶场时不要只看答案的原因。
3. 六类绕过手法逐一拆解:从改包到解析漏洞
3.1 前端绕过:最基础也最容易被无视的一环
前端校验就是用JavaScript在浏览器里检查你选的文件扩展名,常见写法是判断文件名结尾是否以.jpg、.png结束,不是就弹窗报错。绕过方式有三种:一是在浏览器开发者工具里禁用JavaScript后重新上传;二是把文件后缀改成jpg并配合Burp抓包,在请求发出前改回php;三是直接把页面里的JS校验函数删掉或改掉。
前端校验的意义不在于防住谁,而在于"过滤掉不抓包的人"。考试里如果遇到前端校验,千万别高兴太早,很多题是前端校验加后端校验一起上的,前端只是烟雾弹。测试习惯是:永远用Burp或浏览器插件去发送实际请求,而不是相信页面提示。
3.2 Content-Type绕过:改一行头的事
当后端只校验请求头里的Content-Type时,绕过极其简单。用Burp拦截上传请求,把Content-Type从application/octet-stream改成目标服务器可能接受的图片类型,比如image/jpeg、image/png、image/gif,再放行。
这里有个细节:有些题目后端除了校验Content-Type,还会一起校验filename的后缀,此时单纯改Content-Type没用,要文件名也改成jpg,上传成功后再找解析漏洞或包含漏洞来利用。所以判断Content-Type是不是唯一校验条件,是抓包后该做的第一件事。抓包后多看一眼请求头里的字段,比反复试payload高效得多。
3.3 扩展名黑名单绕过:与文件系统特性的一场博弈
黑名单校验是最常见的后端校验,也是最容易出变体的地方。它的思路是:拦截一批脚本后缀(php、asp、jsp等),其余放行。但开发者在列举黑名单时,很难把操作系统和中间件所有能解析的后缀、所有畸形写法都列全,于是衍生出一批经典变体:
- 大小写混淆:
shell.PhP,对大小写敏感的校验规则会漏掉 - 末尾加点:
shell.php.,Windows系统会去除末尾点,保存成shell.php - 末尾空格:
shell.php,Windows自动去除空格 - 双写后缀:
shell.pphphp,某些过滤逻辑只替换一次 - 特殊解析后缀:
shell.php5、shell.phtml、shell.pht,取决于中间件配置 - NTFS ADS流:
shell.php::$DATA,Windows下写文件时把数据流剥离 - 空字节截断:
shell.php%00.jpg,老版本PHP的C语言函数会截断
需要特别强调的是:这些变体不是万能的。.php.在Linux上保存的文件名就是shell.php.,如果Apache配置不解析它,就只是个普通文件。所以使用前先确认题目环境是Windows还是Linux,可以从响应头、报错路径、目录风格判断,再挑对应的变体。
3.4 白名单校验下的思路切换
当后端使用白名单——只允许jpg、png、gif后缀——扩展名变体基本失效,此时要切换思路。方向有三个:
第一是找中间件解析漏洞。Apache的多后缀解析是经典场景:shell.php.jpg传到Apache后,如果配置里有AddHandler处理php,文件会从右往左找能识别的后缀,识别到php后整个文件按PHP解析。Nginx如果开启了cgi.fix_pathinfo,访问/upload/shell.jpg/.php或/upload/shell.jpg%00.php也可能触发解析。
第二是%00截断,前提是PHP版本足够老(5.3.4之前),且上传后的文件路径可控,比如路径由/uploads/拼接文件名,shell.php%00.jpg保存时会截断为shell.php。
第三是文件包含漏洞配合图片马。把一句话木马写进图片文件里,再通过同站点存在的文件包含点去包含它执行。这部分在考试中经常作为组合题出现。如果你发现题目里有个include.php?file=参数,那几乎可以断定这道上传题需要走包含路线。
3.5 图片马:让图片里藏着可执行代码
构造图片马的思路很直接:在图片文件的内容里插入PHP代码。最简单的做法是用文本编辑器在GIF文件头后追加<?php @eval($_POST['x']); ?>;更隐蔽的做法是用exiftool把代码写入图片的EXIF注释字段。很多服务端只检查文件头几个字节的魔数(GIF89a、JPEG的FFD8FF),不检查内容,所以图片马能过文件头校验。
但图片马本身不能执行,它只是把代码藏进了图片。要让代码执行,必须有文件包含漏洞、或中间件把图片当脚本解析的配置、或配合其他漏洞。所以看到"图片马"时,脑子里要立刻浮现一个问题:题目环境提供了解析或包含的通道吗?没有通道,图片马构造得再好看也只是个图片。
3.6 内容检测与二次渲染:一道更硬的关卡
部分题目会检查上传文件的内容,最简单的做法是搜<?php字符串,搜到就拦截。绕过思路有几个层次:用PHP短标签<?=...?>替代<?php;把代码拆开写成无标签形式再拼接;使用大小写或编码混淆。这些能过简单内容检测,但过不了更高级的检测。
二次渲染是另一个常见坑:服务端用GD库重新生成图片,重绘后的图片会丢掉原文件里插入的代码。这时候需要用"渲染前后对比"的思路:先传入一张图片,下载渲染后的文件,对比原图哪些区域的二进制数据没被改写,然后把payload插入到没被改写的区域。这个技巧在upload-labs的某几关里是必练项目,考试里遇到二次渲染的概率不算高,但碰到了会非常卡人,建议提前练熟。
4. 一道"文件上传5"综合题的完整解题链路复盘
4.1 信息收集:先别急着传文件
打开题目页面后,我的习惯是先在浏览器里看三样东西:响应头里的Server和X-Powered-By、URL后缀、页面源码里的隐藏表单字段或注释。这三样能快速告诉我们:服务器是Apache、Nginx还是IIS,脚本语言是PHP、ASP还是JSP,有没有提示上传目录。如果页面没给任何信息,我会顺手做一次轻量目录列举,看看有没有/uploads/、/upload/这类常见路径。
请不要跳过这一步直接传文件。后面所有绕过方案都建立在"确认中间件和语言"之上,环境判断错了,后面可能浪费半小时在无效payload上。
4.2 探测校验规则:用控制变量法摸清边界
探测阶段的目标是搞清楚"服务器到底校验了什么"。我的做法是依次上传几个文件,每次只改一个变量:
- 先传一个正常jpg,看是否成功
- 再把jpg改名成php上传,看报错信息
- 再把php文件配合改Content-Type上传,看是否被拦
- 最后在php文件内容开头加上GIF89a,看是否通过
通过对比这几次返回结果的差异,就能画出校验规则的大致形状。
这一步里,报错信息是最值钱的情报。比如返回"文件类型不正确"通常指向Content-Type校验,返回"不允许上传该类型文件"大概率是扩展名黑名单,返回"文件内容非法"则说明有内容检测。考试环境一般不会关闭报错,善于读报错的人等于拿到了半个答案。
4.3 构造绕过组合:从单点突破到连环组合
假设探测后发现是"黑名单 + Content-Type + 文件头"三重校验,那绕过组合可以是:文件名用shell.phtml(黑名单可能没拦住这个后缀)、Content-Type改成image/gif、文件内容头部加上GIF89a。三重校验各自对应一层绕过,逐一击破,这比单纯试一个变体稳健得多。
另一种常见组合是"白名单 + 文件包含"。探测时发现jpg能上传成功,但上传后的路径在一个疑似可控的地方,或者旁边有一个include.php参数。此时不需要硬碰白名单,直接把PHP代码写进图片(图片马),然后访问include.php?file=上传的图片路径,代码就会被包含执行。这类题目考的就是"能不能识别出组合关系"。
4.4 最后一公里:路径探测与执行验证
上传成功只是前半段,后半段经常卡死人。首先,文件到底存在哪个目录?页面可能会回显上传后的访问路径,也可能完全不给;给不了的,就要回到信息收集阶段去看是否有目录列举、是否有源码提示、是否可以通过访问常见路径猜测。其次,上传后的文件名是否被改变?如果题目做了随机重命名,那shell.php可能变成了20240612xxxxxxxx.jpg之类的名字,你需要在响应包或页面上找到新文件名。
最后才是执行验证。我个人推荐用浏览器直接访问上传脚本的URL,并带上参数如?cmd=id来验证命令执行是否正常。很多选手习惯直接上蚁剑连接,一旦连接失败就去改木马,其实先用浏览器验证能更快定位问题——是路径不对、语法不对,还是服务器根本不解析这个后缀,一测便知。
4.5 这道题最容易踩的三个坑
第一个坑:花费大量时间在扩展名变体上,却忽略了题面早就提示了"上传目录在/var/www/html/upload",没有目录扫描习惯的人会卡死在路径这一环。第二个坑:图片马构造成功后没有找到包含点,传了个寂寞,说明信息收集阶段漏了业务功能分析。第三个坑:命令执行后权限不足,无法直接读目标文件,考试环境里经常需要读固定路径的敏感文件,比如/tmp/360/key,这时候先试试cat、find这类常见命令,而不是一上来就想反弹shell或者提权,先把该拿的分拿到。
5. 五个考场翻车点与习惯养成
5.1 翻车点一:只绕过了前端,后端根本没碰
页面提示"文件格式不正确",于是禁用JS、改包重传,发现上传接口返回了成功,就以为万事大吉。结果访问脚本URL返回404或直接变成下载。原因很简单:前端只是第一道防线,后端校验根本没被绕过。我的建议是:每次"绕过"之后,一定要完整走一遍访问验证流程,确认服务器真的把脚本解析执行了,而不是只看上传接口返回"成功"两个字。
5.2 翻车点二:上传路径不可预测
有些题目上传后会随机重命名,或者存储目录是动态生成的日期目录。你上传的shell.php被保存为d41d8cd98f00b204e9800998ecf8427e.php,没有响应包回显的话,根本猜不到。解法有几种:优先看响应包和页面回显;没有回显就尝试常见目录加原文件名;再不行看是否存在目录列举漏洞;实在不行,回到"信息收集"阶段,检查是否有源码泄露或备份文件可读。路径探测不是碰运气,是有固定套路的,平时练靶场时就要养成"上传后主动找路径"的习惯。
5.3 翻车点三:木马语法与连接工具不匹配
常见一句话木马有很多种写法,对应的连接密码和数据格式也各不相同。比如<?php @eval($_POST['x']); ?>用蚁剑连接时,连接密码填x;<?php @assert($_POST['x']); ?>在某些PHP版本下会因为语法差异而连接失败。习惯建议是:优先使用最通用、最不容易出问题的一句话,上传后用浏览器或命令行先验证文件可访问、可执行,再交给连接工具,不要一上来就开蚁剑。
5.4 翻车点四:忽略中间件解析特性
同样是shell.jpg,在Apache多后缀解析配置下,shell.php.jpg能被当作PHP执行;在Nginx的cgi.fix_pathinfo开启时,/upload/shell.jpg/.php能触发解析;在IIS 6.0下,shell.asp;.jpg被当作ASP执行。这些特性都依赖具体中间件版本和配置。备考时不要只学手法,要把每个手法绑定到"哪个中间件、哪个版本、什么配置"上,考场里先定环境再选手法。
5.5 养成的四步习惯
即使不说考试,这个习惯在真实授权渗透里也很重要:信息收集、探测校验、构造绕过、验证执行。每一步都要留下证据(截图、请求包、返回包),万一最终失败还能回头排查是哪一步的判断错了。很多人翻车是因为第二步和第四步之间来回跳,上传失败马上换payload,换了几十个还是失败,却从没回头看看是不是环境判断错了。按四步走,每一步都做扎实,哪怕最后没打通,你也知道问题出在哪,而不是一头雾水。
6. 备考建议:在upload-labs靶场把时间花在刀刃上
6.1 靶场选择与练习顺序
CISP-PTE备考阶段,我推荐以upload-labs为主,它覆盖了从基础到高阶的大部分上传绕过场景,而且每一关都对应一种典型校验思路。如果你时间有限,可以按照这个顺序练:
- 前5关快速过,熟悉前端、Content-Type、黑名单基础
- 6到10关重点练各种扩展名变体,理解文件系统和系统特性
- 11到15关练习白名单校验下的截断与解析漏洞思路
- 16到19关练图片马和二次渲染
每关做完问自己三个问题:这关校验了什么?绕过的关键点在哪?如果我遇到同样校验但环境变了,我怎么调整?这三个问题能把你从"背答案"的模式拉出来,进入"理解原理"的模式。
6.2 练习方法:记录"为什么"而不是"答案"
很多人练upload-labs的方式是"卡住了去搜答案,记住文件名就过了",这样的练习在考试时基本白费。更好的方式是为每一关写一笔笔记,记录四件事:校验规则是什么、我试了哪些变体、哪一步成功了、成功背后的原理是什么。
举个例子,第5关是扩展名黑名单,你试了.php不行,换成.php5可以,那么笔记里要写清".php5能被Apache解析"这个判断依据,而不是只写"这一关用php5"。有了这样的笔记,考试时遇到同类校验,你脑子里浮现的是"它的规则可能是这样,所以我该试哪几类变体",而不是"当时我用的是哪个后缀来着"。
6.3 考前一晚的检查清单
工具方面:确保Burp Suite能正常拦截浏览器的HTTP和HTTPS流量,证书装好,避免考试时抓不到包;准备一个常用的文件名变体字典文件,省得现场手敲;确认蚁剑或菜刀的编码设置与木马匹配。知识方面:把上传漏洞的校验类型、典型绕过、解析漏洞的中间件列表快速过一遍,像背单词一样过,不用深究。心态方面:记住"不是一次成功就代表会了,也不是失败一次就代表不会",卡住时回到信息收集里找线索,比盲目试payload有效得多。
我在备考和给学员讲题的过程中最大的感受是:文件上传这道题,练到最后比的不是"你知道多少种绕过姿势",而是"你能不能快速判断当前环境吃哪一套"。同样一个.user.ini或.htaccess,在Apache上和在Nginx上完全是两个结果;同样一个shell.php.,Windows和Linux的行为也不一样。所以每次拿到题目,我第一件事永远是看响应头、看报错、看页面结构,把这些环境指纹刻进脑子里,后面所有绕过的成功率都会高一大截。
最后再分享一个小技巧:上传成功后,先用curl或浏览器直接访问上传文件的URL,确认它真的存在而且返回的是可执行内容,再连一句话木马。这一条习惯能帮你在考场上省下至少十分钟的无效操作,也能让你在复盘时清楚地知道——到底是上传环节出了问题,还是执行环节出了问题。