一.什么是文件包含漏洞
文件包含漏洞 ≈ PHP 专属漏洞; 文件包含这个名词,在渗透测试 / 代码审计领域,就是为 PHP 量身定做的include 等文件包含函数可以包含任意后缀的文件(.txt/.png/.jpg 等),只要文件内容里有 PHP 代码,PHP 解释器就会把它当作 PHP 代码执行。
原理:include 是先读取文件内容,再交给 PHP 解析器执行,不关心文件后缀名。PHP 的内核是用 C 语言编写的,include() 等文件包含函数的底层实现也是 C 语言。它的核心逻辑是以「文件流」的方式读取目标文件内容,而不是基于文件后缀来判断如何处理。
PHP 在执行 include() 时,完全不关心文件的后缀名(.txt/.png/.php 都一样),只读取文件的原始字节流。
· 文件流 + <?php 开始标识符:读取到文件流后,PHP 解释器会扫描内容,一旦遇到 <?php 这个起始标识符,就会把后续内容当作 PHP 代码解析执行;遇到 ?> 结束标识符后,再切换回普通文本输出模式。
举例:如果1.txt文件里有 <?php phpinfo();?>类似的php代码内容,当 include('1.txt') 被执行时,这段代码会被正常解析并输出 phpinfo 信息。
触发条件:这个行为的前提是 include() 的参数可控,也就是存在文件包含漏洞。如果参数不可控,就无法随意包含任意文件。
攻击者可以上传一个包含 PHP 代码的图片文件(如 shell.png),再通过文件包含漏洞执行该文件,从而获取服务器权限。
例如:
<?phpfile = _GET['file'];include($file);?>
攻击者上传 1.txt,内容为 <?php phpinfo();?>。
构造 URL:http://example.com/vuln.php?file=1.txt。
服务器执行 include('1.txt'),1.txt 里的 PHP 代码被解析执行,成功输出 phpinfo 信息。
二.什么是PHP伪协议
PHP全称 为Hypertext Preprocessor,超文本预处理器,是一款开源的服务器端脚本语言,
而PHP 伪协议就是 PHP 内置的特殊协议,可配合 file_get_contents() 、 include/require 等函数,实现本地文件读取、数据流处理、压缩包解析等操作,其可用性受 php.ini 配置( allow_url_fopen 、 allow_url_include )影响。
一、 常用伪协议分类及用法
1. file://
file:// 读取本地文件系统内容 无需特殊配置 file_get_contents("file:///etc/passwd"); file:// 协议完全不依赖 allow_url_fopen 和 allow_url_include 这两个 PHP 配置,无论它们是 On 还是 Off,都可以正常使用 。
file:// 是 PHP 本地文件读取协议,使用它读取 / 包含 .php 文件时,PHP 解析器会 直接执行文件里的 PHP 源码 ,页面最终显示的是「源码运行后的结果」,完全看不到文件的原始源码。
适用场景:已知目标文件的绝对 / 相对路径时
示例:
file:///var/www/html/config.php # Linux绝对路径
file://C:/phpstudy/www/test.txt # Windows绝对路径
|----------------|-------|
| 操作 | 支持 |
| 读取文件 | ✅ Yes |
| 写入文件 | ✅ Yes |
| 添加内容 | ✅ Yes |
| 同时读写 | ✅ Yes |
| 文件状态查询(stat()) | ✅ Yes |
| 删除文件(unlink()) | ✅ Yes |
| 重命名(rename()) | ✅ Yes |
| 创建目录(mkdir()) | ✅ Yes |
| 删除目录(rmdir()) | ✅ Yes |
这意味着 file:// 几乎支持对本地文件系统的所有操作,不只是读取。
当我们利用文件包含漏洞,使用 file:// 伪协议去包含文件时:
PHP 会通过file:// 把目标文件的「原始文本内容」完整读取出来;
如果读取的文件是 .php后缀文件 → 读取内容后,PHP 解释器会把里面的<?php 代码 ?> 当作正常 PHP 代码解析并执行;
如果读取的文件是 .txt/.jpg/.png/.ini等非 php 后缀文件 → 读取的原始内容会原样输出,不会解析;
举例说明
打开小皮面板,打开nginx1.25,打开VS code,从VS code里面打开小皮面板下www文件,在最下面创建一个php文件,Php文件里面写代码如下。
<?php ... ?>:PHP 代码的起始和结束标记。
include():PHP 的文件包含函数,作用是读取目标文件的内容,并将其当作 PHP 代码解析执行。
$_GET['file']:获取 URL 中通过GET方式传递的名为file的参数值。
整句话的意思是:从 URL 的GET参数中获取file的值,然后将这个值作为文件名,用include()函数包含并执行该文件。


在同级别创建test.php文件,文件内容输入

现在,我们在浏览器进行传参,读取test.php文件内容,http://127.0.0.1/te.php?file=test.php如此将同级别目录下的test.php给读取出来了。
2. php://filter
php://filter 是 php:// 伪协议家族中的一员,作用是在读取文件内容时,对数据流进行实时处理。我们可以把它想象成一个 "管道",文件内容在被 include() 函数读取的过程中,会先经过这个 "管道" 的过滤和转换,然后再交给 PHP 解析器。 (解决了直接包含.php文件会被执行、看不到源码的问题)。
完全不依赖 allow_url_fopen 和 allow_url_include,无论这两个配置是On还是Off,百分百正常使用;
|----------|-------------------------------------------------|
| 参数 | 描述 |
| resource | 必须参数,指定要过滤的目标文件(如 resource=index.php)。 |
| read | 可选参数,指定读取时使用的过滤器(如 read=convert.base64-encode)。 |
| write | 可选参数,指定写入时使用的过滤器。 |
Filter 可以进行文件read和write string.strip_tags
- read:读取文件时应用过滤器(如 convert.base64-encode、string.toupper),这是我们最常用的场景。
- write:写入文件时应用过滤器(如 string.strip_tags 清理 HTML 标签),在渗透测试中较少使用。
- string.strip_tags:字符串过滤器,作用是移除内容中的所有 HTML/XML 标签,只保留纯文本。
四大过滤器类型,它们对应了 php://filter 中 read= 参数可以使用的具体功能:
|--------------------------|----------------------------------------|--------------------------------------------------|
| 过滤器类型 | 作用 | 渗透测试中的用途 |
| String Filters 字符串过滤器 | 对文本内容进行字符串级别的处理,比如将字符串进行编码,大小写转换、字符替换等 | 用于绕过 WAF 或对内容进行简单处理 |
| Conversion Filters 转换过滤器 | 对内容进行编码 / 解码转换,比如 Base64、Hex 编码等 | 最常用! convert.base64-encode 就是这类过滤器,用于读取 PHP 文件源码 |
| Compression Filters压缩过滤器 | 对内容进行 GZIP/BZIP2 压缩或解压 | 读取压缩后的文件,或绕过某些内容长度限制 |
| Encryption Filters 加密过滤器 | 对内容进行加密 / 解密处理 | 读取加密的配置文件,或在传输中加密数据 |
stream_filter_append():把新的过滤器追加到现有过滤器链的末尾,所有数据都会经过这个新过滤器。stream_filter_prepend():把新的过滤器前置到现有过滤器链的开头,只有新读取的数据会经过它,缓冲区里已有的数据不会。
我们最常用的 convert.base64-encode 是作为 read= 参数使用的,它本质就是通过 stream_filter_append() 追加到读取流中的。
例子如下
|------------------------------------------------------------|
| php://filter/read=convert.base64-encode/resource=index.php |
特点:读取内容为 Base64 编码,不会被解析,解码后得到完整源码;无任何配置限制,永远可用
2.1 string.rot13 过滤器
是 php://filter 协议中的一种 String Filters(字符串过滤器),
核心原理:string.rot13 过滤器的作用,等同于 PHP 内置函数 str_rot13(),它会对数据流中的每个字母执行 ROT13 替换:
|-----------------------------------------------------------------|
| A ↔ N,B ↔ O,...,M ↔ Z a ↔ n,b ↔ o,...,m ↔ z 非字母字符(数字、符号、空格)保持不变 |
因为 ROT13 是对称加密,对同一段内容执行两次 ROT13 就会恢复原始文本。
代码示例解析
|--------------------------------------------------------------------------------------------------------------------------------------------------------|
| <?phpfp = fopen('php://output', 'w');stream_filter_append(fp, 'string.rot13');fwrite($fp, "This is a test.\n");/* Outputs: Guvf vf n grfg. */?> |
php://output 是 PHP 的内置输出流,直接将内容输出到浏览器。
stream_filter_append($fp, 'string.rot13') 把 string.rot13 过滤器追加到输出流上。
写入的文本 This is a test. 经过 ROT13 转换后,变成了 Guvf vf n grfg.。
在文件包含漏洞场景中,string.rot13 主要用于 绕过 WAF(Web 应用防火墙):
当 convert.base64-encode 被 WAF 拦截时,可以用 string.rot13 作为替代编码方式。
页面会输出 ROT13 编码后的源码,你只需将输出内容再执行一次 ROT13,即可得到原始源码。
2.2 string.toupper 过滤器
属于 php://filter 伪协议的 String Filters(字符串过滤器) 类型。
作用:字符串转大写过滤器。
在 include() 函数通过php://filter读取文件内容的过程中,把文件里的所有英文字母,统一转换成大写字母。
只转换英文字母(a-z → A-Z),数字、符号、空格、中文、标点这些内容,完全保持不变,不会做任何修改。
代码示例解释
|--------------------------------------------------------------------------------------------------------------------------|
| <?phpfp = fopen('php://output', 'w');stream_filter_append(fp, 'string.toupper');fwrite($fp, "This is a test.\n");?> |
php://output :PHP 内置的输出流,作用是「把内容直接输出到浏览器页面」。
stream_filter_append($fp, 'string.toupper') :把「转大写过滤器」挂载到输出流上。
执行结果:写入的小写 / 混合字母文本 This is a test. → 全部变成大写 THIS IS A TEST。
2.3 string.tolower 过滤器
属于 php://filter 伪协议的 String Filters(字符串过滤器) 类型
作用:字符串转小写 过滤器
在 include() 函数通过php://filter读取文件内容的过程中,把文件里的所有英文字母,统一转换成小写字母;
只转换英文字母(A-Z → a-z),数字、符号、空格、中文、标点这些内容,完全保持不变,不会做任何修改。
代码示例解释
|--------------------------------------------------------------------------------------------------------------------------|
| <?phpfp = fopen('php://output', 'w');stream_filter_append(fp, 'string.tolower');fwrite($fp, "This is a test.\n");?> |
stream_filter_append($fp, 'string.tolower') :把「转小写过滤器」挂载到输出流上。
执行结果:写入的混合字母文本 This is a test. → 全部变成小写 this is a test。
2.4 string.strip_tags 过滤器
这个过滤器是 php://filter 中 String Filters(字符串过滤器)。
string.strip_tags 过滤器的作用等同于 PHP 内置函数 strip_tags(),它会移除数据流中的所有 HTML/XML 标签,只保留纯文本内容。
默认会移除所有标签。
可以通过参数指定 "允许保留的标签",比如只保留 <b>、<i>、<u>。
代码示例解析
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| <?phpfp = fopen('php://output', 'w');stream_filter_append(fp, 'string.strip_tags', STREAM_FILTER_WRITE, "<b><i><u>");fwrite($fp, "<b>bolded text</b> enlarged to a <h1>level 1 heading</h1>\n");/* Outputs: bolded text enlarged to a level 1 heading */?> |
stream_filter_append(..., "<b><i><u>"):指定只保留 <b>、<i>、<u> 标签,其他标签(如 <h1>)会被移除。
输入的富文本内容经过过滤后,只保留了纯文本,所有未被允许的标签都被清除。
从 PHP 7.3.0 版本开始,string.strip_tags 过滤器已被官方废弃,不建议再使用。
2.5:convert.base64-encode 过滤器
属于 php://filter 伪协议的 Conversion Filters(转换过滤器) 核心成员
在 include() 函数通过php://filter读取文件内容的过程中,对文件的全部原始内容,执行「Base64 编码转换」,把文件里的文本 / 代码 / 二进制内容,统一转换成 Base64 格式的密文。
编码后的内容,只包含大小写英文字母、数字、+、/、= 这几种字符,绝对不会包含 PHP 的执行标记 <?php ?>;
所有类型文件都能转(php、txt、html、图片),无任何限制;
编码后的内容比原内容体积大一点点(约 1.3 倍),不影响读取和解码。
代码示例解释
|---------------------------------------------------------------------------------------------------------------------------------|
| <?phpfp = fopen('php://output', 'w');stream_filter_append(fp, 'convert.base64-encode');fwrite($fp, "This is a test.\n");?> |
php://output :PHP 内置的「输出流」,作用是把内容直接输出到浏览器页面;
stream_filter_append($fp, 'convert.base64-encode') :把「Base64 编码过滤器」挂载到输出流上,后续所有写入的内容,都会先编码再输出;
fwrite(...) :写入文本 This is a test.\n;
最终执行结果:原文本 → Base64 编码 → 页面输出密文 VGhpcyBpcyBhIHRlc3QuCg==。
为什么要做 Base64 编码?核心价值(解决你的核心痛点)
唯一且最重要的作用:读取 PHP 文件源码,不会被执行!
用 file:// 读 te.php → PHP 解析器会执行里面的代码,页面空白 / 出执行结果,看不到源码;
用 php://filter/read=convert.base64-encode 读 te.php → 内容被编码成密文,PHP 解析器「不认识 Base64 密文」,只会把密文原样输出在页面,不会执行任何代码;
我们把密文复制到「Base64 在线解码工具」,点击解码,就能得到文件的 完整原始源码,一字不差!
2.6: convert.base64-decode 过滤器
属于 php://filter 伪协议的 Conversion Filters(转换过滤器) 成员
和编码是完全相反的逆操作:在 include() 函数通过php://filter读取文件内容的过程中,对文件里已经是 Base64 格式的密文,执行「Base64 解码转换」,把密文还原成 文件的原始内容。
只能解码「标准 Base64 格式的密文」,如果文件内容不是 Base64 密文,解码会失败,页面显示乱码 / 空白;
解码后的内容,如果是 PHP 代码,会被 PHP 解析器直接执行;如果是普通文本,就原样输出。
代码示例解释
|---------------------------------------------------------------------------------------------------------------------------------------|
| <?phpfp = fopen('php://output', 'w');stream_filter_append(fp, 'convert.base64-decode');fwrite($fp, "VGhpcyBpcyBhIHRlc3QuCg==");?> |
写入的内容是上面编码得到的 Base64 密文 VGhpcyBpcyBhIHRlc3QuCg==;
经过过滤器解码后,页面直接输出原始文本:This is a test.。
- php://filter
PHP 的内置伪协议,属于「本地文件读取协议」,专门用于读取文件时,对文件内容做过滤 / 转换处理;
核心特性:无任何配置依赖,不受allow_url_fopen/allow_url_include影响,PHP 全版本支持。
- read=convert.base64-encode
read= :固定语法,表示「读取文件时」应用过滤器,是你图片里写的Filter可以进行file read的具体体现;
convert.base64-encode :你重点学的转换过滤器,作用是:读取文件内容时,对原始内容执行Base64编码;
核心价值:编码后的内容,只包含字母 / 数字 /+/=/,没有 PHP 的执行标记<?php ?>,PHP 解析器「不认识」这种内容,只会把它原样输出在页面,不会执行任何代码!
- resource=test.php
resource= :php://filter的固定必写参数,语法要求,没有就失效;
作用:指定你要读取的目标文件是谁,这里就是服务器上的test.php文件;
补充:resource 后面可以跟任意 PHP 文件 / 文本文件,比如resource=index.php、resource=config.php都可以。
2.7 convert.quoted-printable-encode 过滤器
- 核心原理
它会将数据流编码为 Quoted-Printable(QP)格式,这是一种专为电子邮件设计的编码方式:
可打印的 ASCII 字符(如字母、数字)保持不变。
不可打印的字符(如换行符、特殊符号)会被编码为 =XX 格式(XX 是字符的十六进制值)。
例如:换行符 \n 会被编码为 =0A,空格会被编码为 =20。
代码示例解析
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| <?phpfp = fopen('php://output', 'w');stream_filter_append(fp, 'convert.quoted-printable-encode');fwrite($fp, "This is a test.\n");/* Outputs: =This is a test.=0A */?> |
输入文本 This is a test.\n 经过编码后,换行符 \n 被替换为 =0A,开头也多了一个 =。
这种编码主要是为了确保邮件在传输过程中不会被损坏。
渗透测试用途
绕过 WAF:当 convert.base64-encode 被拦截时,可以用它作为替代编码方式。
读取特殊文件:对于包含大量不可打印字符的文件,QP 编码比 Base64 更紧凑。
2.8 convert.quoted-printable-decode 过滤器
是编码的逆操作,会将 QP 格式的编码内容还原为原始文本。
会将 =XX 格式的编码转换为对应的字符。
例如:=0A 会被还原为换行符 \n,=20 会被还原为空格。
代码示例解析
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| <?phpfp = fopen('php://output', 'w');stream_filter_append(fp, 'convert.quoted-printable-decode');fwrite($fp, "=This is a test.=0A");/* Outputs: This is a test.\n */?> |
输入的 QP 编码内容被解码后,还原为原始文本。
- 渗透测试用途
当拿到一个 QP 编码的文件时,可以用它来解码还原内容。
2.9 convert.iconv.* 过滤器
该过滤器的作用等同于 PHP 的 iconv() 函数,它会在读取文件内容时,将数据流从一种字符编码转换为另一种。
|---------------------------------|
| 命名规则:它的名称本身就包含了编码转换的规则, |
| convert.iconv.<输入编码>.<输出编码> |
| convert.iconv.<输入编码>/<输出编码> |
两种写法完全等价。例如:
convert.iconv.utf-16le.utf-8 → 将 UTF-16LE 编码转换为 UTF-8
convert.iconv.GBK.UTF-8 → 将 GBK 编码转换为 UTF-8。
依赖条件
这个过滤器需要 PHP 的 iconv 扩展支持,通常在默认 PHP 环境中是开启的。如果扩展未启用,该过滤器将无法使用。
代码示例解析
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| <?phpfp = fopen('php://output', 'w'); stream_filter_append(fp, 'convert.iconv.utf-16le.utf-8'); fwrite($fp, "T\0h\0i\0s\0 \0i\0s\0 \0a\0 \0t\0e\0s\0t\0.\0\n\0"); /* Outputs: This is a test. */ ?> |
输入:"T\0h\0i\0s\0 \0i\0s\0 \0a\0 \0t\0e\0s\0t\0.\0\n\0" 是典型的 UTF-16LE 编码(每个字符后带一个空字节 \0)。
过滤器:过滤器:convert.iconv.utf-16le.utf-8 的作用 → 把【原本就是UTF-16LE编码】的输入内容,直接转换为 UTF-8 编码输出:转换后得到标准的 UTF-8 文本 This is a test.。
还可以进行扩展操作
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| <?php // 1. 打开PHP内置输出流,直接输出到浏览器 fp = fopen('php://output', 'w'); // 2. 第一次过滤:UTF-8 → UTF-16LE 编码转换 stream_filter_append(fp, 'convert.iconv.utf-8.utf-16le'); // 3. 第二次过滤:UTF-16LE → UTF-8 编码转换 stream_filter_append(fp, 'convert.iconv.utf-16le.utf-8'); // 4. 第三次过滤:把内容全部转成大写 stream_filter_append(fp, 'string.toupper'); // 5. 写入原始内容(PHP默认UTF-8编码) fwrite($fp, "This is a test.\n"); // 最终输出:THIS IS A TEST. ?> |
关键特性
无配置依赖:只要 iconv 扩展启用,就可以使用,不受 allow_url_fopen 和 allow_url_include 影响。
灵活高效:支持几乎所有主流字符编码之间的转换,是处理编码问题的最佳工具。
渗透价值中等:主要用于解决乱码和绕过简单 WAF,不如 convert.base64-encode 常用,但在特定场景下非常实用。
渗透测试用途
处理中文乱码:当读取的文件是 GBK 编码,而你的浏览器是 UTF-8 时,可以用它转换编码,解决乱码问题。
绕过 WAF:有些 WAF 会对特定编码的关键词进行拦截,转换编码后可以绕过检测。
2. php://input
作用:执行 POST 请求体中传入的任意 PHP 代码
适用条件:存在文件包含漏洞,参数可控。
PHP 配置 allow_url_include = On; (PHP5 默认开启,PHP7 默认关闭); 和allow_url_fopen无关
利用方式:
传入 php://input 作为包含参数
在 POST 请求体中写入代码,如 <?php system('ls');?>
特点:可以执行任意命令,危害极大。
3. zip:// / compress.bzip2:// / compress.zlib://
作用:读取压缩包内的文件(文件包含 + 文件上传组合漏洞常用)
适用场景:上传包含 PHP 代码的压缩包(如shell.zip),再通过伪协议读取
示例:php
zip://shell.zip#shell.php
compress.bzip2://shell.bz2
compress.zlib://shell.gz
特点:无需解压,直接读取压缩包内的文件内容并解析执行。
4.data://
作用:直接执行嵌入在 URL 中的 PHP 代码
适用条件:PHP 配置 allow_url_include = On
示例:php
data://text/plain,<?php phpinfo();?>
data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+
特点:无需依赖外部文件,直接在请求中嵌入代码,隐蔽性高。
三.PHP伪协议 代码层防护手段
- 文件包含白名单机制
对 include/require 传入的文件路径,仅允许白名单内的合法文件(如指定 .php 后缀、固定目录下的文件)
示例: allowed_files = \['header.php', 'footer.php'\]; if(!in_array(file, $allowed_files)) { die('非法文件'); }
- 禁止动态路径拼接
杜绝直接使用用户输入拼接文件路径,如避免 include($_GET['file'] . '.php') 这类写法
若需动态加载,使用固定映射关系替代(如 $file_map = ['a' => 'pageA.php', 'b' => 'pageB.php']; )
- 过滤危险伪协议前缀
在接收文件路径参数时,强制过滤 php:// / data:// / phar:// / zip:// 等危险协议前缀。
示例: file = str_replace(\['php://', 'data://', 'phar://'\], '', _GET['file']);
- 严格校验文件后缀与类型
限制包含文件的后缀为 .php (或业务所需的固定后缀),拒绝非预期后缀(如 .txt / .phar )
结合 pathinfo() 函数获取真实后缀,避免攻击者通过 file.php%00.txt 截断绕过。