ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

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

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那样是登录表单而是类似?urlxxxx的传参方式。这种页面结构在CTF里非常常见十有八九是后端拿这个参数去做文件操作。我第一反应是直接传flag.php试试结果页面什么都没有也不报错。这时候有两种可能一是include进去了但文件本身没有输出内容二是参数被过滤后include失败同时错误信息被屏蔽了。遇到这种情况不能瞎猜一定要先把源码读出来。对文件包含题首选是php://filter伪协议读取目标文件内容并做base64编码因为直接include一个PHP文件时PHP解释器会把它当代码执行我们拿到的是执行结果而不是源码。加上base64编码后文件内容变成一串纯字符串就能原样显示出来。构造payload?urlphp://filter/readconvert.base64-encode/resourceflag.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/readconvert.base64-encode/resource目标文件拆开看这行readconvert.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在解析?urlxxx时会把%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形式是这样?urlphp://filter/readconvert.base64-encode/resourcefl%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_xxxPHP会话文件session内容可控新手听到“文件包含”往往只想到读源码、执行远程代码一遇到过滤就死磕payload变形。实际上“换一个文件来源”往往比“突破过滤规则”更快、更稳。这就是web4想要传递的核心思路。3.3 一句话木马的基础写法日志注入的关键载荷就是一句话木马。新手看到?php eval($_POST[a]);?这种写法会发怵其实原理很直白eval()函数把一段字符串当作PHP代码执行只要把POST参数a的内容传进去就相当于让服务器执行我们提交的任意PHP代码。不过在日志注入场景下往日志里塞POST参数比较麻烦更常见的是写一个GET型的一句话?php system($_GET[cmd]); ?意思是接收GET参数cmd把它交给system()函数去执行。配合文件包含加载日志文件访问时带上cmdcat /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.logcmdcat /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.1include这段日志时PHP从?php开始解析到?结束前后没有多余字符干扰实测非常稳定。给新手一个建议日志注入时优先把木马放在请求路径里比放在UA里省去很多语法排错的麻烦。4. 题目串联从绕过过滤到寻找利用入口的思维升级4.1 web3与web4的核心差异对比把两道题放在一起看很像同一个题目的两种晋级形态考察完全不同的能力对比项web3web4过滤方式单个关键字flag多关键字黑名单利用思路在“被检查的字符串”和“实际使用的文件”之间找缝隙放弃对抗过滤直接寻找新的可控文件源核心知识点URL编码、伪协议、源码审计日志注入、文件包含、一句话木马难度侧重对代码执行顺序的理解对服务端日志结构和请求可控点的掌握新手最常犯的错误就是做完web3之后面对web4不停地搜索“更多绕过payload”方向越走越偏。web4想传递的思想很明确过滤规则再严也没有办法过滤掉所有“文件来源”。只要include一个我们能写入内容的文件就能完成从“读文件”到“执行代码”的跨越。4.2 文件包含利用链的完整图谱把文件包含常见的利用方式整理一下后面做题时可以对照查找读取源码php://filter/readconvert.base64-encode/resource文件路径伪协议直接执行php://input结合POST数据data://text/plain;base64,xxx日志注入包含/var/log/nginx/access.logUA或请求路径写木马环境变量注入包含/proc/self/environUA写木马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这道题笔记里记了现象?urlflag.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 本地环境复现是进步最快的路径强烈建议在本地搭一套PHPNginx环境把这两道题复现一遍。不用多复杂一个docker-compose文件就能搞定。本地复现的好处在于你可以随意修改过滤规则测试不同场景下的绕过方式也可以打开PHP错误显示看到具体的语法错误信息这对理解日志注入的原理帮助极大。我在做web4的时候本地复现了至少五次日志注入每次都开着错误日志看PHP到底在哪一行报错慢慢就理解了为什么木马要那样变形、为什么注释符号能解决语法冲突。5.5 学习节奏别被焦虑绑架最后说点心态上的事。CTF新手最容易焦虑看到别人一天刷十个题就着急。但实际上一天能吃透两道题、理解清楚背后的原理远比一天刷十道题、第二天忘光更有价值。web3和web4做完之后我额外花了一天时间把文件包含的伪协议方式全部试了一遍把日志注入的变形方式归纳了一遍后面再遇到文件包含题就明显有底气了。我个人实际做下来的体会是Day4最大的收获不是两道题的flag而是“过滤不是铁板一块”这个概念。任何过滤规则都有边界攻击的重点不是找到一个绕过字符串而是找到这个字符串所依附的那条链路。web3让我学会读源码、理解伪协议、亲手验证URL编码的边界web4则逼着我跳出“绕过过滤”的惯性思维去思考服务器上还有哪些文件是我能写入、又恰好能被include读取的。这两个思路比记一百个payload都管用。如果你也在刷ctfshow的web入门系列建议把web3和web4连着做掉中间不要休息。先别动手搜writeup每道题至少自己折腾一个小时卡住时再回头看自己的笔记找线索。使用这些技巧请务必注意边界只在自己搭建的实验环境、本地靶场或平台明确授权的CTF比赛中使用。学安全不是为了破坏而是为了真正建立起防御意识——理解了攻击是怎么发生的防护才可能做得更靠谱。希望这篇笔记能帮你少走点弯路。
返回列表