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.8 | GHSA-w8hw-w9fx-j79w |
| CVE-2026-109619 | Глобальное состояние (ошибки, сессии, объекты, расширения) продолжает указывать на память уже завершённого запроса. Следующий запрос в том же воркере использует освобождённую память (use-after-free) | 8.1 | GHSA-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.5 | 8.5.12 | php.net, дистрибутивы, Sury, LiteSpeed |
| 8.4 | 8.4.27 | то же |
| 8.3 | 8.3.36 | то же |
| 8.2 | 8.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 12 | php7.4-* | 7.4.33-31+0~20261021... |
| Sury, Debian 12 | php8.1-* | 8.1.34-9+0~20261021... |
| Sury (ppa:ondrej/php), Ubuntu 22.04 | php8.1-* | 8.1.34-9+ubuntu22.04.1+deb.sury.org+1 |
| LiteSpeed (rpms/debs.litespeedtech) | lsphp74 | 7.4.33-21 |
| LiteSpeed | lsphp81 | 8.1.34-21 |
| LiteSpeed | lsphp83 / lsphp84 / lsphp85 | 8.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-9 | 1 | нет | исправлена |
| LiteSpeed lsphp74 7.4.33-21, lsphp81 8.1.34-21 | 1 | нет | исправлена |
Тест проверяет 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 перезапустите все воркеры - перечитывание конфига без рестарта затрагивает не всё.
Чек-лист
- Найти все экземпляры PHP: системный, lsphp, Docker, отдельные версии для старых сайтов.
- Обновить до 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 или поставить пакет с бэкпортом для 7.4 / 8.1.
- Перезапустить php-fpm, LiteSpeed, Apache, воркеры очередей и PHP-демоны.
- Убедиться, что не осталось процессов со старым бинарём.
- Проверить патч: строка в бинаре для 8.2+, поведенческий тест для 7.4 / 8.1.
- Выключить
session.upload_progress.enabled, убрать warning’и из per-request INI. - Запланировать переход с 7.4 / 8.1 на поддерживаемую ветку: следующий бэкпорт может и не выйти.
Если сервер не ваш (виртуальный хостинг, панель провайдера), напишите в поддержку и спросите, какая сборка PHP стоит и перезапущены ли воркеры. Номер версии в phpinfo() ответа не даст.
Pterohost - выделенные серверы на Ryzen 9 и EPYC с DDoS-защитой и поддержкой 24/7. Подобрать выделенный сервер
Ещё по защите сервера: фаервол UFW для игрового сервера, вход по SSH-ключам, защита Plesk.