公司动态
文件包含漏洞实战:从原理到绕过技巧的深度剖析
1. 项目概述一次关于文件包含漏洞的深度实战剖析最近在复盘一些经典的Web安全挑战特别是攻防世界CTF平台上那道名为“file_include”的题目感触颇深。这道题虽然名字直白但其中蕴含的绕过技巧和对文件包含漏洞原理的考察非常贴近真实渗透测试中可能遇到的场景。文件包含漏洞无论是本地包含LFI还是远程包含RFI都是Web安全领域的老牌高危漏洞在OWASP Top 10的历史榜单中也曾多次出现。它不像SQL注入那样有各种自动化工具满天飞更多时候需要测试者对服务器环境、代码逻辑有深刻的理解才能巧妙地利用它。这次我就以这道题为引子结合我这些年踩过的坑和积累的经验带你从漏洞原理的底层逻辑开始一步步拆解常见的过滤机制并分享几种实用的绕过技巧。无论你是刚入门安全的新手还是想深化Web漏洞理解的老兵相信这篇从实战出发的总结都能给你带来一些新的启发。2. 文件包含漏洞的核心原理与危险本质要谈绕过必须先理解其原理。文件包含漏洞的根源在于应用程序在动态包含文件时未对用户输入的文件名或路径进行充分验证。在PHP中这通常涉及include、require、include_once、require_once这四个函数。2.1 动态包含机制是如何被滥用的开发者本意是好的希望通过一个变量来灵活加载不同的页面模块比如include($_GET[page] . .php);期望用户访问?pagehome来加载home.php。问题就出在这个$_GET[page]完全由用户控制。攻击者可以尝试传入../../etc/passwd这样的路径。如果服务器配置不当如open_basedir限制不严或未开启且PHP以足够权限运行就有可能读取到系统的敏感文件。这不仅仅是读取文件那么简单。在特定条件下本地文件包含LFI可以进一步转化为远程代码执行RCE。一个经典的场景是结合文件上传功能或利用服务器日志。例如如果我能控制一部分内容被写入服务器某个文件如访问日志access.log中的User-Agent字段再通过LFI去包含这个日志文件那么我写入的PHP代码就会被服务器解析执行。另一种情况是PHP的某些封装协议如php://input允许我直接通过POST请求体提交PHP代码并被include函数执行。注意allow_url_include这个PHP配置选项直接决定了是否允许包含远程URL即RFI。在早期版本或配置不当的环境中设置为On是极其危险的。现代PHP版本默认通常为Off这使得纯粹的RFI较少见但LFI及其向RCE的转化更为常见。2.2 漏洞的常见触发场景与影响范围文件包含漏洞的影响是链式的、升级的。其直接危害是敏感信息泄露包括源代码、配置文件、数据库凭证、系统用户列表等。更深层的危害则是作为跳板实现远程代码执行从而完全控制Web服务器。在实战中你可能会在以下地方发现它的踪迹模板加载功能许多CMS或框架使用一个中心控制器通过参数调用不同模板。语言包/本地化文件加载通过参数切换语言如?langen_us。文件下载或查看功能看似安全的文件查看参数中可能直接包含了文件路径。插件或模块调用动态加载插件或功能模块的代码。理解这些场景能帮助你在黑盒测试时更快地定位可能的注入点。通常任何看起来像是“加载了另一个页面内容”的参数都值得用../../这样的路径遍历字符串测试一下。3. 攻防世界file_include题目深度拆解回到攻防世界这道题它通常不会直接给你一个毫无防护的include函数。出题人往往会设置一些障碍模拟真实环境中开发人员已意识到风险并尝试修补但修补不完整的情况。这正是这道题的价值所在——它考察的是绕过技巧。3.1 题目环境与初步探测假设我们拿到的题目入口是一个简单的页面有一个名为file的GET参数。首先我们要进行最基础的探测基础路径遍历测试尝试?file../../../../etc/passwd。如果直接返回了/etc/passwd的内容那说明漏洞存在且没有任何过滤但这在CTF或稍具规模的真实系统中几乎不可能。探针文件包含尝试包含Web目录下的已知文件如?fileindex.php。观察响应是文件内容被显示还是被解析执行。如果显示源码可能意味着目标在包含时未使用PHP标签或者被以文本形式读取这本身也是一种信息泄露。协议封装探测尝试使用PHP内置的过滤器如?filephp://filter/convert.base64-encode/resourceindex.php。这个技巧非常关键它利用php://filter这个流包装器在文件被包含“前”先对其进行处理这里是base64编码。这样即使包含后的输出不直接显示源码我们也能拿到经过base64编码的源代码解码即可。这是绕过“代码不直接回显”场景的利器。在攻防世界的这道题中初步测试可能会发现直接包含/etc/passwd被拦截了或者返回了空白/错误。而使用php://filter读取index.php也可能被某种方式过滤。这提示我们存在一个过滤机制。3.2 关键过滤机制base64过滤的识别与挑战根据网络资料提示这道题的一个关键点是“base64过滤机制”。这听起来有点矛盾因为刚才我们还在用base64编码来读取文件。这里的“过滤”可能指以下几种情况黑名单关键字过滤服务器端代码可能检查参数中是否包含base64这个字符串如果包含则拒绝请求或清空参数。例如if (strpos($_GET[file], base64) ! false) { die(Hacker!); }。对php://filter协议的过滤可能直接过滤php://或filter关键字。对编码后字符串的检测虽然少见但理论上可以检测参数是否看起来像base64编码的字符串仅包含特定字符集长度是4的倍数等。我们的任务就是找出过滤的逻辑并绕过它。这需要系统地测试和推理。4. 绕过过滤的实战技巧与思维模型面对过滤我们不能瞎试需要建立一套有条理的绕过思维模型。以下技巧不仅适用于这道题也适用于大多数Web漏洞的绕过场景。4.1 大小写与双写绕过这是最基础的一招针对简单的字符串匹配如stristr或preg_match的默认大小写敏感模式。测试?filePHP://Filter/convert.base64-encode/resourceindex.php原理如果过滤逻辑是strpos($file, php://)那么大小写变体就能绕过。如果过滤使用了str_ireplace(php://, , $file)这种删除操作则可以尝试双写?filephphp://p://filter/...当中间的php://被删除后剩下的字符会拼接成新的php://。4.2 利用编码与超长URL当直接的关键字匹配失效时我们可以对攻击载荷进行编码。URL编码将敏感字符进行URL编码。例如:编码为%3a/编码为%2f。那么php://filter可以变为php%3a%2f%2ffilter。服务器在获取$_GET[file]时通常会自动解码一次如果过滤发生在解码之后则可能绕过。多重编码如果过滤逻辑在解码后执行我们可以对已经编码的字符串再进行一次编码如%编码为%25变成php%253a%252f%252ffilter。这有时能绕过一些简单的解码后过滤逻辑。超长URL截断这是一个较老的技巧依赖于PHP版本和配置。原理是利用PHP处理字符串时的内部长度限制或缓冲区问题。例如在PHP 5.3.4之前的版本可以使用长字符串加/.或?来进行截断使得后面的过滤代码不被执行。但在现代环境中此方法基本已失效了解即可。4.3 协议封装器的花式用法php://filter只是PHP众多流包装器中的一个。当它被过滤时我们可以考虑其他协议。data://协议这是另一个强大的协议允许直接内嵌数据。用法如?filedata://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8其中base64部分为。这可以直接执行代码。但它的使用条件更苛刻allow_url_include必须为On且data://协议本身可能被过滤。zip://或phar://协议这两个协议常用于反序列化漏洞但在文件包含中也能发挥作用。你可以将一个包含恶意代码的PHP文件压缩成ZIP然后通过?filezip:///path/to/evil.zip%23shell.php来包含其中的文件。%23是#的URL编码用于指定压缩包内的文件。这绕过了对.php后缀的检查因为包含的是压缩包路径。phar://类似但用于PHAR归档格式。4.4 路径遍历与目录跳转的变体如果目标是读取特定系统文件而过滤了../可以尝试以下变体绝对路径直接使用/etc/passwdLinux或C:\Windows\System32\drivers\etc\hostsWindows。编码的路径遍历符..%2f(../),..%5c(..\用于Windows)。非标准表示在某些上下文中....//或..;/可能被错误地解析为../。4.5 针对攻防世界题目的综合绕过策略猜想结合“base64过滤”这个提示题目的过滤很可能直接针对base64这个关键字。那么我们的绕过思路可以沿着以下路径展开第一步确认过滤点。先提交?filephp://filter/convert.base64-encode/resourceindex.php预期被拦截。然后提交?filephp://filter/convert.xxxx-encode/resourceindex.php将base64替换为任意字符如果通过则证实过滤的是“base64”字符串本身。第二步尝试大小写和双写。使用BaSe64、BASE64、basse64双写s等进行测试。第三步尝试不使用base64。既然php://filter的convert.base64-encode过滤器被禁我们是否可以使用其他过滤器php://filter支持多种过滤器如string.rot13、string.toupper、string.tolower、convert.iconv.*字符集转换。虽然这些不能像base64那样完美地避免源码被解析但有时也能读出部分信息。例如?filephp://filter/readstring.rot13/resourceindex.php会将源码进行ROT13编码后输出我们拿到后再解码即可。第四步终极技巧——嵌套过滤器。这是本题可能的核心考点。php://filter支持过滤器链用|管道符连接。我们可以构造一个链让base64这个字符串在最终 payload 中“消失”或“变形”。例如思路A先转换字符集让“base64”变形。我们可以使用convert.iconv.UTF-8.UCS-2LE过滤器它将UTF-8字符串转换为2字节小端序的UCS-2编码。经过这个过滤器处理后“base64”这个字符串在字节流层面已经变了样简单的字符串匹配就失效了。然后我们再使用convert.base64-encode过滤器。构造的payload可能形如php://filter/convert.iconv.UTF-8.UCS-2LE|convert.base64-encode/resourceindex.php。这里的关键是服务器端代码在执行include时会先应用整个过滤器链。如果过滤代码是在应用过滤器“之前”检查参数字符串那么它看到的仍然是原始的base64字样会被拦截。但如果过滤代码是在应用了某个过滤器比如一个无害的过滤器之后才检查或者过滤逻辑存在缺陷那么这种嵌套就可能绕过。思路B利用多重编码。虽然直接URL编码可能被解码后过滤但在过滤器链中我们可以先使用convert.base64-decode过滤器。我们提交的payload里不直接写base64而是写它的base64编码形式YmFzZTY0。然后构造过滤器链php://filter/convert.base64-decode|convert.base64-encode/...。第一个过滤器会将YmFzZTY0解码回base64然后第二个过滤器再进行编码。这同样需要过滤逻辑的时机配合。在实际解题中可能需要结合Burp Suite的Intruder模块对php://filter/后面的过滤器字符串进行系统的模糊测试Fuzzing尝试各种组合和编码观察服务器的响应差异。5. 从CTF到实战文件包含漏洞的挖掘与利用框架CTF题目是理想化的沙箱而真实环境更复杂。下面我将分享一套在实战中挖掘和利用文件包含漏洞的方法论。5.1 漏洞挖掘手动与工具的结合信息收集首先识别目标使用的技术栈PHP、JSP、ASP.NET等。文件包含漏洞在PHP中最常见但其他语言也有类似机制如JSP的jsp:include、ASP的!--#include file--。使用Wappalyzer、WhatWeb等工具。参数枚举使用爬虫如Burp Suite的爬虫、OWASP ZAP或被动扫描收集所有可能的输入点。重点关注以下参数名file,page,path,load,lang,template,mod,inc。手动测试对每个可疑参数按以下顺序测试基础LFI测试../../../../etc/passwd,....//....//....//....//etc/passwd,C:\Windows\win.ini。协议封装测试php://filter/convert.base64-encode/resourceindex.php,data://text/plain,,expect://whoami需expect扩展。日志文件包含测试尝试包含../../../../var/log/apache2/access.log并污染User-Agent为再包含一次。Session文件包含如果知道Session IDPHPSESSID可以尝试包含Session文件路径如/tmp/sess_[sessionid]并在Session中注入代码。工具辅助使用如FFuFFuzz Faster U Fool这类模糊测试工具搭配强大的字典如SecLists中的LFI字典对参数进行快速批量测试。5.2 漏洞利用从信息泄露到命令执行发现漏洞后利用的深度取决于环境和配置。利用场景所需条件利用方法目标敏感信息读取路径遍历有效目标文件可读直接包含/etc/passwd,/proc/self/environ, 配置文件(config.php,.env), 源代码(*.php)获取系统信息、数据库密码、源码逻辑利用PHP流包装器allow_url_includeOn(RFI) 或php:///data://可用?filedata://text/plain,或?filephp://input[POST DATA: ]直接执行任意PHP代码利用日志文件可预测日志路径有写入权限通过HTTP请求1. 修改User-Agent为PHP代码。2. 包含Apache/Nginx访问日志文件。在无法直接上传文件时获得RCE利用/proc文件系统Linux系统/proc可访问包含/proc/self/fd/[FD_NUM](文件描述符) 或写入/proc/self/environ后包含有时能读取进程信息或实现写入利用临时文件能触发生成临时文件如上传竞争条件攻击在上传的瞬间包含临时文件难度较高需要精确计时利用压缩包协议能上传ZIP文件知道绝对路径上传含PHP文件的ZIP通过zip://包含绕过文件上传后缀检查5.3 高级绕过针对WAF和自定义过滤的思考真实环境往往部署了WAFWeb应用防火墙或开发者编写了更复杂的过滤逻辑。语义分析绕过一些高级WAF会解析参数值判断其是否构成有效的路径遍历。它们可能能识别编码后的../。此时可以尝试非标准编码..%255c双重URL编码、..%c0%afUTF-8超长编码在特定解析器下可能被解释为/。从右向左字符RLO在文件名中插入Unicode控制字符欺骗显示和部分解析逻辑较少见。上下文混淆如果过滤函数在替换或删除恶意字符串后会进行“再检查”那么可以利用这种“再检查”的漏洞。例如过滤代码是$file str_replace(../, , $file);那么输入....//会被替换成../从而成功绕过。这就是“双写绕过”的原理。利用解析差异服务器端代码、中间件如Apache的mod_rewrite、后端语言PHP对URL的解析可能存在细微差异。例如?fileindex.php%00的空字节截断在PHP旧版本中有效因为include()会认为字符串在%00处结束。注PHP 5.3.4后已修复。分块传输编码Chunked Encoding在HTTP请求中利用分块传输将攻击载荷拆分可能绕过一些基于正则匹配的WAF。6. 防御之道开发与运维的双重加固作为防守方彻底杜绝文件包含漏洞需要从开发和运维两个层面入手。6.1 开发层面白名单与硬编码绝对不要信任用户输入这是铁律。针对文件包含最佳实践是使用白名单机制这是最有效的方法。定义一个允许包含的文件名数组用户输入只能从这个数组中选取。$allowed_pages [home.php, about.php, contact.php]; $page $_GET[page]; if (in_array($page, $allowed_pages)) { include(./templates/ . $page); } else { include(./templates/error.php); }避免动态包含如果业务逻辑允许尽量使用静态包含或路由机制。现代MVC框架基本都通过路由控制器来加载视图避免了直接的文件包含。严格限制包含路径使用basename()函数获取文件名去除任何目录路径。为包含的文件添加固定的后缀如include($_GET[module] . .inc.php);。使用绝对路径并确保路径在Web根目录下的特定子目录内。禁用危险函数如必须在极少数需要动态包含的场景应对输入进行严格过滤。但过滤是最后的手段容易有遗漏。6.2 运维与配置层面最小权限原则PHP配置open_basedir将其设置为Web应用所在的目录限制PHP可访问的文件系统范围。这是防止LFI读取系统文件的关键防线。allow_url_fopen与allow_url_include在生产环境中务必将allow_url_include设置为Off。allow_url_fopen也建议关闭除非业务明确需要。disable_functions在php.ini中可以通过disable_functions指令禁用高危函数如system,exec,passthru,shell_exec等。即使攻击者通过LFI实现了RCE也无法执行系统命令极大地增加了利用难度。Web服务器配置配置Web服务器如Apache、Nginx的运行用户为低权限账户并严格控制其文件系统访问权限。日志文件安全将Web服务器日志、应用日志存放在Web根目录之外并设置适当的权限防止被包含。定期更新与安全扫描保持PHP、Web服务器及所有应用组件的最新版本。使用静态代码分析工具SAST和动态应用安全测试工具DAST定期进行安全扫描。文件包含漏洞的攻防是一场关于“信任边界”和“输入解析”的持久战。攻防世界的题目是一个绝佳的练兵场它把复杂的现实对抗浓缩成一个可解的谜题。通过深入理解其原理系统性地练习绕过技巧并最终将这些知识融入主动防御的实践中我们才能在这条路上走得更稳、更远。真正的安全能力就体现在对这些基础漏洞深刻而灵活的理解之中。