// Новости

Уязвимость PHP2Shell: 35 шеллов Apache на PHP 8.4.26

Обложка: уязвимость PHP2Shell, 35 процессов /bin/sh от Apache на PHP 8.4.26

24 октября 2026 honeypot связал 35 POST уязвимости PHP2Shell с 35 процессами /bin/sh. Их напрямую породили воркеры Apache на PHP 8.4.26. Признак успеха — родительский процесс оболочки и падение воркера, а не строка access log.

Коротко

  • 35 полных POST стадии выполнения совпали с 35 процессами /bin/sh. Родитель — воркер Apache, uid 33, фиксация событиями Tetragon.
  • Два адреса, 15.204.59.70 и 162.35.99.94, — два эпизода с одним шаблоном, не доказанные два оператора. Ещё два адреса дали только попытки.
  • Цели: PHP 8.4.26, mod_php, memory_limit 128 МиБ. Ошибки «Unknown on line 0» до кода приложения. Записи согласуются с CVE-2026-109618 и не подтверждают CVE-2026-109619.
  • Успех читается по родителю оболочки и падению воркера. Утечка — cookie PHPSESSID на 819 200 байт. Пока бюллетень PHP не подтвердил исправленную сборку, обновляют интерпретатор и выключают session.upload_progress.enabled.

Что honeypot подтвердил 24 октября

Honeypot 24 октября 2026 года подтвердил эксплуатацию уязвимости PHP2Shell по процессам, а не по «похожему» HTTP. 35 полных POST стадии выполнения совпали один к одному с 35 стартами /bin/sh. Родителем каждой оболочки был воркер Apache с uid 33. Родительский процесс зафиксирован событиями Tetragon, а не выведен из кода ответа.

Цели — три одноразовых PHP-honeypot, T1, T2 и T3. Шесть сенсоров закрыли окно свидетельств между 19:26 и 19:31 UTC. Сами оболочки стартовали раньше, днём. Цифры относятся к этому снимку.

Корреляцию собрали из трёх слоёв: двусторонний захват пакетов, записи Apache о запросе и PID воркера, события процессов Tetragon. Склейка шла по порту источника, времени, идентификатору воркера и порядку запросов. Код состояния HTTP за доказательство выполнения не принимали.

Снимок: 3 459 запечатанных сегментов, 10 948 файлов манифеста, 11,21 ГБ. Перечисленные размеры и SHA-256 совпали с локальными копиями. В записях Apache и Tetragon в формате JSONL битых строк не нашли.

«Полный» POST стадии выполнения — запрос, пересобранный на стороне цели и дошедший до финала цепочки, плюс отдельный старт оболочки. Код 200 в эту проверку не входит. 35 подтверждённых запусков нельзя складывать с адресами, которые слали трафик той же формы без оболочки.

Подтверждённые запросы шли открытым текстом, HTTP/1.1 на порт 80. Отчёт не утверждает, что цепочка невозможна после TLS: на этих целях шифрованный порт не попал в подтверждённые запуски.

Два адреса — это два оператора?

Нет. Отчёт фиксирует два эпизода с адресов-источников и не называет их двумя операторами. Общий инструмент не доказывает общее управление. Разные IP и разные команды проверки не доказывают разных авторов. Обоснованный вывод: два эпизода адресов с близким инструментарием.

Два эпизода с адресов-источников и близкий инструментарий, без вывода об общем операторе.

Эпизод A дал 3 оболочки, все на T1. Эпизод B дал 32 оболочки с 16:46 до 18:57 UTC: 19 на T2, 10 на T3 и 3 на T1. Вместе 35. B прогонял шаблон по всем трём стендам, A остался на одной цели.

ЭпизодИсточникЦелиCookie утечкиОболочкиКоманда
A15.204.59.70T133 на T1Один и тот же id, файл-доказательство и wget
B162.35.99.94T1, T2, T3493 на T1, 19 на T2, 10 на T3PHP CLI, curl, wget, затем проверки имени, хоста и архитектуры

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

Тело финальной стадии менялось вместе с диапазоном адресов, слитым с конкретной цели, и вместе с командой оболочки.

Оба подтверждённых адреса на 24 октября числились за коммерческим хостингом. 15.204.59.70 входил в сеть OVH US LLC, префикс 15.204.0.0/16. PTR vps-b225fb62.vps.ovh.us разрешался обратно в тот же адрес. 162.35.99.94 входил в сеть Interserver, Inc, префикс 162.35.96.0/19. PTR vps3749638.trouble-free.net подтверждающей A-записи не имел.

Отдельно 104.28.208.84 и 144.48.4.242 слали запросы той же формы. Рядом были ошибки PHP или падения воркера. Оболочки с родителем Apache не было, cookie размера утечки тоже не было. Отчёт считает их индикаторами попытки.

Уязвимость PHP2Shell — баг движка или дыра в CMS?

Наблюдаемая цепочка указывает на движок PHP, а не на баг конкретной CMS. Ошибки вспыхивали на старте запроса, до кода приложения. Цели работали на PHP 8.4.26 под Apache с mod_php. Лимит памяти PHP — 128 МиБ, то есть 134 217 728 байт.

В отчёте схема названа single-worker Apache mod_php. Это не сводка 35 оболочек к одному PID. Первая оболочка на T1 вышла из PID 31 и сразу завершилась по сигналу 11. Следующие тоже имеют родителя-воркера, отчёт не склеивает их в один процесс.

В ошибке «Allowed memory size of 134217728 bytes exhausted» фигурируют попытки выделить 4 096 байт на первом давлении и 20 480 байт на втором. Пометка — «Unknown on line 0». Сама по себе она не доказывает дыру ядра: так PHP пишет, когда у сообщения нет строки исходника. Вес выводу придаёт момент: старт запроса, до пользовательского кода, одна и та же цепочка на трёх honeypot. Номер исправленного релиза этот отчёт не называет: патч берут из бюллетеня разработчика PHP.

Ошибки пришлись на старт запроса, до кода приложения, и согласуются с уязвимостью движка, а не конкретной CMS.

Для сайта на WordPress или 1С-Битрикс практический вывод делает не отчёт, а версия интерпретатора. Если CMS крутится на PHP 8.4.26 с mod_php, закрывать нужно сборку PHP и способ запуска, не только плагины. Проверку WordPress и 1С-Битрикс имеет смысл ставить после сверки интерпретатора.

Записи согласуются с CVE-2026-109618 и с семейством публичных доказательств, которые работают только по HTTP. Исследователи Wiz описывали PHP2Shell вокруг прогресса загрузки и утечки памяти. Отдельный путь CVE-2026-109619 эти записи не устанавливают.

Наблюдение поддерживает уже описанный обход ASLR по утечке, а не новый примитив порчи памяти. Слово, похожее на адрес, в финальном теле держало один и тот же сдвиг от доминирующего указателя на T1, T2 и T3.

Утечка возвращалась раздутым значением cookie сессии. Таких записей в журнале Apache 52: у эпизода A — три на T1, у эпизода B — четыре на T1, 29 на T2 и 16 на T3. Каждое значение раскодируется в 819 200 байт. В одной из утечек одно доминирующее слово повторилось 1 095 раз.

Во всех четырёх телах были поля PHPSESSID и PHP_SESSION_UPLOAD_PROGRESS. Отчёт относит цепочку к семейству утечки через прогресс загрузки. Отдельная функция загрузки в CMS в этих записях не фигурирует.

Трафик близок к публичному семейству HTTP-only PoC. Клиент 9bie считает относительный адрес тем же типом сдвига. Пакеты не называют конкретную программу и ревизию. Ревизия exploit.py, просмотренная 24 октября, отбрасывала указатели ниже 0x7f0000000000. У ранних успешных цепочек слов выше порога в cookie не было, две поздние cookie на T3 такие указатели содержали. Уверенность средняя: захват узнаёт семейство запроса и не называет реализацию клиента.

Размер HTTP-тела нельзя приравнивать к расходу памяти PHP. Раскодированное тело стадии утечки — 33 128 677 байт, около 31,6 МиБ, при лимите 128 МиБ.

Почему код ответа HTTP ничего не доказывает?

Финальный запрос убивает воркер Apache уже после старта оболочки, и обычной строки access log у этой стадии нет. HTTP 200 на предыдущих стадиях отчёт критерием успеха не считает. Segmentation fault родителя не откатывает то, что дочерний процесс уже сделал.

Запросы шли POST на «/» и «/index.php», multipart, без заголовка User-Agent. Граница multipart — десять символов. У стадий с Transfer-Encoding: chunked порядок заголовков: Host, Content-Type, Transfer-Encoding, Connection. У стадий фиксированной длины: Host, Content-Type, Content-Length, Connection. Для детекта это почерк клиента, не подпись CVE.

Первое давление несло пять нумерованных параметров вида q…=v. POST без User-Agent и с сотнями однотипных полей — повод для проверки, а не признак компрометации.

A1 давление1 060 211 байт15:35:32 UTCB1 утечкаcookie 819 200 байт15:35:34 UTCA2 давление279 369 байт15:35:37 UTCB2 выполнение/bin/sh, затем SIGSEGV15:35:38 UTC
Четыре стадии цепочки на T1 между 15:35:32 и 15:35:39 UTC. Оболочка стартует на B2, строка access log этой стадии не завершается.

Размеры тел сняты с полной пересборки TCP на стороне цели. Обрамление chunk в размер не входит. Одни и те же три первых раскодированных тела нашлись у обоих источников и всех трёх целей.

  • A1, давление: 1 060 211 байт, 973 части, из них 859 полей files*, передача chunked. В журнале запрос завершился с HTTP 200, память уже была исчерпана.
  • B1, утечка: 33 128 677 байт, 918 частей, из них 900 полей x*, chunked. В полном ответе — PHPSESSID на 819 200 байт и предупреждения старта сессии.
  • A2, давление: 279 369 байт, 997 частей, из них 880 полей files*, фиксированная длина. Снова исчерпание лимита, попытка выделить 20 480 байт.
  • B2, выполнение: 451 384 байт, 998 частей, среди них 500 полей s* и 495 полей t*, фиксированная длина. Плюс значения, похожие на адреса, и строка команды.

Ошибки PHP на A1 и A2 возникали до скрипта. B1 могла отдать большой cookie или уронить воркер. B2 могла уронить воркер уже после старта оболочки и не оставить завершённой строки access log. Тот же дайджест тела B1 встречался в захватах, где воркер только падал, без подтверждённого RCE. Совпадение тела узнаёт семейство запроса.

Доставку утечки сверили с захватом. 51 из 52 ответов собран целиком, с подтверждением TCP до конца ответа: T1 — 7 из 7, T2 — 28 из 29, T3 — 16 из 16. Один файл на T2 начинается с середины ответа. Cookie в журнале у этого файла есть, полная доставка этим файлом не доказана.

Apache с mod_php исполняет PHP внутри рабочего процесса httpd. Прямой родитель httpd у /bin/sh — сигнал, что выполнение вышло из веб-воркера. На PHP-FPM подозрительным родителем был бы процесс пула, не httpd. uid 33 на Debian и Ubuntu чаще всего www-data. Права оболочки равны правам воркера. Повышения привилегий не зафиксировано.

На проверках сайтов цепочка, которая роняет воркер, в отчёте сканера числится обрывом или 502 балансировщика. На хосте к этому моменту уже может быть дочерний sh, curl или php с родителем воркера. Пентест веб-приложений здесь смотрит интерпретатор и SAPI, а не только маршруты CMS.

Как шли два эпизода по времени

Эпизод A: проба путей в 13:47 UTC, давление на память около 15:16, первая оболочка 15:35:38.673, ещё две в 16:04. У всех трёх совпали хеш тела финальной стадии и команда. Цель одна.

В 15:35:32.793 с порта 54952 началось A1, тело 1 060 211 байт. Около 15:35:33 PHP исчерпал 134 217 728 байт, пытаясь выделить 4 096, пометка «Unknown on line 0». Access log завершился с HTTP 200.

В 15:35:34.127 с порта 38108 пошла B1, тело 33 128 677 байт. В 15:35:34.205 Apache записал запрос на воркере PID 31 и cookie размера утечки. В 15:35:37.429 с порта 38122 началось A2, тело 279 369 байт, попытка выделить 20 480 байт.

В 15:35:38.238 с порта 38126 начался финальный POST, тело 451 384 байт. В 15:35:38.673 Tetragon зафиксировал apache2, затем /bin/sh с uid 33, затем /usr/bin/id. В 15:35:39.379 PID 31 вышел по сигналу 11. Между стартом оболочки и смертью воркера — 706 мс. Строки access log на финальный запрос нет. A1, B1 и A2 записаны на том же PID 31.

Команда писала вывод id во временный файл и в корень сайта, затем звала wget. Запись в корень упёрлась в файловую систему только для чтения, wget в системе не было. Оболочка к этому моменту уже работала. На боевом хосте с записываемым корнем и wget в PATH та же команда оставила бы файл и исходящую загрузку.

Эпизод B появился в 16:36 уже с ответами размера утечки. Около 16:44–16:46 шли повторные попытки. Первая подтверждённая оболочка — в 16:46. До 18:57 Tetragon насчитал 32 старта: 19 на T2, десять на T3, три на T1. Команды менялись между PHP CLI, curl и wget; в поздних URL сидели id -un, имя хоста и uname -m. Cookie размера утечки у B — 49, из них 48 собраны целиком. Три cookie эпизода A тоже собраны целиком. Окно сенсоров 19:26–19:31 UTC — сбор свидетельств, не момент взлома.

До рестарта сервиса стоит снять список процессов с PPID, дескрипторы и сокеты, журналы Apache и PHP, захват пакетов. Рестарт рвёт связь воркера и живой оболочки.

Что делать, если на сервере PHP 8.4.26

Если на периметре ещё PHP 8.4.26 под mod_php, стенд в той же поверхности, что honeypot. Сверять пакет нужно с бюллетенем разработчика PHP. Отчёт не публикует матрицу всех веток 8.x и не называет номер исправленной сборки. Образы, куда версия зашита при сборке, после обновления пакета нужно пересобрать. Пока канал обновлений не подтвердил исправленную сборку, выключите session.upload_progress.enabled: через эти поля в цепочке фигурируют PHPSESSID и PHP_SESSION_UPLOAD_PROGRESS. После патча настройку возвращают, только если прогресс загрузки нужен приложению.

Имеет смысл уйти с mod_php на PHP-FPM с отдельным пользователем пула. Права оболочки равны правам воркера. Отдельный пользователь не закрывает дыру движка, но режет то, что оболочка прочитает и запишет. Проверьте исходящие от www-data, apache или пользователя пула: в эпизоде A команда звала wget, в эпизоде B мелькали curl и wget. Рядом смотрите новые файлы во временном каталоге и в корне сайта. Сканирование уязвимостей версию интерпретатора не заменяет.

Имеет смысл искать связку, а не один признак:

  • исчерпание memory_limit с «Unknown on line 0» на старте запроса, до кода приложения;
  • аномально большой PHPSESSID: в этом захвате значение раскодировалось в 819 200 байт;
  • смерть воркера по segmentation fault без обычной строки access log на последнем POST;
  • дочерние sh, dash, bash, php, curl или wget с родителем httpd, apache2 или php-fpm;
  • исходящие подключения этих процессов и записи во временный каталог или в корень сайта.

Огромный cookie, multipart без User-Agent и сотни однотипных полей — повод для проверки, а не признак компрометации. Подтверждение — оболочка от воркера, файл, исходящее соединение, падение. Два адреса дали ошибки и падения воркера без оболочки и без cookie размера утечки. Считать их RCE — завышать инцидент.

Индикаторы тел запроса

Ниже SHA-256 раскодированных тел multipart, не всего TCP-потока. A1 и B1 шли с Transfer-Encoding: chunked, A2 и B2 — с Content-Length. Дайджест B2 в таблицу не входит: он менялся вместе с адресами цели и командой. Совпадение A1–A2 узнаёт семейство. Исход выполнения подтверждает только родитель оболочки.

ТелоПоляSHA-256 раскодированного тела
A1files* доминируют, 859 из 973 частей8e8fc166e6b326d20bb7611d8ef3cee3bd5da46447985312c1492ab0a44d1eb6
B1x* доминируют, 900 из 918 частейb6c547e0e9a2bb26607df1521de96f4d5c9adf4621c28b7ab491587b21ae40f2
A2files* доминируют, 880 из 997 частей7af66c8f35c5fa6b47a539da6dca361d19543d9f62d61e4af891c2802bd5e236

Тот же дайджест B1 есть в захватах попыток без RCE. Поля s* и t* (500 и 495 частей в разобранном B2) плюс строка команды отличают стадию выполнения, но хеш этой стадии стабильным индикатором не будет.

Одна строка memory exhausted без большого cookie, без падения и без дочернего процесса — ещё не вердикт. На эпизоде A разведка путей началась в 13:47, за два часа до оболочки. Публичный эксплойт на боевом контуре гонять не нужно: цепочка выжигает память и роняет воркеры. Реагирование на инцидент здесь начинается с родителя процесса и журнала ошибок, а не с кода ответа.

Частые вопросы

Что такое PHP2Shell?

PHP2Shell — цепочка против движка PHP: давление на память, утечка в раздутом cookie PHPSESSID, повторное давление и запрос, который подстраивается под слитые адреса. В захвате 24 октября она доходила до /bin/sh с родителем воркера Apache.

CVE-2026-109618 какие версии PHP затрагивает и есть ли патч для 8.4.26?

Этот отчёт показывает эксплуатацию на PHP 8.4.26 и не публикует матрицу остальных веток. Записи согласуются с CVE-2026-109618. Номер исправленного релиза берите из бюллетеня разработчика PHP.

Как проверить, что PHP уже эксплуатируют?

Ищите связку: memory_limit на line 0, PHPSESSID около 819 200 байт, segmentation fault воркера без строки access log и дочерний sh, curl, wget или php с родителем httpd или php-fpm.

Почему при RCE в PHP нет строки в access log?

Финальный запрос стартует оболочку и затем роняет воркер по сигналу 11. Apache не успевает дописать обычную строку этой стадии.

Если на периметре PHP 8.4 под Apache или FPM, а сканер смотрит только код ответа, имеет смысл сверить версию интерпретатора и родителя процессов вручную. SecurityLab.pro закрывает это пентестом внешнего периметра: веб-воркеры, сессии и то, что access log не показывает.


← назад в блог