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, разрешений и конфигурации.

Почему очистка пути не решает проблему

Удаление ../, добавление фиксированного расширения или префикса каталога не доказывает, что итоговый ресурс разрешён. Кодировки, абсолютные пути, правила платформы и wrappers меняют интерпретацию. Уберите контроль пользователя над именем ресурса: разрешайте только фиксированные ключи приложения.

Если путь необходимо разрешить, канонизируйте его через realpath() и проверьте границу каталога, как описано в руководстве Path Traversal. Ограничьте результат разрешёнными файлами и учитывайте symlink и гонки между проверкой и использованием. Фиксированный список целей безопаснее.

Частые вопросы

Отключение allow_url_include делает код безопасным?

Эта настройка отключает URL-wrappers для include/require. В PHP она выключена по умолчанию и объявлена устаревшей с версии 7.4. Это снижает риск удалённого включения, но не предотвращает локальный traversal и небезопасные локальные include. Оставьте её выключенной и исключите контроль пользователя над целью.

Работает ли ещё приём с нулевым байтом?

PHP 5.3.4 исправил завершение пути нулевым байтом. Не полагайтесь на исчезновение этого старого приёма: недоверенные цели include всё ещё опасны.

А как насчёт require, include_once и readfile?

require и варианты _once также подключают и разбирают цель; их обработка ошибок и повторного включения отличается, но недоверенную цель они не обезвреживают. readfile() и file_get_contents() возвращают данные, не разбирая их как PHP; недоверенный путь может вызвать traversal или раскрытие данных, но не выполнение через include.

Источники

См. также Обход каталогов (CWE-22): как это работает и как предотвратить на любом языке — flawopen.com Защита от Path Traversal в Python os.path.join() Что такое уязвимость Zip Slip? Node.js path.join() не защищает от Path Traversal