news 2026/8/3 16:28:03

PHP文件包含漏洞与php://filter协议利用深度解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PHP文件包含漏洞与php://filter协议利用深度解析

1. 从一个真实的渗透测试场景说起

那天下午,我正在对一个内部系统进行授权安全测试。目标是一个典型的文件上传功能,界面看起来平平无奇:一个选择文件的按钮,一个上传按钮。我随手传了个图片,成功了。接着,我尝试上传一个包含一句话木马的PHP文件,结果页面弹出了一个醒目的红色提示:“文件类型不允许!”。这在意料之中,开发者通常会通过检查文件扩展名(如.php)或MIME类型来防御。于是,我祭出了常规的绕过手段:将文件改名为shell.php.jpg,并修改了HTTP请求中的Content-Typeimage/jpeg。再次上传,系统竟然提示“上传成功!”。我心里一喜,以为找到了突破口,立刻去访问上传后的文件路径。然而,浏览器返回的却是乱码——服务器并没有把这个文件当作PHP脚本来解析,它只是被当作一个普通的、内容为PHP代码的图片文件存储了起来。这意味着,虽然绕过了前端的检查,但文件并没有获得执行权限。

这种“上传成功但无法执行”的困境,在渗透测试和代码审计中非常常见。就在我思考下一步是去挖掘解析漏洞还是目录穿越时,突然想起了php://filter这个奇特的协议。它不像http://file://那样指向一个网络或本地资源,而是PHP内置的一个“处理器”或“过滤器”,专门用于在数据流上执行各种转换操作,比如编码、解码、压缩、字符串处理等。更重要的是,在某些特定配置下,结合文件包含漏洞,php://filter能够实现一种被称为“死亡绕过”的技巧——即在不直接写入可执行文件的情况下,通过编码、转换等方式,将恶意代码“注入”到目标文件中,最终达成命令执行的目的。这听起来有点绕,但却是理解现代Web应用安全中一些深层漏洞的关键。今天,我们就来彻底拆解php://filter的机制,并一步步还原如何利用它进行“死亡绕过”。

2. 深入理解php://filter协议栈

在PHP中,php://是一个用于访问各种输入/输出流(I/O streams)的封装协议。而php://filter是其中功能最强大、也最复杂的一个子集。你可以把它想象成一个流水线上的加工站。原始数据(比如一个文件的内容)是原材料,它流经这个“过滤器”流水线,经过一系列预定义的“加工”(如编码、替换字符),最终输出处理后的成品。

2.1 核心语法与过滤器类型

php://filter的基本语法结构是:php://filter/过滤器链/resource=目标资源。其中,“过滤器链”是核心,它决定了数据要经过哪些处理。过滤器之间用竖线|连接,表示依次执行,类似于Linux管道。

PHP内置了多种过滤器,主要分为几大类:

字符串过滤器:

  • string.rot13: 执行ROT13编码,这是一种简单的字母替换密码。
  • string.toupper/string.tolower: 将字符串转换为全大写或全小写。
  • string.strip_tags: 尝试去除PHP和HTML标签。注意:这个过滤器的行为在复杂情况下可能不可靠,不应作为安全过滤的唯一手段。

转换过滤器:主要用于处理编码。

  • convert.base64-encode/convert.base64-decode: 进行Base64编码和解码。
  • convert.quoted-printable-encode/convert.quoted-printable-decode: 进行Quoted-Printable编码和解码,常用于电子邮件。
  • convert.iconv.*: 进行字符集转换,例如convert.iconv.UTF-8.UTF-16BE

压缩过滤器:zlib.deflatezlib.inflate,用于压缩和解压数据。

加密过滤器:mcrypt.*mdecrypt.*(已废弃)。

一个简单的例子是读取一个文件并对其进行Base64编码:

$content = file_get_contents('php://filter/convert.base64-encode/resource=/etc/passwd'); echo $content; // 输出 /etc/passwd 文件的Base64编码内容

在这个例子中,数据流从/etc/passwd文件流出,经过convert.base64-encode过滤器加工,然后被file_get_contents读取。

2.2 过滤器链的“栈式”执行顺序

这是理解高级利用的关键。过滤器链的执行顺序是“从左到右”吗?并不完全是。更准确地说,它类似于一个栈(Stack)的操作,遵循“后进先出”的原则,但体现在读写操作上。

  • 对于读操作(如file_get_contents,include:过滤器链的执行顺序是从左到右。数据从resource流出,先经过最左边的过滤器处理,再传递给下一个,最后到达你的PHP代码。

    • 示例:read=convert.base64-encode|string.rot13/resource=test.txt
    • 过程:test.txt内容 →convert.base64-encodestring.rot13→ 输出给PHP。
  • 对于写操作(如file_put_contents:过滤器链的执行顺序是从右到左。数据从你的PHP代码流出,先经过最右边的过滤器处理,再传递给前一个,最后写入resource

    • 示例:write=string.rot13|convert.base64-decode/resource=test.txt
    • 过程:PHP输出数据 →convert.base64-decodestring.rot13→ 写入test.txt

这个反向特性在构造攻击链时至关重要。例如,如果你想通过写操作,最终让文件里包含一段Base64编码后的内容,你需要将convert.base64-encode放在写链的最右侧(即最后执行)。

2.3 与文件包含漏洞的天然结合

php://filter最常见的危险暴露点是与本地文件包含(LFI)漏洞的结合。假设有一段问题代码:

// file: include.php $page = $_GET['file']; include($page . '.php');

攻击者可以传入:?file=php://filter/convert.base64-encode/resource=index代码最终会执行:include(‘php://filter/convert.base64-encode/resource=index.php’)这时,include函数会尝试读取index.php文件,但数据流先经过了Base64编码。由于Base64编码后的内容不是有效的PHP代码,include会失败,但编码后的文件内容会被直接打印到页面上。这就实现了对服务器上任意PHP文件源码的读取(只要有读取权限),即使这些.php文件因为配置原因无法直接通过Web访问。

注意includerequire等函数在包含php://filter流时,如果流的内容不是有效的PHP代码,会引发一个警告,但编码后的内容通常会作为警告信息的一部分或直接输出到页面,从而被攻击者捕获。这正是利用的起点。

3. “死亡绕过”的核心:利用过滤器链制造PHP代码

“死亡绕过”这个听起来很骇人的词,实际上描述的是一种利用php://filter过滤器链的编码转换特性,将非PHP内容(如纯文本、图片)转换为有效PHP代码的技术。它的核心应用场景通常围绕文件包含文件写入两个点。

3.1 场景一:从日志文件、Session文件到Webshell

这是最经典的“死亡绕过”利用链。假设我们有一个文件包含漏洞:include($_GET[‘file’]);,并且我们知道服务器上一个文件的可写路径和大致内容(例如/var/log/apache2/access.log),我们能否通过包含这个日志文件来执行代码?

直接包含日志文件,由于里面是HTTP访问记录的纯文本,不是PHP代码,所以不会执行。但是,如果我们能向这个日志文件写入一个包含PHP标签的HTTP请求,然后再去包含它,不就行了吗?问题在于,很多Web服务器或应用框架会对写入日志的内容进行转义或过滤,比如将``转义为<?php,导致其失效。

这时,php://filter的转换过滤器就派上用场了。我们可以利用convert.iconv过滤器进行字符集转换。字符集转换本质上是一种字节到字节的映射,如果我们在一个字符集(如UTF-8)中构造一个特殊字节序列,当它被用另一种字符集(如US-ASCII, UTF-16BE)解码或编码时,可能会意外地产生``这样的字节序列。

一个简化示例:

  1. 攻击者向服务器发送一个精心构造的HTTP请求,User-Agent设置为:<?php system(‘id’); ?>
  2. 这个请求被记录到Apache的access.log中,内容可能是纯文本。
  3. 攻击者利用LFI漏洞,尝试包含日志文件:?file=/var/log/apache2/access.log,失败,因为日志内容是文本。
  4. 攻击者现在使用过滤器链进行包含:?file=php://filter/convert.iconv.UTF-8.UTF-16BE/resource=/var/log/apache2/access.log
  5. PHP在读取日志文件时,会尝试将文件内容从UTF-8编码转换为UTF-16BE编码。在这个转换过程中,日志文件中原本代表<?php system(‘id’); ?>的文本字节,可能因为编码规则的差异,被错误地解释重组,使得在输出的数据流中,偶然形成了一个有效的``标签及其后的代码。如果这个转换后的流被include函数处理,PHP引擎可能会识别并执行这段“意外生成”的代码。

这个过程高度依赖于具体的字符集、原始字节和PHP版本,需要精心构造和测试。它利用了编码转换过程中的“歧义”来“制造”出恶意代码。

3.2 场景二:配合文件写入与二次包含

另一个更直接的场景涉及文件写入。假设有一个功能允许你将php://filter链的结果写入一个文件(例如,通过file_put_contents),或者有一个文件上传点允许你控制文件的部分内容。

攻击思路:

  1. 目标:在一个我们已知路径的文件(比如一个图片文件avatar.jpg,或者一个临时文件)中注入PHP代码。
  2. 限制:直接写入``会被过滤或转义。
  3. 绕过:利用php://filter的写操作链。我们构造一个过滤器链,使得我们输入的“无害”文本,在经过链式解码/转换后,写入目标文件时,恰好变成有效的PHP代码。
  4. 触发:再通过另一个文件包含漏洞(LFI)去包含这个已经被“污染”的文件。

构造示例:假设我们可以控制file_put_contents的第一个参数(路径)。我们想向/tmp/test.txt写入一句话木马``。 我们不能直接写,因为<?可能被过滤。我们可以这样构造:

// 攻击者控制的输入 $malicious_payload = "PD9waHAgZXZhbCgkX1BPU1RbJ2MnXSk7Pz4="; // 这是 `` 的Base64编码 $path = "php://filter/convert.base64-decode/resource=/tmp/test.txt"; file_put_contents($path, $malicious_payload);

执行后,$malicious_payload字符串在写入/tmp/test.txt之前,会先经过convert.base64-decode过滤器解码。于是,解码后的原始字节——也就是``——就被写入了/tmp/test.txt文件。这样,我们就在目标服务器上创建了一个纯文本文件,但其内容是一个完整的Webshell。

接下来,如果存在LFI漏洞,攻击者包含/tmp/test.txt,这个文件就会被当作PHP代码执行。这里的关键在于,写入操作和包含操作是分离的。写入时我们利用了过滤器的解码功能“绕过”了内容检查;包含时,该文件因为扩展名是.txt,可能不会被Web服务器直接解析,但PHP的include函数不在乎扩展名,只关心文件内容是否包含有效的PHP标签。

4. 实战演练:构造一个完整的攻击链

让我们通过一个模拟的、简化的CTF场景,将上面的知识串联起来。假设我们有一个Web应用:

  1. 漏洞点A(文件写入/上传):有一个“设置个性签名”的功能,签名内容会保存到服务器的一个固定文件/tmp/signature_[userid].txt中。服务器对输入进行了严格的过滤,移除了所有<>?php等字符。
  2. 漏洞点B(文件包含):在个人主页有一个LFI漏洞,?page=../../tmp/signature_123会尝试包含对应用户的签名文件(自动补全.php扩展名,但我们的文件是.txt,不过include依然会尝试读取它)。

目标:在服务器上执行命令ls /

攻击步骤:

第一步:利用过滤器链写入Webshell我们无法直接写入``。但我们可以写入它的Base64编码形式。 我们知道,Base64解码器会忽略不在其字符集(A-Za-z0-9+/=)内的字符。因此,我们可以构造一个payload,使其Base64解码后恰好是我们想要的代码。

<?php system(‘ls /’); ?>的Base64编码是:PD9waHAgc3lzdGVtKCdscyAvJyk7ID8+但是,直接写入这个字符串,解码后就能得到原语句吗?不一定。因为Base64解码以4个字符为一组,如果字符串长度不是4的倍数,需要用=填充。我们的字符串是PD9waHAgc3lzdGVtKCdscyAvJyk7ID8+,长度是36,是4的倍数,所以可以正确解码。

那么,我们如何利用“个性签名”功能写入呢?我们需要让服务器执行类似这样的代码:

file_put_contents($user_signature_path, $_POST[‘signature’]);

我们无法直接控制$user_signature_path,但假设服务器愚蠢地允许签名内容包含换行,并且路径是固定的。我们更可能的情况是,我们需要找到一个能控制完整文件路径的写入点。为了演示,我们假设存在一个更脆弱的端点,允许我们指定写入路径和内容,但内容会经过过滤。

构造payload:我们无法写入<?,但我们可以写入php://filter/convert.base64-decode/resource=/tmp/shell.php作为文件名(如果服务器允许),然后将Base64编码后的代码作为文件内容。但很多情况下,路径和内容是分开的。

实际上,更常见的利用是“死亡绕过”的变种:通过控制过滤器链本身来写入文件。如果应用有这样的代码:

$data = $_GET[‘data’]; $file = $_GET[‘file’]; file_put_contents($file, $data);

那么攻击者可以:

file=php://filter/convert.base64-decode/resource=/tmp/shell.php&data=PD9waHAgc3lzdGVtKCdscyAvJyk7ID8+

这样,data参数中的Base64字符串在写入/tmp/shell.php前被解码,于是/tmp/shell.php文件中就包含了原始的PHP代码。

第二步:触发文件包含执行Webshell拿到LFI漏洞点:?page=../../tmp/shell服务器代码执行include(‘../../tmp/shell.php’);,成功加载并执行我们写入的Webshell,执行ls /命令,输出根目录列表。

整个攻击链的关键在于找到了一个“写入点”,并且这个写入点允许我们使用php://filter协议来指定目标文件,从而在写入过程中对内容进行解码或转换,绕过了直接的内容安全检查。然后,再利用另一个“包含点”来执行写入的恶意文件。

5. 防御之道:如何避免成为“死亡绕过”的受害者

理解了攻击原理,防御思路就清晰了。核心原则是:最小化攻击面,对用户输入进行严格、多层、上下文相关的过滤。

5.1 严格限制文件包含与文件操作的参数

  • 白名单机制:对于includerequirefile_get_contentsfile_put_contents等函数的路径参数,尽可能使用白名单。如果必须动态包含,只允许包含预定义的、安全的文件列表。
  • 路径固定:避免用户输入直接、未经处理地拼接到文件路径中。如果需要基于用户输入,应使用映射关系(如ID到文件名),而不是直接使用输入作为路径的一部分。
  • 禁用危险协议:在PHP配置文件(php.ini)中,使用allow_url_fopen = Offallow_url_include = Off来禁用php://data://http://等URL封装协议。这是最有效的一劳永逸的方法之一。在生产环境中,除非有绝对必要,否则应保持关闭。
    allow_url_fopen = Off allow_url_include = Off

5.2 对用户输入进行深度过滤与验证

  • 上下文相关过滤:过滤不是简单地删除<>。对于文件路径,要检查是否包含目录遍历序列(../)、空字节(%00,在PHP老版本中可用于截断)以及php://data://等协议字符串。
  • 规范化与绝对路径:使用realpath()函数获取规范化的绝对路径,并检查该路径是否在允许的目录范围内(例如Web根目录之外)。
    $user_file = $_GET[‘file’]; $base_dir = ‘/var/www/html/uploads/’; $real_path = realpath($base_dir . $user_file); if ($real_path === false || strpos($real_path, $base_dir) !== 0) { // 路径非法,拒绝访问 die(‘Access denied.’); }
  • 内容安全检查:对于文件上传,除了检查扩展名和MIME类型,还应该对文件内容进行安全检查(如病毒扫描、图片重渲染)。对于用户输入将要写入文件的情况,要根据文件的预期用途进行严格的过滤或转义(例如,如果写入的是HTML,就用htmlspecialchars;如果写入的是配置文件,就严格限制字符集)。

5.3 安全的服务器与PHP配置

  • 将用户上传的文件存储在Web根目录之外:这是黄金法则。这样即使攻击者上传了恶意文件,也无法通过直接的HTTP请求访问到它,必须结合服务器本身的其他漏洞(如文件包含、解析漏洞)才能触发,大大增加了攻击难度。
  • 为上传文件分配随机、不可预测的文件名:避免使用用户提供的原始文件名。
  • 限制PHP执行权限:通过Web服务器配置(如Apache的.htaccess或Nginx的location规则),禁止在特定目录(如上传目录)中执行PHP脚本。
    • Apache示例(在上传目录的.htaccess中):
      <FilesMatch “\.(php|php5|phtml|phps)$”> Order Allow,Deny Deny from all </FilesMatch>
    • Nginx示例(在server配置中):
      location ^~ /uploads/ { location ~ \.php$ { deny all; } }
  • 保持PHP版本更新:新版本通常会修复旧版本中协议处理器或过滤器的潜在安全问题。

5.4 开发层面的安全意识

  • 避免动态包含:从根本上审视业务逻辑,是否真的需要动态包含文件?很多时候可以用路由、模板引擎等更安全的方式替代。
  • 代码审计:定期对代码进行安全审计,特别关注所有包含文件操作、文件读写操作的函数调用点,检查其参数是否用户可控。
  • 使用安全函数:例如,用basename()去除路径中的目录部分,但注意它可能无法处理所有情况,仍需结合白名单。

“死亡绕过”听起来神秘,但其本质是攻击者对PHP流过滤器特性与应用程序逻辑缺陷的深度结合利用。防御的关键不在于封堵某一个特定的payload,而在于建立纵深防御体系:从协议开关、路径控制、内容过滤到服务器配置,层层设防,让攻击者即便掌握了一个漏洞点,也难以串联成有效的攻击链。作为开发者,理解这些攻击手法,不是为了实施攻击,而是为了在编写每一行代码时,都能清晰地意识到数据流动的边界与风险,从而构建出更坚固的应用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/3 16:27:40

视频原声怎么单独提取?2026年视频原声单独提取方法全指南:电脑免费工具与手机APP一网打尽

把视频里的声音单独拎出来存成音频文件&#xff0c;是视频剪辑、播客制作、素材归档时的高频需求。这件事远比想象中简单&#xff0c;无需专业设备&#xff0c;也无需复杂的转码知识。下面这份教程覆盖了从微信小程序到电脑端免费软件再到手机APP的多种方案&#xff0c;每一种我…

作者头像 李华
网站建设 2026/8/3 16:25:14

Wail2Ban:3步构建Windows服务器的终极主动防御系统

Wail2Ban&#xff1a;3步构建Windows服务器的终极主动防御系统 【免费下载链接】wail2ban fail2ban, for windows. 项目地址: https://gitcode.com/gh_mirrors/wa/wail2ban 你的Windows服务器是否正在遭受暴力破解攻击&#xff1f;每天面对数百次RDP登录失败警报&#…

作者头像 李华
网站建设 2026/8/3 16:19:28

设计师正在被AI淘汰?不——但用错工具的人将在3个月内掉队:2024真实项目交付周期对比(含Figma AI插件链优化前后数据)

更多请点击&#xff1a; https://codechina.net 第一章&#xff1a;设计师正在被AI淘汰&#xff1f;不——但用错工具的人将在3个月内掉队&#xff1a;2024真实项目交付周期对比&#xff08;含Figma AI插件链优化前后数据&#xff09; 设计行业的分水岭并非来自AI是否取代人类…

作者头像 李华
网站建设 2026/8/3 16:16:01

增强自信的SOP到庖丁解牛

真正的自信&#xff0c;不是不断告诉自己“我很厉害”&#xff0c;而是通过持续获得现实证据&#xff0c;让大脑相信&#xff1a;“我遇到问题时&#xff0c;有能力处理。”很多人误解自信&#xff1a; 以为&#xff1a; 自信 正面想法 鼓励自己但稳定自信更接近&#xff1a;…

作者头像 李华
网站建设 2026/8/3 16:12:26

tModLoader:开启泰拉瑞亚无限创意的开源模组开发平台

tModLoader&#xff1a;开启泰拉瑞亚无限创意的开源模组开发平台 【免费下载链接】tModLoader A mod to make and play Terraria mods. Supports Terraria 1.4 (and earlier) installations 项目地址: https://gitcode.com/gh_mirrors/tm/tModLoader 你是否曾想过为《泰…

作者头像 李华
网站建设 2026/8/3 16:11:00

板材批发公司哪家强

在板材批发领域&#xff0c;“哪家强”从来就不是一个能靠简单排名回答的问题——工程项目、家具厂批量集采、装企定点供货、个人零散采购……不同场景对板材的环保等级、基材类型、供货稳定性和服务纵深有着截然不同的诉求。作为行业从业者&#xff0c;本文不为任何品牌站台&a…

作者头像 李华