文件包含漏洞及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 截断绕过。

相关推荐
木木木一2 小时前
Rust学习记录--C13 Part1 闭包和迭代器
开发语言·学习·rust
木木木一2 小时前
Rust学习记录--C13 Part2 闭包和迭代器
开发语言·学习·rust
CopyProfessor2 小时前
Java Agent 入门项目模板(含代码 + 配置 + 说明)
java·开发语言
枫叶丹42 小时前
【Qt开发】Qt系统(八)-> Qt UDP Socket
c语言·开发语言·c++·qt·udp
一晌小贪欢2 小时前
用 PyQt5 做一个「批量目录重命名」工具,并打包成带图标的 EXE
开发语言·驱动开发·python·python基础·python小白
阿蒙Amon2 小时前
C#每日面试题-简述类成员
开发语言·c#
阿蒙Amon2 小时前
C#每日面试题-ValueTuple和Tuple的区别
开发语言·c#
百***78752 小时前
一步API+GPT-5.2生产级落地指南:架构设计+高可用+成本控制
开发语言·gpt·架构
Vallelonga2 小时前
Rust 中 extern “C“ 关键字
c语言·开发语言·rust