Pterohost docs

PHP2Shell (CVE-2026-109618, CVE-2026-109619): как закрыть уязвимость, в том числе на PHP 7.4 и 8.1

Как проверить и закрыть PHP2Shell на PHP-FPM, LiteSpeed и Apache: версии с исправлением, бэкпорты для PHP 7.4 и 8.1, тест патча и временные меры.

Кратко: PHP2Shell - две критические уязвимости в ядре PHP (CVE-2026-109618 и CVE-2026-109619), которые позволяют выполнить код на сервере без авторизации. Исправление - PHP 8.2.35, 8.3.36, 8.4.27, 8.5.12 или пакет с бэкпортом для 7.4 и 8.1. После обновления обязательно перезапустите все процессы PHP и проверьте, что патч действительно стоит: у бэкпортов номер версии не меняется.

PHP2Shell: как закрыть CVE-2026-109618 и CVE-2026-109619

21 октября 2026 года команда PHP выпустила внеплановые релизы. Они закрывают две уязвимости в самом движке, не в фреймворках и не в CMS. По оценке NVD у CVE-2026-109618 CVSS 3.1 - 9.8, то есть удалённое выполнение кода без логина. Затронут почти весь PHP последнего десятилетия: от 5.4 до 8.5. Ниже - что именно сломано, какие версии безопасны, как обновить PHP-FPM, LiteSpeed и Apache, как обновить старые 7.4 и 8.1, которые официально уже не поддерживаются, и как убедиться, что патч реально работает. Всё проверено на нашей инфраструктуре в день выхода исправлений.

Pterohost - выделенные серверы на Ryzen 9 и EPYC с DDoS-защитой и поддержкой 24/7. Подобрать выделенный сервер

Что за уязвимости

CVEСутьCVSS 3.1 (NVD)Advisory
CVE-2026-109618При нехватке памяти PHP выходит из текущего контекста через longjmp() и оставляет память запроса в недоинициализированном состоянии. Процедура завершения запроса потом читает этот мусор9.8GHSA-w8hw-w9fx-j79w
CVE-2026-109619Глобальное состояние (ошибки, сессии, объекты, расширения) продолжает указывать на память уже завершённого запроса. Следующий запрос в том же воркере использует освобождённую память (use-after-free)8.1GHSA-6wmf-4rv5-p3rp

Главная опасность первой уязвимости - память можно исчерпать снаружи, ничего не зная о приложении. В advisory описаны три способа, и все через разбор multipart-тела запроса:

  • multipart-запрос без корректного Content-Length проходит начальную проверку post_max_size и продолжает отдавать данные;
  • у одной части multipart может быть сколько угодно заголовков, и в памяти PHP они занимают больше, чем на проводе;
  • пустые поля файлов не считаются в max_file_uploads, но всё равно создают массивы метаданных.

Всё это происходит до того, как начинает выполняться ваш код. Поэтому аккуратное приложение не спасает: уязвим сам интерпретатор.

Насколько опасно для разных способов запуска PHP

Способ запускаОценка команды PHP
Apache + mod_phpСамый опасный: эксплойт подтверждён, срабатывает до кода приложения
PHP-FPM (nginx, Apache proxy_fcgi)Уязвим. FPM завершает воркер, если старт запроса упал, но OOM после старта остаётся опасным
LiteSpeed / OpenLiteSpeed (LSAPI)Устроен как FPM, эксплуатация считается вероятной
FrankenPHPКак mod_php, вероятно уязвим независимо от приложения
CLI (воркеры очередей, cron)Атакуем, только если процесс сам принимает данные из сети

Если у вас PHP-FPM за nginx, это не повод откладывать обновление. FPM просто требует от атакующего больше попыток.

Какие версии безопасны

Ветка PHPИсправленная версияГде взять
8.58.5.12php.net, дистрибутивы, Sury, LiteSpeed
8.48.4.27то же
8.38.3.36то же
8.28.2.35то же
8.1, 8.0, 7.4новых релизов не будетбэкпорт в пакете дистрибутива или LTS-вендора; исходники с минимальным бэкпортом от команды PHP

Для 7.4, 8.0 и 8.1 команда безопасности PHP опубликовала минимальные бэкпорты в исходниках, в репозитории php-src это ветки вида PHP-7.4-CVE-2026-109618-109619. Они нужны тем, кто собирает PHP сам. Остальным проще поставить готовый пакет.

Проверенные пакеты для старых веток

Эти сборки мы поставили и проверили тестом из раздела ниже:

РепозиторийПакетИсправленная версия пакета
Sury (packages.sury.org), Debian 12php7.4-*7.4.33-31+0~20261021...
Sury, Debian 12php8.1-*8.1.34-9+0~20261021...
Sury (ppa:ondrej/php), Ubuntu 22.04php8.1-*8.1.34-9+ubuntu22.04.1+deb.sury.org+1
LiteSpeed (rpms/debs.litespeedtech)lsphp747.4.33-21
LiteSpeedlsphp818.1.34-21
LiteSpeedlsphp83 / lsphp84 / lsphp858.3.36 / 8.4.27 / 8.5.12

Обратите внимание: php -v после обновления 7.4 и 8.1 покажет ту же версию 7.4.33 или 8.1.34. Меняется только версия пакета и дата сборки (built: Oct 21 2026). У Sury исправление прямо указано в changelog:

apt-get changelog php8.1-common 2>/dev/null | grep -A2 109618
# или из скачанного пакета:
apt-get download php8.1-common
dpkg-deb --fsys-tarfile php8.1-common_*.deb | tar -xO --wildcards './usr/share/doc/*/changelog.Debian.gz' | zcat | grep -B2 -A2 109618

У сборок LiteSpeed changelog внутри пакета нет, поэтому их проверяйте только поведенческим тестом.

Официальные пакеты Debian и Ubuntu (без сторонних репозиториев) тоже получают бэкпорты через security-обновления. Сверяйтесь с security tracker своего дистрибутива по номеру CVE и с apt-get changelog.

Шаг 1. Найдите весь PHP на сервере

PHP часто стоит в нескольких экземплярах сразу: системный php-fpm, lsphp у LiteSpeed, старые версии для отдельных сайтов, PHP внутри Docker-контейнеров. Обновлять нужно все.

# Пакеты PHP и lsphp
dpkg -l | awk '/^ii/ && $2 ~ /^(php|lsphp|libapache2-mod-php)/ {print $2, $3}'

# Все бинарники и их версии
for b in /usr/bin/php[0-9]* /usr/sbin/php-fpm* /usr/local/lsws/lsphp*/bin/lsphp; do
  [ -x "$b" ] && echo "$b: $($b -v 2>/dev/null | head -1)"
done

# Что из этого реально запущено
ps -eo user,args | grep -E '[p]hp-fpm|[l]sphp|[a]pache2|[h]ttpd|[p]hp[0-9.]* ' | sort | uniq -c | sort -rn | head -30

# PHP в контейнерах
docker ps --format '{{.Names}} {{.Image}}' | grep -i -E 'php|fpm|wordpress|nextcloud|mailcow'

Не забудьте про mod_php: если apache2ctl -M | grep php показывает php_module, это самый уязвимый вариант. Его стоит обновить первым, а лучше перевести сайт на PHP-FPM.

Шаг 2. Обновите пакеты

Debian и Ubuntu (официальные пакеты или Sury)

apt-get update
apt list --upgradable 2>/dev/null | grep -E '^(php|lsphp)'
apt-get install --only-upgrade -o Dpkg::Options::=--force-confold \
  $(apt list --upgradable 2>/dev/null | grep -E '^(php|lsphp)' | cut -d/ -f1)

--force-confold сохраняет ваши php.ini и конфиги пулов FPM.

Если после обновления сайты на PHP-FPM начали отдавать 503 или 500 с ошибками записи в storage или templates_c, проверьте юнит FPM. Пакет может перезаписать его с ProtectSystem=full, и тогда каталоги под /usr (например, /usr/local/lsws/vhost) становятся только для чтения. Лечится drop-in-файлом:

systemctl show php8.1-fpm -p ProtectSystem -p ReadWritePaths
mkdir -p /etc/systemd/system/php8.1-fpm.service.d
printf '[Service]\nReadWritePaths=/usr/local/lsws/vhost\n' > /etc/systemd/system/php8.1-fpm.service.d/override.conf
systemctl daemon-reload && systemctl restart php8.1-fpm

CentOS, AlmaLinux, Rocky (Remi, LiteSpeed)

dnf check-update | grep -E '^(php|lsphp)'
dnf upgrade 'php*' 'lsphp*'

Docker

Официальные образы php пересобираются на исправленных версиях. Поднимите тег до 8.2.35, 8.3.36, 8.4.27 или 8.5.12 и пересоздайте контейнер: docker compose pull && docker compose up -d. Если образ на 7.4 или 8.1 - собирайте свой с бэкпортом или переходите на поддерживаемую ветку.

Для mailcow исправленный php-fpm идёт в релизе 2026-10 (образ ghcr.io/mailcow/phpfpm:8.2.35-1). Обновление - штатным ./update.sh. Перед ним сделайте снимок VM: если инсталляция давно не обновлялась, update.sh пройдёт сразу через много миграций.

Шаг 3. Перезапустите все процессы PHP

Это самый частый пропуск. Пакет обновился, а воркер php-fpm, процесс lsphp или воркер очереди Laravel продолжают выполнять старый код из памяти.

# PHP-FPM
systemctl restart php7.4-fpm php8.1-fpm php8.3-fpm   # свои версии

# LiteSpeed / OpenLiteSpeed - вместе с ними перезапускаются процессы lsphp
/usr/local/lsws/bin/lswsctrl restart

# Apache с mod_php
systemctl restart apache2

# Laravel-очереди: мягкий перезапуск, текущая задача доработает
php artisan queue:restart

Потом найдите процессы, которые всё ещё держат удалённый старый бинарь или библиотеку:

for p in $(pgrep -f 'php|lsphp'); do
  e=$(readlink /proc/$p/exe 2>/dev/null)
  case "$e" in *php*|*lsphp*)
    if [[ "$e" == *deleted* ]] || grep -q 'php.*(deleted)' /proc/$p/maps 2>/dev/null; then
      echo "СТАРЫЙ БИНАРЬ: $p $(ps -o args= -p $p | cut -c1-100)"
    fi;;
  esac
done

Пустой вывод - всё работает на новых бинарях. Если что-то осталось, посмотрите, каким юнитом запущен процесс (cat /proc/PID/cgroup), и перезапустите этот юнит. Типичные кандидаты: artisan queue:work, Telegram- и Discord-боты на PHP, websocket-серверы, долгие cron-скрипты.

Шаг 4. Проверьте, что патч действительно стоит

PHP 8.2 и новее: строка в бинаре

Исправление добавляет «жёсткий» лимит памяти с собственным сообщением. Если строки нет - бинарь старый:

for b in /usr/sbin/php-fpm8.3 /usr/bin/php8.3 /usr/local/lsws/lsphp83/bin/lsphp; do
  [ -x "$b" ] && echo "$b: $(grep -a -c 'Hard memory limit of' "$b")"
done
# 1 и больше - патч есть, 0 - уязвимая сборка

PHP 7.4 и 8.1: поведенческий тест

У бэкпортов для старых веток новой строки нет, а номер версии не меняется. Зато меняется поведение при нехватке памяти: исправленный PHP завершает процесс сразу через _exit() и не запускает shutdown-функции. Уязвимый идёт обычным путём завершения запроса - тем самым, который и эксплуатируется.

cat > /tmp/php2shell-test.php <<'EOF'
<?php
ini_set('memory_limit', '8M');
register_shutdown_function(function () { echo "SHUTDOWN_RAN\n"; });
$a = str_repeat('x', 32 * 1024 * 1024);
EOF

for b in /usr/bin/php7.4 /usr/bin/php8.1 /usr/local/lsws/lsphp74/bin/php /usr/local/lsws/lsphp81/bin/php; do
  [ -x "$b" ] || continue
  out=$($b -n /tmp/php2shell-test.php 2>&1); rc=$?
  if [ $rc -eq 1 ] && ! echo "$out" | grep -q SHUTDOWN_RAN; then
    echo "$b: патч есть"
  else
    echo "$b: УЯЗВИМ (rc=$rc)"
  fi
done
rm -f /tmp/php2shell-test.php

Как мы это проверяли:

СборкаКод выходаSHUTDOWN_RANИтог
Sury 7.4.33-30, 8.1.34-8 (до патча)255дауязвима
LiteSpeed lsphp74 7.4.33-3, lsphp81 8.1.34-1 (до патча)255дауязвима
Sury 7.4.33-31, 8.1.34-91нетисправлена
LiteSpeed lsphp74 7.4.33-21, lsphp81 8.1.34-211нетисправлена

Тест проверяет CLI-бинарь из того же пакета. FPM и lsphp собираются из тех же исходников, поэтому результат на них переносится. Главное - чтобы после обновления они были перезапущены (шаг 3).

На PHP 8.2+ этот тест не подходит: там исправление сделано иначе («мягкий» OOM на безопасной точке), и shutdown-функции штатно выполняются и в пропатченной версии. Для 8.2+ используйте проверку строки в бинаре.

Шаг 5. Дополнительные меры (и временная защита, если обновиться нельзя)

Команда PHP прямо пишет: единственная полная защита - обновление. Но часть мер полезна и после него.

Выключите прогресс загрузки через сессию. 23 октября php.net отдельно рекомендовал это до следующего релиза: есть ещё один, маловероятный вектор, CVE-2026-115560, исправление которого выйдет в плановом обновлении. Функцией почти никто не пользуется:

; /etc/php/8.3/fpm/conf.d/99-php2shell.ini (и для каждой версии/SAPI)
session.upload_progress.enabled = 0

Для lsphp положите такой же файл в /usr/local/lsws/lsphp83/etc/php/8.3/mods-available/. Настройку нужно задавать в php.ini, пуле FPM или конфиге сервера. Через ini_set() или .user.ini она не сработает.

Уберите предупреждения из INI, которые применяются на каждый запрос. Вторая уязвимость срабатывает, если до инициализации запроса PHP выдаёт warning или deprecation. Так бывает с fastcgi_param PHP_VALUE в nginx, с php_value в .htaccess и с устаревшими директивами (mbstring.internal_encoding, iconv.*_encoding, неверный memory_limit и т.п.). Прятать предупреждения через error_reporting или @ бесполезно, их нужно устранить. Надёжнее всего перенести такие настройки в пул FPM (php_admin_value) или в php.ini.

Только если обновиться прямо сейчас невозможно:

  • memory_limit = -1 через php_admin_value (чтобы приложение не могло вернуть лимит). Это убирает управляемый извне OOM, но сервер становится уязвим к перерасходу памяти, поэтому ограничьте его cgroup: MemoryMax= в юните systemd. Не используйте RLIMIT_AS и RLIMIT_DATA - они как раз провоцируют уязвимый путь.
  • post_max_size = 8M для сайтов без загрузки файлов и 32M - для сайтов с загрузкой.
  • На прокси ограничьте размер тела запроса, число частей multipart и заголовков в частях. Если приложение не принимает multipart-формы - режьте такие запросы на входе.
  • На PHP 8.5: fatal_error_backtraces = 0.

После любой правки INI перезапустите все воркеры - перечитывание конфига без рестарта затрагивает не всё.

Чек-лист

  1. Найти все экземпляры PHP: системный, lsphp, Docker, отдельные версии для старых сайтов.
  2. Обновить до 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 или поставить пакет с бэкпортом для 7.4 / 8.1.
  3. Перезапустить php-fpm, LiteSpeed, Apache, воркеры очередей и PHP-демоны.
  4. Убедиться, что не осталось процессов со старым бинарём.
  5. Проверить патч: строка в бинаре для 8.2+, поведенческий тест для 7.4 / 8.1.
  6. Выключить session.upload_progress.enabled, убрать warning’и из per-request INI.
  7. Запланировать переход с 7.4 / 8.1 на поддерживаемую ветку: следующий бэкпорт может и не выйти.

Если сервер не ваш (виртуальный хостинг, панель провайдера), напишите в поддержку и спросите, какая сборка PHP стоит и перезапущены ли воркеры. Номер версии в phpinfo() ответа не даст.

Pterohost - выделенные серверы на Ryzen 9 и EPYC с DDoS-защитой и поддержкой 24/7. Подобрать выделенный сервер

Ещё по защите сервера: фаервол UFW для игрового сервера, вход по SSH-ключам, защита Plesk.