Облачное хранилище
Критические RCE в PHP CVE-2026-109618 и CVE-2026-109619 и проверка версии
Две критические RCE в движке PHP — фиксы 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12

21 октября 2026 команда PHP закрыла две критические уязвимости в самом движке: CVE-2026-109618 и CVE-2026-109619, обе ведут к удалённому выполнению кода (RCE). Исследователи назвали связку PHP2Shell. Оценка по NVD — CVSS 9.8 из 10, куда выше не бывает.

Самое неприятное: дыра не в чьём-то кривом коде, а в интерпретаторе. Значит, под ударом любой сайт на уязвимой версии PHP — и WordPress, и самописный, и любой фреймворк. Починка одна: обновить PHP до патченной версии в своей ветке и перезапустить php-fpm. Ниже — кто в риске, как за полминуты проверить все свои серверы и как закрыть.

Кто в риске

Обе CVE затрагивают одни и те же ветки. Уязвимо всё, что ниже фикса в своей ветке:

Ветка PHPУязвимыИсправлено в
8.5< 8.5.128.5.12
8.4< 8.4.278.4.27
8.3< 8.3.368.3.36
8.2< 8.2.358.2.35
8.1 и старшевсёнет фикса — ветка снята с поддержки

Ветки 8.1 и старше уже не получают даже секьюрити-патчей, поэтому официального исправления для них не будет — единственный выход переехать на поддерживаемую 8.3 или 8.4. Если вы сидите на пакетах из репозитория дистрибутива (Debian, Ubuntu, RHEL), номер версии может отличаться — там фикс приезжает бекпортом в текущий номер, ориентируйтесь на дату обновления пакета, а не на upstream-версию.

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

Если коротко — две ошибки работы с памятью в движке PHP (классы CWE-416 use-after-free и CWE-824 обращение к неинициализированной памяти). По описанию в advisory:

  • CVE-2026-109618. PHP использует longjmp() при нехватке памяти (OOM), чтобы оборвать обработку запроса. Некоторые участки кода при этом оставляют память запроса в недоинициализированном состоянии, а последующая очистка её читает. При определённых условиях это приводит к RCE.
  • CVE-2026-109619. У PHP есть глобальное состояние, которое временно ссылается на память запроса. Если оно не сбрасывается корректно, то начинает указывать на уже освобождённую память, и следующие запросы читают чужое — тоже вплоть до RCE.

Деталей эксплуатации ни я, ни статья не даём — это не то, что нужно админу. Админу важны три вещи, и все три неприятные. Первое: атака идёт по сети без аутентификации (вектор AV:N/PR:N в CVSS). Второе: страдает сам интерпретатор, то есть никакой аудит вашего PHP-кода от этого не спасает. Третье: флаг AT:P (Attack Requirements: Present) говорит, что надёжная эксплуатация требует определённых условий — но на 9.8 это влияет слабо, рассчитывать на «авось» не стоит.

Как понять, в риске ли вы

Самый быстрый способ — посмотреть версию на самом сервере:

php -v
php-fpm -v        # или php-fpm8.3 -v, смотря как назван

Сравните с таблицей выше. Важный нюанс: CLI и FPM — это часто разные сборки, и обновить надо именно тот SAPI, через который работает сайт (обычно FPM). Проверяйте оба.

Когда серверов больше одного, руками проверять долго. Я написал маленький скрипт-инвентаризатор. Он никуда не стучится и ничего не эксплуатирует — только читает версии PHP на том же хосте (бинарники, пакеты дистрибутива и запущенные контейнеры) и сравнивает с патченной версией. Годится для cron и Zabbix: код возврата 2, если найдена уязвимая версия.

#!/usr/bin/env bash
# check-php-cve-2026-109618.sh — в риске ли PHP на этом хосте.
# Только читает локальные версии, никуда не стучится.
# Коды: 0 — всё патчено, 2 — есть уязвимые, 1 — PHP не найден.
set -u

declare -A FIXED=( [8.2]="8.2.35" [8.3]="8.3.36" [8.4]="8.4.27" [8.5]="8.5.12" )

RED=$'\033[31m'; GRN=$'\033[32m'; YEL=$'\033[33m'; DIM=$'\033[2m'; RST=$'\033[0m'
[ -t 1 ] || { RED=; GRN=; YEL=; DIM=; RST=; }

found=0; risk=0
extract() { grep -oE '[0-9]+\.[0-9]+\.[0-9]+' <<<"$1" | head -n1; }

verdict() {
  local ver="$1" src="$2"; found=1; ver="${ver%%-*}"
  local branch="${ver%.*}" major="${ver%%.*}" fix="${FIXED[$branch]:-}"
  if [ -z "$fix" ]; then
    if [ "$major" -lt 8 ] 2>/dev/null || { [ "$major" -eq 8 ] && [ "${branch#*.}" -lt 2 ]; } 2>/dev/null; then
      risk=1; printf '%s[РИСК]%s  %-16s %sветка %s снята с поддержки, переходите на 8.3/8.4%s\n' "$RED" "$RST" "$ver" "$DIM" "$branch" "$RST"
    else
      printf '%s[?]%s     %-16s %sнеизвестная ветка %s, сверьте вручную%s\n' "$YEL" "$RST" "$ver" "$DIM" "$branch" "$RST"
    fi
    printf '          %s%s%s\n' "$DIM" "$src" "$RST"; return
  fi
  local lowest; lowest="$(printf '%s\n%s\n' "$ver" "$fix" | sort -V | head -n1)"
  if [ "$ver" = "$fix" ] || { [ "$lowest" = "$fix" ] && [ "$ver" != "$fix" ]; }; then
    printf '%s[OK]%s    %-16s %s>= %s%s\n' "$GRN" "$RST" "$ver" "$DIM" "$fix" "$RST"
  else
    risk=1; printf '%s[РИСК]%s  %-16s %sобновите до %s+ в ветке %s%s\n' "$RED" "$RST" "$ver" "$DIM" "$fix" "$branch" "$RST"
  fi
  printf '          %s%s%s\n' "$DIM" "$src" "$RST"
}

echo "== Проверка PHP на CVE-2026-109618 / CVE-2026-109619 =="; echo

# 1) бинарники в PATH
for name in php php8.2 php8.3 php8.4 php8.5 php-fpm php-fpm8.2 php-fpm8.3 php-fpm8.4 php-fpm8.5; do
  bin="$(command -v "$name" 2>/dev/null)" || continue
  v="$(extract "$("$bin" -v 2>/dev/null | head -n1)")"
  [ -n "$v" ] && verdict "$v" "бинарник: $bin"
done

# 2) пакеты дистрибутива
if command -v dpkg-query >/dev/null 2>&1; then
  while read -r pkg ver; do
    [ -n "$ver" ] || continue
    v="$(extract "$ver")"; [ -n "$v" ] && verdict "$v" "пакет: $pkg"
  done < <(dpkg-query -W -f='${Package} ${Version}\n' 'php*-common' 'php-common' 2>/dev/null)
elif command -v rpm >/dev/null 2>&1; then
  while read -r line; do
    v="$(extract "$line")"; [ -n "$v" ] && verdict "$v" "пакет: $line"
  done < <(rpm -q php php-cli php-fpm 2>/dev/null | grep -v 'not installed')
fi

# 3) запущенные Docker-контейнеры
if command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then
  while read -r cid cname; do
    [ -n "$cid" ] || continue
    v="$(extract "$(docker exec "$cid" php -v 2>/dev/null | head -n1)")"
    [ -n "$v" ] && verdict "$v" "контейнер: $cname"
  done < <(docker ps --format '{{.ID}} {{.Names}}' 2>/dev/null)
fi

echo
if [ "$found" -eq 0 ]; then echo "PHP на этом хосте не найден."; exit 1
elif [ "$risk" -eq 1 ]; then echo "${RED}Итог: есть уязвимые версии. Обновите PHP и перезапустите fpm.${RST}"; exit 2
else echo "${GRN}Итог: все найденные версии пропатчены.${RST}"; exit 0; fi

Запуск:

chmod +x check-php-cve-2026-109618.sh
./check-php-cve-2026-109618.sh

Если на хосте крутится сборка не из веток 8.2–8.5 (например, собранная вручную), скрипт честно пометит её [?] и попросит сверить вручную — ложного «всё чисто» не будет.

Как закрыть

Лечение одно — обновить PHP и перезапустить то, что его держит в памяти. Обновление пакета само по себе не перезапускает php-fpm — старый процесс останется уязвимым в памяти, пока его не рестартовать.

Debian / Ubuntu:

sudo apt update
sudo apt install --only-upgrade php-cli php-fpm php-common
sudo systemctl restart php8.3-fpm   # подставьте свою версию fpm

RHEL / AlmaLinux / Rocky:

sudo dnf update php php-cli php-fpm
sudo systemctl restart php-fpm

Если PHP в Docker — обновляется не на хосте, а в образе. Поднимите тег базового образа до патченной версии (например php:8.3.36-fpm), пересоберите и пересоздайте контейнеры:

docker compose pull
docker compose up -d --build

После обновления прогоните скрипт ещё раз — он должен показать сплошь [OK].

Если обновиться прямо сейчас нельзя

Честно: полноценной замены патчу нет, уязвимость в самом движке. Но пока окно обслуживания не наступило, разумно снизить риск: держать сайт за WAF, ограничить доступ к админкам по белому списку IP, убрать наружу всё, что можно убрать, и внимательно смотреть логи на аномальные падения и перезапуски fpm-воркеров. Всё это не лечит, а только тянет время до нормального обновления.

Итог

Две критические RCE в самом PHP, CVSS 9.8, без аутентификации и независимо от вашего кода. Порядок действий простой:

  • Прогнать скрипт на всех серверах, где есть PHP — и на веб-серверах, и там, где PHP только в CLI/cron.
  • Проверить оба SAPI: и CLI (php), и FPM — они могут быть разных версий.
  • Не забыть про Docker-образы — там PHP живёт отдельно от хоста.
  • Обновить до фикса в своей ветке: 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12.
  • Перезапустить php-fpm и веб-сервер, иначе старый процесс останется в памяти.
  • Ветки 8.1 и старше — планировать переезд на поддерживаемую, фикса для них не будет.
  • Прогнать скрипт повторно и убедиться, что всюду [OK].

Удобно закинуть скрипт в cron или Zabbix и ловить код возврата 2 — тогда следующая такая дыра не застанет врасплох.

Источники

Облачное хранилище