FlawOpen

flawopen.com/文件包含/PHP include()

PHP include() 使用用户输入安全吗?

CWE-98: PHP Remote File Inclusion参考页
简要回答

不安全。include() 会将选定文件按 PHP 解析,但影响取决于可访问的文件及其内容。缺陷可能通过 wrapper 暴露源代码、包含意外的本地文件;只有攻击者能控制带 PHP 标签的内容且应用随后包含该文件时,才会执行其 PHP 代码。远程 URL 包含还取决于 PHP 配置。将用户选择映射到固定键列表,切勿用请求数据拼接 include 路径。

存在漏洞
<?php
// User-controlled path: unintended local file
include $_GET['page'] . '.php';

// Example selector: ../../private/sample
// Impact depends on the resolved file and
// whether it contains PHP code to interpret.

// Remote URL inclusion requires the relevant
// URL wrapper and allow_url_include setting.

// Wrappers can change which resource is read;
// php://filter is a read filter, not a path
// containment check.
已修复
<?php
// Allowlist: input selects a KEY,
// never a path or wrapper.
$pages = [
    'home'    => __DIR__ . '/pages/home.php',
    'about'   => __DIR__ . '/pages/about.php',
    'contact' => __DIR__ . '/pages/contact.php',
];

$key = $_GET['page'] ?? 'home';

if (!is_string($key) || !array_key_exists($key, $pages)) {
    http_response_code(404);
    exit;
}

include $pages[$key];

为什么本地包含不等于自动执行代码

本地包含不会自动执行代码。PHP 会解析被包含文件:有效 PHP 标签内的语句在包含上下文中执行,其他字节可能作为输出发送。代码执行需要可访问的攻击者可控 PHP 文件或其他可利用行为。例如,上传或日志写入缺陷可能形成攻击链:将可控 PHP 内容写入之后会被包含的文件。

php://filter 会在打开流时应用过滤器。在存在漏洞的读取或包含流程中,合适的过滤器可能暴露源文本而不执行它;能否读取取决于 wrapper、权限和应用配置。

为什么清理路径不是正确修复

删除 ../、追加固定扩展名或添加目录前缀都不能证明最终资源已获准。编码、绝对路径、平台规则和 wrapper 都可能改变解释方式。应移除用户对资源名称的控制:只允许选择应用定义的固定键。

如果必须解析文件系统路径,请用 realpath() 规范化,并按 Path Traversal 指南检查目录边界。随后仅允许预先批准的文件,同时考虑符号链接和检查后使用竞态。固定目标允许列表更安全。

常见问题

关闭 allow_url_include 就安全吗?

此设置会为 include/require 禁用 URL wrapper。PHP 文档说明它默认关闭,并自 PHP 7.4 起弃用。这能降低远程包含风险,但不能阻止本地路径遍历或不安全的本地包含。保持关闭,并移除用户对目标的控制。

空字节技巧仍然有效吗?

PHP 5.3.4 修复了路径处理中的空字节截断。不要把旧技巧失效当作防护;不可信的 include 目标仍然危险。

require、include_once 和 readfile 呢?

require 和 _once 变体也会包含并解析目标;它们在失败和重复包含处理上有所不同,但不会让不可信目标变安全。readfile() 和 file_get_contents() 返回数据,不会按 PHP 解析;不可信路径可能造成遍历或泄露,而非 include 代码执行。

参考资料

另请参阅 路径遍历(CWE-22):原理及在各种语言中的防范 — flawopen.com 修复 Python os.path.join() 路径穿越 什么是 Zip Slip 漏洞? Node.js path.join() 无法阻止路径穿越