17 октября 2026 года были зафиксированы попытки атаки с использованием PHP2Shell. Патч вышел 21 октября. Если отсчёт времени уязвимости начинается с момента раскрытия (disclosure), вы ошибаетесь на четыре дня ещё до того, как успели что-либо сделать.

В вашем процессе патчинга наверняка есть аккуратный таймлайн. Публикуется CVE, создаётся тикет, запускается таймер SLA, и команда оценивает себя относительно нормы — например, 14 дней для критических уязвимостей. Таймлайн выглядит логичным и строгим, потому что все согласны с тем, когда он начинается. А начинается он с момента раскрытия.

PHP2Shell публично нарушила это соглашение. Первая зарегистрированная попытка эксплуатации произошла до появления публичного CVE, до выхода патча и до того, как в вашей системе вообще мог появиться тикет. У вас есть два плохих варианта действий в этой ситуации. Вариант первый: продолжать отсчёт от момента раскрытия и смириться с тем, что ваши отчёты показывают окно уязвимости меньшим, чем оно было у злоумышленников на самом деле. Вариант второй: перестроить отсчёт вокруг даты, которую вы обычно не знаете — даты первой эксплуатации, и принять тот факт, что часть показателей уязвимости станет оценкой «не менее чем», а не точной цифрой. Второй вариант менее комфортен. Но это единственный честный подход.

Что произошло на самом деле: разбор по датам

PHP2Shell — это название, которое компания Wiz дала двум уязвимостям безопасности памяти в самом движке PHP: CVE-2026-109618 и CVE-2026-109619, каждая из которых получила оценку 9.2 по шкале CVSS v4.0. В случае успешной эксплуатации неаутентифицированный атакующий получает возможность удалённого выполнения кода (RCE) на сервере, обрабатывающем веб-запросы. Уязвимый код восходит к PHP 5.4 2012 года, а PHP используется примерно на 70% веб-сайтов с известным серверным языком, так что под угрозой оказалось даже минимальное приложение hello world на уязвимой версии. Wiz продемонстрировала рабочие атаки на актуальные релизы WordPress, Drupal и Joomla. Уязвимости находились в движке, на котором работают все три системы.

История обнаружения важна не меньше самой баги. Wiz нашла первую уязвимость 19 августа 2026 года, используя свои инструменты Atlas вместе с передовыми ИИ-моделями. По заявлению компании, один исследователь сделал за неделю то, что раньше требовало месяцев работы команды экспертов. Вторая фирма, Calif, работая в рамках инициативы OpenAI Patch the Planet, независимо обнаружила ту же первопричину ранее в августе. В итоге три независимые стороны сообщили об одной и той же базовой уязвимости команде безопасности PHP в период с 11 августа по 18 сентября. Перечитайте это ещё раз: три независимых обнаружения за пять недель уязвимости, которая пролежала в коде четырнадцать лет.

Затем скоординированная часть процесса перестала быть таковой. 17 октября, за четыре дня до официального раскрытия, ловушка для хакеров (honeypot) MadPot от Amazon зафиксировала попытку эксплуатации PHP2Shell. В Amazon прямо заявили, что неизвестно, как именно атакующий получил информацию об эксплойте. Идентификаторы CVE утекли 20 октября, что заставило сдвинуть релиз PHP на день раньше — на 21 октября. Исправления вышли в версиях 8.5.12, 8.4.27, 8.3.36 и 8.2.35. В течение нескольких часов после релиза случайно опубликовали видео с демонстрацией концепта (PoC), а один из дистрибьюторов выложил пропатченные бинарники раньше времени. К 24 октября было подтверждено выполнение команд атакующими на honeypot-серверах, а 26 октября CISA добавила обе уязвимости в свой каталог Known Exploited Vulnerabilities (KEV). Википедия, нидерландский госсервис DigiD и провайдер HR-систем BambooHR на этой неделе временно отключили свои системы в качестве меры предосторожности на время патчинга.

Пробел, о котором все молчат

Большинство разборов PHP2Shell сводятся к одному совету: патчите быстрее. Этот совет правильный, но недостаточный, так как он по умолчанию предполагает, что гонка начинается в момент выхода патча. Приведённый выше таймлайн показывает, что гонка началась раньше, причём дважды. Во-первых, обнаружение уязвимости больше не является редким событием, происходящим один раз, у одного аккуратного исследователя и под эмбарго. Когда три группы находят одну и ту же 14-летнюю багу за пять недель, повторное обнаружение становится базовым сценарием. И как минимум у одного злоумышленника был рабочий эксплойт до каких-либо анонсов. Во-вторых, сам патч — это карта. Сравнение исправленного кода со старым (patch diffing) изолирует изменения, и эти изменения указывают прямо на уязвимость. Раньше диффинг патчей был медленной работой узких специалистов. Теперь это задача, с которой отлично справляются ИИ-модели. Именно поэтому интервал между выходом фикса и появлением эксплойта в сети в последнее время измеряется часами, а не неделями.

Окно уязвимости не начинается тогда, когда вы узнаёте о баге. Оно начинается тогда, когда о ней узнаёт первый хакер, а об этой дате вы, скорее всего, узнаете позже — или не узнаете вовсе.

Пробел, который закрывает эта статья, касается учёта времени. Если эксплуатация опережает раскрытие, то два ваших стандартных показателя ошибочны в противоположных направлениях. Ваш SLA на патчинг, отсчитываемый от момента раскрытия, занижает реальное время, в течение которого вы были уязвимы. А окно разбора инцидентов (incident review), которое большинство команд открывает с даты раскрытия, начинается на четыре дня позже, чем нужно. В результате поиск по логам просто не захватывает те дни, когда происходили ранние попытки атак. Исправление второго показателя стоит вам всего лишь изменения одной даты в посковом запросе. Но большинство команд этого до сих пор не сделали.

Считайте время уязвимости с первого выстрела, а не с анонса

Вот небольшой инструмент на Python (stdlib), который выполняет расчёт обоими способами для тестового парка серверов. Он проверяет версию PHP на каждом хосте относительно исправленных релизов, а затем оценивает четыре популярные стратегии патчинга дважды: от даты раскрытия и от даты первой зарегистрированной попытки эксплуатации 17 октября.

 #!/usr/bin/env python3
from datetime import date

FIRST_EXPLOITATION = date(2026, 10, 17)  # Amazon MadPot honeypot attempt
PATCH_RELEASED = date(2026, 10, 21)      # PHP 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35
CISA_KEV = date(2026, 10, 26)            # Known Exploited Vulnerabilities

FIXED = {"8.5": (8, 5, 12), "8.4": (8, 4, 27), "8.3": (8, 3, 36), "8.2": (8, 2, 35)}

def parse(version):
    return tuple(int(p) for p in version.split("."))

def status(version):
    branch = ".".join(version.split(".")[:2])
    if branch not in FIXED:
        return "AFFECTED, no fix (end of life branch)"
    if parse(version) < FIXED[branch]:
        return f"AFFECTED, patch to {'.'.join(map(str, FIXED[branch]))}"
    return "patched"

FLEET = [
    ("web-01", "8.4.26"),
    ("web-02", "8.4.27"),
    ("blog-01", "8.3.30"),
    ("legacy-01", "8.1.33"),
    ("api-01", "8.5.12"),
    ("shop-01", "8.2.28"),
]

PLANS = [
    ("same day as the patch", date(2026, 10, 21)),
    ("48 hour critical SLA", date(2026, 10, 23)),
    ("weekly patch cycle", date(2026, 10, 28)),
    ("monthly patch cycle", date(2026, 11, 4)),
]

for host, version in FLEET:
    print(host, version, status(version))

for name, patched in PLANS:
    print(name,
          (patched - PATCH_RELEASED).days, "days after disclosure,",
          (patched - FIRST_EXPLOITATION).days, "days after first exploitation")
 

Запустите его на полном файле (версия, сохранённая с этим черновиком, также выводит таблицу парка серверов и дату KEV), и вы получите следующее:

 Fleet check (PHP2Shell, CVE-2026-109618 and CVE-2026-109619)
  web-01     php 8.4.26   AFFECTED, patch to 8.4.27
  web-02     php 8.4.27   patched
  blog-01    php 8.3.30   AFFECTED, patch to 8.3.36
  legacy-01  php 8.1.33   AFFECTED, no fix (end of life branch)
  api-01     php 8.5.12   patched
  shop-01    php 8.2.28   AFFECTED, patch to 8.2.35
  -> 4 of 6 hosts affected

Exposure per patch plan, counted two ways
  first exploitation attempt recorded: 2026-10-17
  patch released:                       2026-10-21
  CISA KEV listing:                     2026-10-26
  same day as the patch      patched 2026-10-21:  0 days after disclosure,  4 days after first exploitation
  48 hour critical SLA       patched 2026-10-23:  2 days after disclosure,  6 days after first exploitation
  weekly patch cycle         patched 2026-10-28:  7 days after disclosure, 11 days after first exploitation
  monthly patch cycle        patched 2026-11-04: 14 days after disclosure, 18 days after first exploitation

The disclosure clock understates exposure by 4 days on every plan.
 

В этом выводе стоит обратить внимание на несколько вещей. Во-первых, месячный цикл, который выглядит приемлемым (14 дней) в отчёте по комплаенсу, превращается в 18 дней относительно даты реальной эксплуатации. И 18 — это нижняя граница, а не точный срок, поскольку 17 октября — это всего лишь первая попытка, которую случайно заметил honeypot. Во-вторых, хост с завершённым сроком поддержки (End of Life) — самая проблемная строка в таблице. Для него нет исправленной версии, поэтому никакой SLA, выраженный в днях на патчинг, закрыть его не сможет. Ему нужна изоляция, правило WAF или вывод из эксплуатации, а это совсем другие тикеты с другими ответственными. В-третьих, обратите внимание: даже идеальный план (патчинг в тот же день) несёт в себе 4 дня уязвимости, в которых команда никак не виновата. Эта часть окна — не провал производительности, а цена процесса раскрытия, соблюдения которого злоумышленники больше не гарантируют.

Для реального парка серверов данные о версиях на большинстве инфраструктур получаются одной командой:

 ansible webservers -m shell -a 'php -v | head -1'
 

А проверка KEV, которая должна приоритезировать вашу очередь критических задач, должна быть автоматическим фидом, а не веб-страницей, на которую кто-то иногда заходит:

 curl -s https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json \
  | jq '.vulnerabilities[] | select(.cveID=="CVE-2026-109618") | .dateAdded'
 

Проводите поиск угроз (threat hunting) от самой ранней обоснованной даты

Весь процесс сквозной аналитики выглядит так:

 first exploitation (known or estimated)
        |
        v
disclosure / patch release ----> patch diffing by attackers (hours)
        |
        v
your deployment completes -----> exposure ends for that host
        |
        v
hunt window: [first exploitation - safety margin, deployment date]
 

Первый этап — это выбор даты, а не инструмента. Когда выходит критическая уязвимость в широко используемом рантайме, устанавливайте начало поиска атак на самую раннюю дату эксплуатации из отчётов. Если в отчётах указано, что точная дата первой попытки неизвестна (как у Amazon в данном случае), отнимайте дополнительный запас времени. Для PHP2Shell поиск, начатый 21 октября, по определению пропустит зафиксированную попытку. Поиск с 17 октября зафиксирует её, но всё равно, скорее всего, стартует с задержкой.

Второй этап — сопоставление с инвентаризацией. Оценка уязвимости индивидуальна для каждого хоста и версии, поэтому проверка парка серверов должна запускаться по достоверным конфигурационным данным, включая PHP внутри контейнеров и тот единственный легаси-сервер, за который никто не отвечает. Сервисы, которые ушли в оффлайн на неделе раскрытия, по сути выбрали гарантированный плановый простой вместо неизвестного риска компрометации. Такой выбор возможен только в том случае, если вы заранее знаете, какие хосты уязвимы.

Третий этап — временные меры для хостов, которые нельзя пропатчить прямо сейчас. Виртуальный патчинг, правило WAF или фильтр на реверс-прокси, составленный по опубликованным рекомендациям (detection guidance), даёт время на деплой, не создавая иллюзии, что хост исправлен. Отслеживайте это как отдельное состояние в той же таблице. Хост за виртуальным патчем имеет статус mitigated (риск снижен). Он не пропатчен (patched), и в ваших отчётах эти два понятия никогда не должны подменять друг друга.

Где эта схема ломается

  • Дата первой эксплуатации — это нижняя граница, о которой вы узнаёте с задержкой. Amazon перехватила попытку 17 октября благодаря своим honeypot-сетям. Если уязвимый сервис не покрыт ловушками, даты у вас не будет вовсе, и честный показатель времени уязвимости превратится в оценочный, о чём нужно явно указывать в отчётах.
  • Повторное открытие уязвимостей работает в обе стороны. Тот же ИИ-инструментарий, который помог защитникам найти PHP2Shell, доступен кому угодно. Режим эмбарго теперь должен выдержать молчание не одного исследователя, а всех независимых специалистов, плюс утечки вроде слива CVE-идентификатора 20 октября.
  • Из-за диффинга патчей незаметный (silent) патч больше не является безопасным. Выпуск фикса без публичного бюллетеня (advisory) лишь задерживает тикет у защитников, тогда как атакующему, имеющему на руках две бинарные сборки, никакой бюллетень не нужен.
  • Ветки с истекшим сроком поддержки (EOL) ломают модель полностью. Никакие часы, даже самые честные, не помогут пропатчить рантайм, который никто не поддерживает. Для PHP 8.1 и старше в этом инциденте исправлений не было вообще.
  • Отключение сервиса иногда бывает правильным решением, но это всегда расходы. Википедия и DigiD могли позволить себе профилактический простоя. Платежный шлюз в конце квартала — навряд ли. Именно поэтому такие решения должны опираться на данные инвентаризации до кризиса, а не во время него.

Внедрять или пропустить?

Внедряйте, если у вас используется любой доступный из интернета рантайм или библиотека с большой базой установок, а текущие отчёты считают время патчинга от даты раскрытия уязвимости. Экспресс-план на полдня: добавьте поле «первая дата эксплуатации» в шаблон критических CVE, по умолчанию ставьте туда дату раскрытия, перезаписывайте её, как только появляются надёжные данные о более ранних атаках, и настройте поисковые запросы по логам на чтение этого поля вместо даты создания тикета. Добавьте проверку KEV-фида в пайплайн формирования очереди на патчинг. На этом всё. Два поля и один фид.

Пропустите, если ваша инфраструктура настолько мала, что критические патчи гарантированно накатываются в тот же день на абсолютно все хосты, включая контейнеры и легаси-серверы. В этом случае ваш таймер раскрытия и таймер эксплуатации отличаются на дни, которые вы уже закрыли, и изменение учёта не даст практической пользы. Главное — убедитесь, что легаси-сервер действительно есть в инвентаре, прежде чем претендовать на это исключение. Обычно его там нет.

Запускайте таймер тогда, когда это сделал атакующий

Возьмите ваш самый свежий критический патч — тот, который на дашборде отмечен как закрытый вовремя. Пересчитайте сроки относительно самой ранней даты эксплуатации, которую сможете найти в бюллетене вендора, записи KEV или отчётах honeypot-систем, и сдвиньте начало поиска атак на эту дату минус запас времени. Затем перезапустите поиск по логам за эти образовавшиеся дни. Оцените изменения: насколько вырос показатель времени уязвимости и потребовалось ли дополнительное внимание к событиям из этих «выпавших» дней. На основе этих данных решите, жизнеспособен ли ваш текущий месячный цикл патчинга.

Когда ваша команда отчитывается об эффективности патчинга, с какой даты запускается отсчёт и кто в вашем процессе отвечает за его сдвиг на более ранний срок, если появляются соответствующие данные?

Источники

  1. PHP2Shell: exploitation before disclosure for an AI found flaw, Cloud Security Alliance research note
  2. OffSec launches free PHP2Shell lab following critical vulnerability discovery, CIO
  3. Decade old PHP bug puts websites at risk: how AI uncovered it, Mint