文件包含漏洞及PHP伪协议

一.什么是文件包含漏洞

文件包含漏洞 ≈ 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![](https://i-blog.csdnimg.cn/direct/ab02eb7faa5b4a018c28c95e647bc5e7.png)如此将同级别目录下的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.。

  1. php://filter

PHP 的内置伪协议,属于「本地文件读取协议」,专门用于读取文件时,对文件内容做过滤 / 转换处理;

核心特性:无任何配置依赖,不受allow_url_fopen/allow_url_include影响,PHP 全版本支持。

  1. read=convert.base64-encode

read= :固定语法,表示「读取文件时」应用过滤器,是你图片里写的Filter可以进行file read的具体体现;

convert.base64-encode :你重点学的转换过滤器,作用是:读取文件内容时,对原始内容执行Base64编码;

核心价值:编码后的内容,只包含字母 / 数字 /+/=/,没有 PHP 的执行标记<?php ?>,PHP 解析器「不认识」这种内容,只会把它原样输出在页面,不会执行任何代码!

  1. resource=test.php

resource= :php://filter的固定必写参数,语法要求,没有就失效;

作用:指定你要读取的目标文件是谁,这里就是服务器上的test.php文件;

补充:resource 后面可以跟任意 PHP 文件 / 文本文件,比如resource=index.php、resource=config.php都可以。

2.7 convert.quoted-printable-encode 过滤器
  1. 核心原理

它会将数据流编码为 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 编码内容被解码后,还原为原始文本。

  1. 渗透测试用途

当拿到一个 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伪协议 代码层防护手段

  1. 文件包含白名单机制

对 include/require 传入的文件路径,仅允许白名单内的合法文件(如指定 .php 后缀、固定目录下的文件)

示例: allowed_files = \['header.php', 'footer.php'\]; if(!in_array(file, $allowed_files)) { die('非法文件'); }

  1. 禁止动态路径拼接

杜绝直接使用用户输入拼接文件路径,如避免 include($_GET['file'] . '.php') 这类写法

若需动态加载,使用固定映射关系替代(如 $file_map = ['a' => 'pageA.php', 'b' => 'pageB.php']; )

  1. 过滤危险伪协议前缀

在接收文件路径参数时,强制过滤 php:// / data:// / phar:// / zip:// 等危险协议前缀。

示例: file = str_replace(\['php://', 'data://', 'phar://'\], '', _GET['file']);

  1. 严格校验文件后缀与类型

限制包含文件的后缀为 .php (或业务所需的固定后缀),拒绝非预期后缀(如 .txt / .phar )

结合 pathinfo() 函数获取真实后缀,避免攻击者通过 file.php%00.txt 截断绕过。

相关推荐
BingoGo1 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack1 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo2 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack2 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
JaguarJack3 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo3 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack4 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理5 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
feifeigo1235 天前
matlab画图工具
开发语言·matlab
dustcell.5 天前
haproxy七层代理
java·开发语言·前端