Все мои клиентские приложения под управлением PHP 8.4 в Docker были обновлены до версии 8.4.27, протестированы и перезапущены менее чем за четыре часа. Большую часть времени заняло ожидание, и самая медленная часть процесса никак не была связана с кодом.

Если вы пропустили прошлую неделю: в PHP выпустили экстренные исправления для двух критических уязвимостей удаленного выполнения кода без аутентификации, получивших общее название PHP2Shell. Спустя два дня была раскрыта третья, до сих пор не исправленная уязвимость. Вот хронология событий.

Дата (UTC) Что произошло
26 октября CVE-2026-109618 и CVE-2026-109619 добавлены в каталог эксплуатируемых уязвимостей CISA Known Exploited Vulnerabilities
23 октября PHP раскрывает CVE-2026-115560, исправления пока нет, временное решение — через конфигурацию
21 октября, 14:00 PHP выпускает версии 8.5.12, 8.4.27, 8.3.36 и 8.2.35
21 октября, 07:43 Cloudflare сообщает о первых атаках, за шесть часов до выхода исправления
20 октября В соцсетях утекают подробности до запланированного раскрытия

Обе уязвимости от 21 октября оценены в 9.2 балла (CVSS v4.0) и затрагивают все версии PHP вплоть до 5.4. Никакой авторизации или специфического кода приложения не требуется.

Почему Laravel вас не защитил

Атака бьет по движку PHP в момент парсинга входящего запроса, еще до того, как выполнится первая строка вашего кода на Laravel. Ваши мидлвары, поліси и правила валидации даже не успевают ее увидеть.

Типичная реакция: «У Laravel собственный драйвер сессий, так что уязвимости сессий меня не касаются, верно?» Касаются. Известный вектор атаки проходит через функцию отслеживания прогресса загрузки файлов в сессиях PHP (session upload progress), которая обрабатывается движком во время парсинга multipart-запросов. Драйвер сессий Laravel находится на уровень выше и здесь не имеет значения.

Два важных последствия для приложения на Laravel или Filament:

  •  Обновление PHP — единственное полное исправление. PHP прямо заявляет об этом в своих релизных заметках (php-src 8.4.27).
  •  Отключение  session.upload_progress.enabled  блокирует известные пути атаки, но не все варианты. Кроме того, этот параметр должен быть задан в  php.ini  или пуле FPM: вызовы  .user.ini  или  ini_set()  срабатывают слишком поздно в жизненном цикле запроса. Сам Laravel эту функцию не использует, так что вы ничего не потеряете, отключив её.

Почему  php artisan down  ни от чего не защитил

Я видел, как многие сайты на Laravel перевели в режим обслуживания (maintenance mode) 21 октября. Это дало им красивую страницу 503 и нулевую защиту.

Режим обслуживания работает через файл  storage/framework/down , который проверяет  public/index.php . Чтобы выполнить эту проверку, запрос должен дойти до PHP-FPM. А PHP парсит multipart-тело запроса, включая прогресс загрузки, прямо на этапе инициализации запроса — до выполнения первой строки  index.php . Атака происходит раньше, чем Laravel понимает, что он «лежит».

Реальную защиту обеспечивает только запрос, который вообще не доходит до PHP. До выхода патча варианты были следующими (от самых простых к сложным):

  1.   session.upload_progress.enabled = 0  в конфигурации PHP. Применяется за минуты, сайт остается доступен, но блокирует только известные векторы атак.
  2.  Страница 503 на уровне веб-сервера. Блокировка через  return 503;  в Nginx до передачи запроса в  fastcgi_pass  вместе со статической страницей. Полная защита, а посетители получают корректное сообщение.
  3.  Полное отключение сайта. Та же защита, что и в варианте 2. Несколько голландских сайтов, включая NCSC, сделали именно так 21 октября.

Как я пропатчил приложения клиентов

Большинство моих клиентов используют PHP 8.4, всё работает в Docker на  php:8.4-fpm-alpine  вместе с GitLab CI/CD. Целью была версия 8.4.27. Вот как выглядел этот день.

  1.  Дождаться официального образа. Релиз PHP состоялся, но Docker-образ собирался с задержкой. Сначала новые теги появились без готовых образов за ними — казалось, что их опубликовали, а затем пересобрали. Я ждал до тех пор, пока команда pull реально не скачала 8.4.27.
  2.  Пересобрать с новым базовым образом. Мои сборочные джобы всегда подтягивают базовый образ заново, поэтому при использовании плавающего тега вроде  8.4-fpm-alpine  пересборка подтянула именно 8.4.27, а не устаревший слой из кеша. Без этого принудительного пулла «зеленый» пайплайн мог бы выкатить старый PHP.
  3.  Запустить полный CI-пайплайн без изменений. Проверки безопасности, юнит-тесты, фича-тесты и сканирование контейнеров в GitLab. Минорный релиз PHP не должен ничего ломать, но экстренный релиз — это как раз тот случай, когда лучше проверить, чем слепо надеяться.
  4.  Деплой через стандартный CD-пайплайн. Никаких ручных хотфиксов на серверах. Пересоздание контейнеров также перезапускает воркеры очередей и планировщики, иначе они продолжали бы удерживать старый PHP в памяти.

Итог: менее четырех часов для всех клиентов.

 Настоящим узким местом стал PECL. Во время пересборок сервер pecl.php.net перестал отвечать. Мое предположение, которое я не могу доказать: полмира PHP одновременно пересобирали образы, и каждый Dockerfile с командами  pecl install xdebug  или  pecl install imagick  долбился на один и тот же сервер. Мои пайплайны не могли завершиться, пока он не ожил.

CVE от 23 октября и обновление стартовых шаблонов

Стартовые сборки Filament Mastery теперь поставляются с отключенным прогрессом загрузки сессий. Стандартное Laravel-приложение вряд ли подвержено уязвимости от 23 октября, и это изменение сделано в рамках концепции эшелонированной защиты (defense in depth).

Для CVE-2026-115560 патча еще нет. PHP выпустит его в следующем регулярном релизе. Обновление до 8.4.27 эту уязвимость не закрывает. Приложение уязвимо только при одновременном выполнении трех условий:

  1.  session.serialize_handler  установлен в значение  php_binary  или  php_serialize 
  2. код переключает его в  php  с помощью  ini_set()  до вызова  session_start() 
  3. отслеживание прогресса загрузки сессий включено (это поведение по умолчанию в PHP)

Стандартная работа с сессиями в Laravel обычно не задействует нативный флоу  session_start()  в PHP. Тем не менее, сторонние пакеты или кастомный код вполне могут это делать, поэтому проверьте свой стек зависимостей, прежде чем заявлять, что приложение в безопасности. Рекомендуемое PHP временное решение совпадает с настройкой, которую уже советовали применять для CVE-2026-109619 еще 21 октября. Одна строчка закрывает обе проблемы, поэтому в стартовых шаблонах Filament Mastery она включена по умолчанию.

 ; docker/common/php-fpm/conf.d/php.ini
; PHP2Shell (CVE-2026-109619) and CVE-2026-115560 mitigation
session.upload_progress.enabled=Off

 

Скопировано в образ:

 RUN if ["$APP_ENV" = "local"] ; then \
        cp "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" ; \
    else \
        cp "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" ; \
    fi

COPY docker/common/php-fpm/conf.d/php.ini "$PHP_INI_DIR/conf.d/custom.ini"
 

Полная конфигурация с уже примененными настройками доступна в моем руководстве Production-ready Docker setup for Laravel Filament, которое было обновлено сегодня.

Официальный образ читает эту директорию как для CLI, так и для FPM. Если вы используете PHP 8.5, также добавьте  fatal_error_backtraces = Off : PHP указывает это как второе средство минимизации для CVE-2026-109619 на данной версии. Сохраняйте обе настройки даже после патча, пока PHP не выпустит исправление для CVE-2026-115560.

 Стартовые шаблоны Filament Mastery обновлены и уже доступны, эта конфигурация включена в них по умолчанию:

Laravel Filament Backend Starter

Laravel Filament Multipanel Starter

Laravel Filament Multi-Tenant Starter

Чек-лист для ваших приложений на Laravel и Filament

  • [] Версия PHP равна 8.5.12, 8.4.27, 8.3.36 или 8.2.35:  docker compose exec php-fpm php -v 
  • [] На PHP 8.1 или более старых версиях  php -v  показывает один и тот же номер до и после бэкпорта от дистрибутива: проверяйте номер ревизии пакета, а затем планируйте обновление (Laravel Forge)
  • [] Образы пересобраны с чистым пулом базового образа, а не из кеша
  • [] Воркеры очередей, Horizon, Octane и Reverb перезапущены (пересоздание контейнеров делает это автоматически)
  • [] Прогресс загрузки отключен и задан в  php.ini  или  conf.d , а не в  .user.ini :  docker compose exec php-fpm php -i | grep session.upload_progress.enabled  возвращает  Off 
  • [] На PHP 8.5 параметр  fatal_error_backtraces  установлен в  Off 
  • [] Каждая версия PHP на сервере пропатчена, включая те, которые в данный момент не используются ни одним сайтом