Несмотря на зрелость PHP и большую экосистему, многие разработчики упускают из виду редкие уязвимости, скрывающиеся в стандартных и малоизученных механизмах языка. Изучив их, вы сможете укрепить безопасность приложений и предотвратить неожиданные атаки.
1. Введение
PHP – один из самых популярных языков веб-разработки. Благодаря экосистеме фреймворков (Laravel, Symfony, Yii) и обширному опен-сорсу он остаётся выбором №1 для быстрого вывода приложений. При этом сообщество в основном концентрируется на классических уязвимостях (SQL-инъекции, XSS, CSRF, LFI/RFI). Тонкие особенности интерпретатора, малоизученных расширений и встроенных механизмов нередко остаются вне поля зрения. В этой статье мы подробно рассмотрим малоизвестные опасности и способы защиты от них.
2. Уязвимость сериализации объектов: wakeup() и destruct()
Магические методы wakeup() и destruct() вызываются при десериализации и уничтожении объекта соответственно. В сочетании с неконтролируемыми данными это позволяет запускать побочные эффекты и вредоносный код.
Расшире��ный сценарий
Модификация конфигураций – через свойства объекта подставляются пути к конфигам, и в
__wakeup()выполняетсяinclude/require.Потенциальный побочный эффект — при десериализации злоумышленник может вызвать деструктор объекта, внутри которого выполняется
curl_exec(), отправляющий данные на внешний сервер.Отложенный запуск – с помощью
register_shutdown_function()внутри сериализуемых объектов можно отложить выполнение до завершения скрипта, что осложняет отладку.
Пример
<?php
class Notifier {
private $url;
private $payload;
public function __construct(string $url, array $payload) {
$this->url = $url;
$this->payload = $payload;
}
public function __wakeup() {
// Отправка запроса на внешний API (опасно при неконтролируемых данных)
$ch = curl_init($this->url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $this->payload);
curl_exec($ch);
curl_close($ch);
}
public function __destruct() {
// Очистка временного файла (может удалить любой путь)
if (!empty($this->payload['file'])) {
@unlink($this->payload['file']);
}
}
}
Если злоумышленник контролирует сериализованную строку, он может подставить URL атакующего и путь к конфиденциальному файлу.
Защита
Никогда не вызывать
unserialize()на данных от пользователя.При необходимости использовать опцию
allowed_classesдля белого списка допустимых классов.Переходить на безопасные форматы сериализации: JSON (с валидацией по схеме), YAML с валидацией.
Разделять десериализацию и критичные операции: валидировать свойства перед выполнением побочных эффектов.
Минимизировать использование магических методов в классах, которые могут принимать внешние данные.
3. «Промежуточные» атаки через stream-фильтры
Stream-фильтры позволяют обрабатывать данные «на лету». При некорректной проверке параметров можно получить доступ к исходному коду, конфигам и даже базе данных.
Примеры атак
php://filter/convert.iconv.UTF-8/CP1251/resource=config.php– чтение файла в другой кодировке и дальнейшая инъекция.php://filter/zlib.inflate/resource=backup.sql.gz– распаковка дампа БД «на лету».
Защита
Запрет любых
php://иdata://префиксов при приёме имён файлов: проверять регуляркой, напримерpreg_match('/^[a-z0-9_\-]+\.php$/i', $file).Всегда применять
realpath()и сверять директорию:strpos(realpath($file), BASE_PATH) === 0.-
Отключить в
php.iniпараметры, если не требуются:o
allow_url_include = Offo
allow_url_fopen = Off(если не нужен доступ к URL-ресурсам) На уровне приложения: валидировать и нормализовать пути, использовать белые списки.
4. Побочные эффекты слабой типизации при сравнении ключей массивов
PHP автоматически преобразует строковые числовые ключи в целые, что приводит к коллизиям при создании массивов.
Демонстрация
$data = [
"00" => "zero",
0 => "int_zero",
0.0 => "float_zero",
false => "bool_zero",
null => "null_zero"
];
var_export($data); // ['0' => 'null_zero']
Все записи сольются, и в массиве останется лишь последний элемент.
Угрозы
Неправильная агрегация ролей при хранении флагов.
Фильтрация/поиск по ключу может давать ложные срабатывания.
Рекомендации
Приводить ключи к строковому виду:
$key = (string)$key;-
Использовать объекты или структуры SPL:
o
SplFixedArrayдля индексированных коллекций;o
SplObjectStorageдля хранения объектов-ключей. Явно проверять типы (
===) при сравнениях.
5. Тайм-ауты и неожиданное поведение c URL-мультизапросов
curl_multi_exec() может блокировать всю группу запросов из-за одного «зависшего» дескриптора.
Особенности
При возврате
curl_multi_select()значения-1дескриптор может быть испорчен.Без обработки
CURLM_CALL_MULTI_PERFORMцикл может зациклиться.
Пример безопасного шаблона
$mh = curl_multi_init();
foreach ($urls as $url) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 3000);
curl_multi_add_handle($mh, $ch);
}
do {
while (($mrc = curl_multi_exec($mh, $active)) === CURLM_CALL_MULTI_PERFORM);
if ($mrc !== CURLM_OK) break;
while ($info = curl_multi_info_read($mh)) {
$ch = $info['handle'];
if ($info['result'] !== CURLE_OK) {
// логирование ошибки
}
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
if ($active) {
$select = curl_multi_select($mh, 1.0);
if ($select === -1) {
// исправление «ошибочных» сокетов
usleep(100);
}
}
} while ($active);
curl_multi_close($mh);
6. Потенциальные дыры в intl-расширении
Расширение intl опирается на ICU; уязвимости в ICU могут приводить к краху процесса или более серьёзным проблемам.
Кейсы
Переполнение буфера при форматировании сложных скриптов (арабский, иврит).
Ошибки в разборе даты в нестандартных локалях (например,
fa_IR).
Как быть
Мониторить CVE для ICU и обновлять ICU-библиотеку и пакеты ОС.
Ограничивать длину и набор символов у пользовательских шаблонов через
preg_match.Обрабатывать
IntlExceptionи не допускать «fallback» на дефолтные локали без уведомления.
7. «Невидимые» XSS через невидимые юникод-символы
Невидимые символы (ZWJ, ZWNJ, NBSP, RLM, LRM и т. п.) позволяют «расщеплять» ключевые слова HTML-атрибутов и обходить фильтрацию.
Пример
Атака с вставкой невидимого символа:
<img src=x on\u200Dmouseover=alert(1)>
Браузеры объединяют символы и могут выполнить onmouseover.
Нормализация и защита
Использовать
Normalizer::normalize($str, Normalizer::FORM_C).Удалять все управляющие/неотображаемые символы:
preg_replace('/\p{Cf}/u','',$str).Применять whitelist-фильтрацию и специализированные библиотеки – например, HTML Purifier с кастомным набором тегов/атрибутов.
8. Утечки данных через pcntl в многопроцессном режиме
Процессы, созданные через pcntl_fork(), наследуют память родителя (copy-on-write). Без явного удаления секретов и запуска сборщика мусора конфиденциальная информация остаётся доступной в адресном пространстве дочерних процессов.
Последствия
Утечка API-ключей, паролей, токенов в дочерних процессах;
Накопление «мусора» и рост памяти при некорректном завершении дочерних процессов.
Безопасный шаблон использования fork
<?php
$sensitive = 'SECRET';
$pid = pcntl_fork();
if ($pid === -1) {
// ошибка fork
exit(1);
} elseif ($pid === 0) {
// дочерний процесс
unset($sensitive); // удалить секреты из видимой области
gc_enable();
gc_collect_cycles(); // попытаться освободить память
// выполнить задачу...
exit(0);
} else {
// родитель
pcntl_wait($status);
}
Рекомендации
Удалять (unset) все переменные с конфиденциальной информацией перед
fork, если это возможно.Использовать минимальный набор глобальных переменных в родительском процессе.
Ограничивать продолжительность жизни дочерних процессов и корректно их завершать/перезапускать.
Рассмотреть альтернативы (системные демоны, очереди, RoadRunner, Swoole).
9. Особенности OPcache, ведущие к гонкам и потенциальному RCE при динамическом создании кода
OPCache кэширует байткод файлов. При динамическом создании/перезаписи скриптов возможна гонка между записью на диск и инвалидацией кэша – приложение может выполнить старую (или неконсистентную) версию файла.
Пример проблемы
file_put_contents('/tmp/job.php', $code);include '/tmp/job.php';
Если OPcache ещё содержит старую версию или не успел корректно её инвалировать – может выполниться некорректный/неожиданный байткод.
Меры предосторожности
После записи файла вызывать
opcache_invalidate('/tmp/job.php', true);Настроить
opcache.revalidate_path,opcache.validate_timestampsиopcache.file_update_protectionв соответствии с моделью деплоя.Избегать записи исполняемых PHP-скриптов в общедоступные временные директории – лучше хранить код в версиях/деплое и использовать безопасные механизмы очередей или планировщиков задач.
При динамическом исполнении кода обрабатывать содержимое как данные, а не как исполняемый файл (парсить/проверять / использовать безопасные контейнеры).
10. Риск расширений с C-модулями
Расширения на C (Imagick, ZipArchive, др.) работают на уровне нативного кода. Ошибки в нём приводят не только к «крашу» скрипта, но и к падению интерпретатора или потенциальному выполнению произвольного кода.
Критичные кейсы
Обработка вредных/повреждённых бинарных форматов изображений (ImageMagick /
imagick) – возможный RCE/DoS.Чтение повреждённых ZIP – переполнение/крах в
ZipArchive.Уязвимости в нативных драйверах для БД.
Профилактика
Ограничивать входной набор форматов и максимальный размер файлов на уровне веб-сервера (NGINX/Apache) и приложения.
Запускать обработку бинарных данных в изолированных контейнерах (chroot, контейнеры, отдельные сервисы) или с пониженными привилегиями.
Использовать AppArmor/SELinux для ограничения прав.
Автоматически отслеживать CVE и обновлять расширения/библиотеки.
11. Особенности PHP-плагинов в CMS (WordPress / Joomla / Drupal)
Плагины часто пишут непрофессионалы, в результате появляются скрытые бэкдоры, небезопасный eval, открытые REST-эндпоинты и неправильно настроенные хуки.
Типичные угрозы
eval(base64_decode(...))в коде плагина (часто встречается в админской панели);REST-эндпоинты, принимающие команды shell;
Переопределение хуков и интеграция злоумышленного кода через
add_action/hook_node_view.
Практические меры
Жёсткие права на каталог
uploads(запрет выполнения файлов), минимизация прав на файлы.Ежедневный/регулярный скан плагинов: WPScan, специализированные сканеры для Joomla/Drupal.
Настройка CSP (Content-Security-Policy), ограничение внешних скриптов и источников.
Ревью кода сторонних плагинов перед установкой; использовать только проверенные плагины и держать их в актуальном состоянии.
12. Особенности файловой системы и автозагрузки (Composer, PSR-4)
Composer-автолоадер (PSR-4, classmap, ключ files) даёт гибкость, но при неверных настройках возможна загрузка нежелательных файлов или затенение классов.
Проблемы
Дублирование классов и «затенение» безопасных реализаций.
Использование
filesвcomposer.jsonможет подключать произвольный код во время загрузки.
Защита
Минимизировать использование
files, отдать предпочтение PSR-4.Для production-среды включить
classmap-authoritativeиoptimize-autoloader.Проводить
composer validateи ревьюcomposer.jsonпри PR/деплое.
13. Уязвимости в фоновых очередях (Gearman, RabbitMQ и др.)
Payload задач в очередях часто не валидируются строго: в них могут попадать исполняемые фрагменты, шелл-команды или сериализованные объекты.
Реальные атаки
Передача аргументов, которые используются в
exec()/system().Внедрение PHP-кода в тело задачи, который затем выполняется через
eval()/include.
Защита
Валидировать и хранить payload в безопасном формате (JSON с JSON-schema).
Разделять права: выделенный системный пользователь для демонов очередей с минимальными правами доступа.
Логирование и мониторинг аномалий в payload.
14. Побочные эффекты при использовании eval() и assert()
eval() и (в старых версиях) assert() при включённых опциях выполняют строковый PHP-код – это прямой путь к RCE при недостаточной фильтрации.
Примеры риска
assert($_GET['code']);– исполнение пользовательского кода при старом синтаксисе.eval("return {$userCode};");– обход ограничений через вложенные кавычки и спецсимволы.
Рекомендации
Полностью отказаться от
eval()/опасныхassert()и заменить их безопасными альтернативами.Для анализа/выполнения выражений использовать парсер (например,
nikic/php-parser) и whitelist-подход к AST.Если нужен «песочникообразный» парсинг – выносить выполнение в изолированный процесс/контейнер.
15. Заключение и краткий чеклист (ключевые меры)
Избегать небезопасной десериализации: использовать JSON/YAML + валидацию,
allowed_classesприunserialize().Нормализовать и фильтровать входные данные: stream-фильтры, юникод-символы, HTML-атрибуты.
Устанавливать явные таймауты для сетевых запросов и форков, контролировать дескрипторы c URL.
Регулярно обновлять PHP-расширения, ICU, Composer-зависимости, отслеживать новые CVE.
Ограничивать обработку бинарных данных в C-модулях и запускать их в изоляции (контейнеры, AppArmor/SELinux).
Аудитировать плагины CMS, применять жёсткие права доступа на файловой системе.
Избегать
eval()/assert(), минимизировать динамическую автозагрузку файлов.Во всех фоновых механизмах (Gearman, RabbitMQ и т. п.) – валидировать payload через схемы и изолировать процессы.
Внимание к деталям и понимание внутренних механизмов PHP и его расширений помогут значительно повысить безопасность приложений и защититься от редких, но опасных атак.
shuchkin
ха, да, еще не оставляйте
config.php.bakна продеFragster
ну всякую конфигурацию можно вынести на уровень выше папки веб публикации и забыть (в определенной мере, конечно) про этот класс уязвимостей (таких как доступность /.git и прочих служебных файлов с чувствительной информацией, что бывает намного чаще).