PHP2Shell: критические уязвимости PHP и защита Kubernetes
21 октября 2026 года команда PHP выпустила экстренные патчи для двух критических уязвимостей с общим названием PHP2Shell — CVE-2026-109618 и CVE-2026-109619, обе с оценкой 9.2 по CVSS v4.0. Они позволяют неаутентифицированному злоумышленнику удалённо выполнить код почти на любом PHP-сервере, следует из разбора российского вендора защиты контейнеров Luntry.
Краткий ответ
PHP2Shell — пара уязвимостей в ядре PHP: повреждение памяти при нехватке памяти (OOM) и use-after-free между запросами в одном worker-процессе. По данным Luntry, они затрагивают почти все версии PHP за последнее десятилетие и все способы обслуживания запросов — PHP-FPM, mod_php, php-cgi, FrankenPHP, LiteSpeed. Единственное полное решение — обновление до исправленной версии. Пока патч не раскатан, поведенческий Runtime-контроль способен заметить аномальный процесс внутри контейнера и остановить атаку.
Что известно об уязвимостях PHP2Shell
CVE-2026-109618 (GHSA-w8hw-w9fx-j79w) — RCE через повреждение памяти. Если запрос упирается в memory_limit в неудачный момент, PHP оставляет внутренние данные частично инициализированными, а последующая очистка трактует их как валидные. Атакующий получает возможность подставить контролируемые данные туда, где PHP ожидает собственную служебную структуру.
CVE-2026-109619 (GHSA-6wmf-4rv5-p3rp) — RCE через устаревшее глобальное состояние. Фатальная ошибка в неудачный момент оставляет указатель на уже освобождённую память. Следующий запрос в том же worker-процессе обращается к этому указателю до штатной инициализации — классический use-after-free, только не в пределах одного запроса, а между соседними запросами одного процесса.
Общий знаменатель объясняет, почему проблема не привязана к конкретному фреймворку или прикладной библиотеке. PHP-FPM, mod_php и большинство других SAPI держат долгоживущие worker-процессы: один процесс обслуживает сотни и тысячи запросов подряд, переиспользуя память между ними ради скорости. Обе уязвимости PHP2Shell бьют именно в эту модель — прерванный аварийно запрос оставляет повреждённое или устаревшее внутреннее состояние, которое затем переиспользуется в том же процессе. Проблема лежит в том, как PHP обрабатывает фатальные ошибки во время выполнения запроса, а не в коде конкретного приложения.
Уязвимости найдены в рамках скоординированного раскрытия. Одну обнаружила команда Calif в ходе аудита PHP по программе OpenAI «Patch the Planet», вторую независимо нашла и отправила компания Wiz. Патчи выпустила команда PHP при поддержке PHP Foundation. По данным Luntry, обе уязвимости выявлены с помощью AI-ассистентов, а за день до планового раскрытия идентификаторы CVE и часть сведений преждевременно попали в публичный доступ — это сократило окно между официальным анонсом и появлением рабочих эксплойтов.
Почему PHP2Shell критичен для инженерных команд
По оценке Luntry, на PHP работает порядка 70% сайтов, язык которых известен — эта цифра приводится в разборе вендора без ссылки на отдельный независимый замер. Уязвимости затрагивают все способы обслуживания web-запросов — PHP-FPM, Apache с mod_php, php-cgi, FrankenPHP и LiteSpeed, причём наибольший риск, по оценке команды PHP, несёт mod_php: там неаутентифицированный запрос может сработать ещё до выполнения прикладного кода. Затронута практически каждая версия PHP за последнее десятилетие, вплоть до 5.4, а уязвимо даже приложение уровня «Hello World» — проблема лежит ниже уровня прикладного кода, и конкретная применимость пути зависит от версии PHP, способа обслуживания запросов и конфигурации.
На момент выхода исправлений watchTowr сообщал о попытках эксплуатации «в дикой природе». Huntress на тот же момент не фиксировал массовой эксплуатации в своей базе. Подтверждённых фактов компрометации серверов на момент раскрытия, по данным Luntry, не приводилось. Тем не менее именно окно между публикацией CVE и установкой патча на конкретном сервере остаётся периодом наибольшего риска: рабочий эксплойт уже существует, а в крупных парках инфраструктуры обновление растягивается на дни и недели — согласовать окно обслуживания, прогнать регрессию, выкатить по очереди на все кластеры получается не мгновенно, даже если патч формально уже доступен.
Почему одного сканирования образов недостаточно
Сканирование образов необходимо, но его одного мало. Уязвимая версия PHP может оказаться в кластере в обход проверок — через сторонний образ, быстрое развёртывание или уже работающую нагрузку, которую ещё не успели обновить. Патч может выйти с задержкой или потребовать согласованного окна обслуживания.
PHP-приложения на платформе собираются из множества базовых образов, и неочевидно, какие именно из работающих нагрузок содержат уязвимую версию интерпретатора: один и тот же сервис может пересобираться из разных базовых слоёв в разных окружениях, а легаси-нагрузки годами живут без пересборки. Поэтому к сканированию стоит добавить два уровня: инвентаризацию работающих нагрузок, чтобы понимать, что именно уязвимо прямо сейчас, и контроль поведения в Runtime, который, по описанию Luntry, обнаруживает эксплуатацию независимо от того, установлен ли патч.
Как Luntry воспроизвела эксплуатацию PHP2Shell в Kubernetes
Чтобы показать, как эксплуатация выглядит со стороны защиты, инженеры Luntry воспроизвели её на собственном стенде. За основу взяли публичный эксплойт php2shell-bypass-aslr-exp, нацеленный на официальный образ php:8.4.26-apache — версию до исправления 8.4.27. По их описанию, под docker-compose эксплойт отрабатывал «как есть», но при прямом переносе в Kubernetes Pod эксплуатация не воспроизводилась.
Причина, как объясняет Luntry, в том, что цепочка эксплойта чувствительна к распределению памяти в процессе, а окружение Pod'а отличается от docker-compose. Для детерминированного воспроизведения потребовалось несколько правок в конфигурации ConfigMap'ов и Deployment.
Переменные окружения и $_ENV
В ini-конфигурации задали variables_order="GPCS", отключив заполнение переменной $_ENV. Официальный образ использует EGPCS и пересобирает $ENV при каждом запросе; Pod несёт дополнительные переменные окружения KUBERNETES*, которых нет в docker-compose, и эти лишние записи сдвигают последовательность выделений в аллокаторе памяти Zend MM — причём сдвигается не только носитель эксплойта, но и последовательность, которая его готовит. Отказ от заполнения $_ENV делает распределение памяти на запрос независимым от окружения и совпадающим с эталоном docker-compose, на который откалибрована цепочка.
Service-link переменные
В Deployment выставили enableServiceLinks: false — по той же причине: чтобы Kubernetes не внедрял переменные вида <SERVICE>_*, которые тоже смещают последовательность выделений памяти.
Один Pod и один worker
Цепочка работает в пределах одного адресного пространства и опирается на ротацию единственного prefork-worker'а Apache. Поэтому в стенде задали replicas: 1 со стратегией Recreate, а в конфигурации Apache — ServerLimit 1, MaxRequestWorkers 1 и KeepAlive Off. Второй Pod или второй worker — это другое адресное пространство, и воспроизведение ломается.
Health-пробы и архитектура
Пробы readiness и liveness пришлось убрать: httpGet-проба обслуживается тем же единственным worker'ом и попадает между запросами эксплойта, запуская штатную инициализацию и завершение запроса, которые возмущают состояние процесса — и попытка промахивается. В docker-compose проб нет, поэтому там всё срабатывало, а в Pod'е с пробами — нет. Смещения в памяти зависят от архитектуры процессора, поэтому узел должен исполнять linux/amd64.
С этими изменениями стенд воспроизводится детерминированно, и запуск публичного эксплойта против него приводит к выполнению команды в контейнере от имени www-data. Показательно, что сама хрупкость цепочки — она ломается от второго worker'а, health-пробы или нескольких лишних переменных окружения — уже наблюдаемое с точки зрения защиты свойство: успешная эксплуатация требует очень специфичных условий и сопровождается аномальной активностью, на которую опирается Runtime-контроль.
Как работает поведенческая защита в Runtime
Runtime-политики Luntry построены на анализе поведения процессов и приложений во время выполнения, а не на сигнатурах конкретных атак. Легитимное PHP-приложение ведёт себя предсказуемо: worker-процесс обслуживает запросы и не порождает сторонних процессов. Успешная эксплуатация PHP2Shell, как и любого RCE, нарушает эту модель — в контейнере появляется новый процесс, shell или интерпретатор, запущенный из web-контекста, за которым следуют разведка, закрепление и сетевые обращения к внешним адресам.
По описанию Luntry, такой поведенческий подход фиксирует отклонение независимо от того, известна ли конкретная CVE на момент атаки. На тестовом стенде это выглядело так: как только автоматическая реакция сработала на подозрительную активность, Luntry остановила уязвимый контейнер прямо в ходе эксплуатации — следующий запрос ротации worker'а упёрся в уже остановленный контейнер, соединение было отклонено, и эксплойт аварийно завершился.
Помимо реакции на аномальное поведение, платформа описывает детектирование типовой пост-эксплуатационной активности — порождение shell процессом web-сервера, попытки reverse-shell, разведывательные команды — и сканирование образов на разных этапах, от сборки до уже работающих нагрузок, чтобы находить уязвимые версии PHP раньше, чем они попадут в прод или задержатся там дольше нужного. Luntry отдельно отмечает, что сама компания не использует PHP ни в одном своём компоненте, поэтому PHP2Shell не создаёт угрозы для продуктов вендора напрямую — речь идёт о защите PHP-нагрузок клиентов.
Что делать инженерным командам сейчас
Обновление до исправленной версии остаётся единственным полным решением.
| Ветка PHP | Статус | Действие |
|---|---|---|
| 8.5 | Поддерживается | Обновить до 8.5.12 |
| 8.4 | Поддерживается | Обновить до 8.4.27 |
| 8.3 | Поддерживается | Обновить до 8.3.36 |
| 8.2 | Поддерживается (EOL 31.12.2026) | Обновить до 8.2.35 |
| 7.4 / 8.0 / 8.1 | Не поддерживается проектом | Минимальные бэкпорты, проверить пакеты у вендора или дистрибутива |
Пока обновление не завершено, Luntry рекомендует провести инвентаризацию работающих нагрузок — определить, какие именно сервисы и контейнеры сейчас используют уязвимую версию PHP, — и включить контроль поведения в Runtime для этих нагрузок на время обновления, чтобы не ждать завершения патч-цикла по всему парку, прежде чем получить хоть какую-то защиту.
Если немедленное обновление невозможно, команда PHP допускает временные частичные меры: memory_limit=-1 и низкий post_max_size для CVE-2026-109618, session.upload_progress.enabled=0 для CVE-2026-109619, а на PHP 8.5 — дополнительно fatal_error_backtraces=0. Команда PHP прямо подчёркивает, что эти настройки лишь снижают поверхность атаки по отдельным векторам и не заменяют патч.
Почему это важно
Эпизод с PHP2Shell показывает, что сканирование образов и патч-менеджмент сами по себе не закрывают окно между раскрытием уязвимости и обновлением продакшн-нагрузок в контейнерах. По словам Luntry, поведенческий контроль в Runtime помогает прикрыть это окно, пока патч ещё не раскатан по кластеру. Для инженерных команд это не повод отказываться от обновления — единственного полного решения, — а аргумент держать поведенческий слой защиты как постоянную часть эксплуатации, а не разовую меру на время конкретного инцидента.
Что дальше
Поддерживаемые ветки PHP 8.2–8.5 уже получили исправления; ветка 8.2 остаётся поддерживаемой до конца 2026 года, после чего командам, которые на ней сидят, придётся планировать переход на более новую линейку заранее, а не в авральном режиме при следующей критической уязвимости. Для веток 7.4, 8.0 и 8.1, которые PHP больше не поддерживает официально, решение зависит от вендора дистрибутива — если такие версии ещё эксплуатируются в продакшне, это стоит проверить отдельно и не откладывать миграцию на более новую ветку.
FAQ
Что такое PHP2Shell?
Это общее название для двух критических уязвимостей PHP — CVE-2026-109618 и CVE-2026-109619 с оценкой 9.2 по CVSS v4.0, которые позволяют неаутентифицированному злоумышленнику удалённо выполнить код на сервере из-за повреждения памяти и устаревшего состояния между запросами в одном worker-процессе.
Можно ли полагаться только на временные меры из advisory PHP?
Нет. Команда PHP прямо указывает, что полноценных обходных мер не существует и обновление — единственный безопасный выбор. Временные настройки снижают экспозицию по отдельным векторам, но не делают установку безопасной.
Поможет ли runtime-защита, если патч ещё не установлен?
По описанию Luntry — да: поведенческие аномалии и правила обнаружения не зависят от сигнатуры конкретной CVE и срабатывают на сам факт нетипичной активности после эксплуатации, поэтому работают в окне между раскрытием и обновлением.
Затрагивает ли PHP2Shell приложения без загрузки файлов?
Да. Риск не ограничивается приложениями с загрузкой файлов — уязвим даже минимальный код, а конкретная применимость пути зависит от версии PHP, способа обслуживания запросов (SAPI) и конфигурации.
Нужно ли обновлять PHP, если в инфраструктуре уже есть runtime-защита?
Да. Runtime-инструменты помогают найти уязвимые нагрузки и прикрыть их на время обновления, но обновление PHP до исправленной версии остаётся обязательным шагом.