Аудитория: люди, которые управляют сайтами на PHP и применяют экстренное обновление PHP или только что его применили и беспокоятся о пробелах.
В октябре 2026 года были выпущены исправленные версии для двух критических уязвимостей PHP (CVE-2026-109618 и CVE-2026-109619). Сайт, которым управляет оператор этого сайта, был обновлён в ту же ночь. Эта страница переводит произошедшее там, два первых предположения, которые оказались неверными, и повторную проверку на взлом, примененную затем ко всем серверам, в общие шаги. Об уязвимостях и исправленных версиях рассказано в статье Две критические уязвимости PHP и экстренные исправленные выпуски (октябрь 2026).
Что произошло (на сайте, которым управляет оператор этого сайта)
Сайт использует PHP в более чем одной среде. Поскольку команда PHP заранее объявила, когда выйдут исправленные версии, оператор перевёл сайт на страницу обслуживания до выпуска и оставался офлайн, пока исправленная версия не появилась. После выпуска он установил её и снял страницу обслуживания.
После объявления
Переход на страницу обслуживания и ожидание исправленной версииПосле выпуска
Установка исправленной версии и снятие страницы обслуживанияПроверка после обновления
Веб-часть была обновлена, но PHP в командной строке, которым пользовался cron, всё ещё указывал на старую версию, поэтому его перенастроилиВ ту же ночь
Временный скрипт, который снимал режим обслуживания, остановился до записи чего-либо. Причина: set -e в сочетании с diff. Вреда не было, скрипт переписалиПозже
Тот же чек-лист проверки на взлом применили к каждому серверу, а один подозрительный запрос, пришедший до обновления, проверили сравнением файлов
Позднее американское CISA добавило обе уязвимости CVE в свой каталог уязвимостей, известных как эксплуатируемые (KEV) (объявление CISA). Это подтверждает решение спешно обновиться, но патч лишь закрывает дверь. Проверка того, что никто уже не проник внутрь, — отдельная задача.
Предположение 1: «Веб-PHP обновлён, значит, всё готово»
Первое предположение состояло в том, что если сайт работает на новой версии, весь PHP на этом сервере обновлён. На деле PHP в командной строке, который вызывал cron, всё ещё указывал на старую версию.
PHP может быть разной программой в зависимости от способа запуска (SAPI, Server API). php.net отмечает, что CLI SAPI во многом отличается от других SAPI (PHP: использование PHP из командной строки). Если на одной машине установлено несколько версий, веб может работать на новой версии, а командная строка — на старой.
Веб-запросы → PHP-FPM / модуль Apache
Работает обновлённая версия (видна в Site Health и похожих экранах)
cron и скрипты оболочки → PHP в командной строке
Всё ещё указывает на старую версию, которую веб-проверка не покажет
Проверяйте веб и CLI отдельно
Проверьте версию PHP в командной строке
php -v показывает версию PHP, которую вызывает эта оболочка. php.net указывает, что php -v также сообщает, является ли PHP сборкой CGI или CLI. which php показывает расположение исполняемого файла, который действительно вызывается.
В системах в стиле Debian с несколькими версиями проверьте, куда указывает команда
update-alternatives --display php показывает, на какую версию указывает команда php (руководство update-alternatives). --config меняет её, но это изменение конфигурации сервера, поэтому сначала уточните порядок действий и способ отката.
Указывайте полный путь к PHP в заданиях cron
cron запускает задания в собственной среде, отдельной от интерактивной оболочки входа (crontab(5)). Если вы пишете только php, то выбранный PHP зависит от этой среды. Совет этого сайта: укажите полный путь к версии, которую вы обновили, чтобы проверка после обновления давала однозначный ответ.
Проверьте версию веб-PHP со стороны веба
В WordPress её показывает Site Health на экране администратора. В коде доступны константы PHP_VERSION (версия) и PHP_SAPI (способ запуска PHP: cli для CLI, fpm-fcgi для PHP-FPM, apache2handler для модуля Apache) (php_sapi_name).
Удаляйте страницу phpinfo() сразу после проверки
Публичный файл, который вызывает phpinfo(), показывает версию, но также раскрывает настройки конфигурации и внутренние детали сервера. Удалите его сразу после работы. Неоставление его на сайте — условие использования этого метода.
Предположение 2: «diff либо успешен, либо завершается с ошибкой»
Временный скрипт, который снимал режим обслуживания, был написан так, чтобы перед записью сравнить текущее и новое содержимое с помощью diff. В нём был включён set -e.
diff возвращает код выхода 1, когда находит различия. В руководстве GNU 0 означает отсутствие различий, 1 — что различия найдены, а 2 — проблему (GNU diffutils: diff). Поиск различий как раз и ожидался скриптом. Но set -e (errexit) останавливает оболочку при ненулевом коде, кроме таких мест, как условия. Скрипт остановился из-за найденных различий и так и не дошёл до записи.
Поэтому шаг снятия режима обслуживания остановился без записи, и вреда не было. Он завершился в безопасную сторону, но обратное сочетание, когда скрипт не останавливается там, где должен, было бы опасным. Скрипт переписали.
Команды, где код выхода 1 не означает «ошибку»
| Команда | Значение 1 | Значение 2 | Источник |
|---|---|---|---|
diff | Найдены различия | Проблема | GNU diffutils: diff |
cmp | Найдены различия | Проблема | GNU diffutils: cmp |
grep | Не выбрано ни одной строки (нет совпадений) | Произошла ошибка | GNU grep |
Безопасные шаблоны
Руководство GNU Bash говорит, что при set -e оболочка не завершается, если команда возвращает ненулевой код как часть проверки в if, в списке && или || не на последнем месте или если её статус инвертирован с помощью ! (GNU Bash: The Set Builtin). Используйте это.
Останавливает скрипт или скрывает реальный сбой
- Голый
diff a bприset -e(останавливается только из-за найденных различий) diff a b || true(поглощает и «различия», и реальную ошибку (2))|| trueпослеgrep, из-за которого «нет совпадений» и «не удалось прочитать файл» выглядят одинаково
Обрабатывайте коды выхода отдельно
- Сохраняйте код выхода в переменную и разветвляйте по 0, 1 и 2 (рекомендуемый шаблон)
- Если нужно только знать, есть ли различия,
if diff -q a b; then …подойдёт, но учтите, что 2 (ошибка) попадает в ту же ветку, что и «различия» - Если используете
|| true, сначала решите, допустимо ли пропустить 2 (реальную ошибку)
Вот пример, который различает 0, 1 и 2. Сбой в левой части || освобождён от errexit, поэтому скрипт не останавливается, а rc содержит код выхода.
#!/usr/bin/env bash
set -euo pipefail
rc=0
diff -rq deployed/ live/ || rc=$?
case "$rc" in
0) echo "no differences" ;;
1) echo "differences: review before continuing" ;;
*) echo "diff failed" >&2; exit 2 ;;
esacset -e не является полной страховкой. У него много исключений, которые могут скрывать ошибки, поэтому для важных ветвей проще самому читать код выхода и принимать решение (совет этого сайта).
После обновления: выполните те же проверки на каждом сервере
Обновление закрывает точку входа. Оно не говорит, проник ли кто-то до обновления. Для уязвимостей KEV CISA просит проверить наличие взлома до обновления (см. «Подтверждённая эксплуатация и добавление в KEV CISA» в статье о PHP). Чтобы проверки не отличались от сервера к серверу, оператор применил один и тот же чек-лист ко всем серверам.
| Пункт | Что искать | Пример команды |
|---|---|---|
| Ключи для входа | Публичные ключи, которые вы не помните как добавляли | Открыть authorized_keys |
| Пользователи | Неизвестные пользователи, изменения в административных группах | getent passwd, getent group sudo |
| Изменённые файлы пакетов | Файлы, установленные пакетом, которые изменились с момента поставки | debsums -s (семейство Debian), rpm -Va (семейство RHEL) |
| Исполняемые файлы без владельца-пакета | Новые исполняемые файлы в /usr/local, /opt или в PATH, которые не принадлежат ни одному пакету | dpkg -S <file> (семейство Debian) или rpm -qf <file> (семейство RHEL); ищите файлы без пакета-владельца |
| SUID | Новые файлы, которые запускаются с правами владельца | find / -xdev -perm -4000 -type f |
| cron и systemd | Неизвестные запланированные задания и таймеры | crontab -l, /etc/crontab, /etc/cron.d, systemctl list-timers |
| /tmp | Исполняемые файлы, размещённые там | find /tmp /var/tmp -type f -perm /111 |
| Исходящие соединения | Соединения с адресатами, которые вы не узнаёте | ss -tunp |
| Внутри контейнеров | Запущенные процессы и файлы, записанные после старта | docker top, docker diff |
crontab -l показывает только задания текущего пользователя, поэтому проверяйте /etc/crontab, /etc/cron.d и спулы cron для каждого пользователя отдельно. Чтобы ss -tunp показал процесс за каждым соединением, нужны права root. dpkg -S и rpm -qf сообщают, какой пакет владеет файлом; «нет пакета-владельца» может означать файл, который вы разместили сами, поэтому сверяйте его с тем, что, как вы помните, устанавливали.
find -perm -4000 находит файлы, у которых установлены все заданные биты (find(1)). docker diff перечисляет файлы, изменённые с момента создания контейнера (A: добавлен, C: изменён, D: удалён), а docker top показывает запущенные процессы (docker container diff, docker container top). Контейнеры обычно пересобирают, а не меняют, поэтому исполняемый файл, показанный docker diff, считайте неожиданным и разбирайтесь с ним (совет этого сайта).
Как читать каждый пункт, а также шаги для общего хостинга, WordPress и VPS описаны в статье Как проверить, был ли взломан ваш сайт или сервер.
Разрешите подозрительную запись в журнале сравнением файлов
Во время этой проверки команда обнаружила, что до обновления POST-запрос пришёл в контактную форму с Tor-узла выхода (точки выхода сети, которая скрывает отправителя). Журнал показывает только то, что запрос пришёл. Он не показывает, было ли что-то записано в результате.
Поэтому они сравнили живые публичные файлы с копией, сохранённой при развёртывании. Они совпали полностью, лишних файлов PHP не было. Эта проверка не нашла признаков того, что какой-либо файл был изменён.
Храните копию развёрнутого вне сервера
Если единственное, с чем можно сравнить, находится на самом сервере, одновременное изменение останется незамеченным. При каждом развёртывании храните опубликованное состояние вне сервера.
Сравните его с живыми файлами
Используйте diff -rq, чтобы найти различающиеся файлы, или сравните списки хешей по каждому файлу (sha256sum). Если что-то отличается, проверьте, было ли это изменение намеренным.
Ищите и добавленные файлы
Даже если содержимое совпадает, новый файл PHP отсутствует в исходной копии, поэтому его нужно искать отдельно. diff -rq также сообщает о файлах, которые есть только с одной стороны.
Совпадение не является доказательством безопасности
Этот метод лишь показывает, что сравниваемые файлы не изменились. Он не видит базу данных, папки загрузок или изменения вне сервера. Оценивайте его вместе с другими проверками (совет этого сайта).
Взгляд этого сайта: составьте чек-лист до ночи экстренных работ
В спешке ночью то, что на каком сервере проверили, обычно различается. Если список пунктов определён заранее, каждый сервер проходит одинаковые проверки, и пробелы видны. Старый PHP в командной строке удалось найти именно потому, что проверка после обновления не ограничилась вебом.
Что сделать сегодня
- Если можете войти на сервер, выполните
php -vиwhich phpи укажите полный путь к PHP в заданиях cron - Проверьте версию веб-PHP со стороны веба, например в Site Health (и удалите страницу
phpinfo()сразу после использования) - Найдите в скриптах с
set -eвызовыdiff,grepилиcmpи решите, как обрабатывать код выхода 1 - Храните копию опубликованного вне сервера и сравнивайте её после обновлений
- Сделайте чек-лист проверки на взлом одинаковым на каждом сервере и проходите его пункт за пунктом
Источники
- PHP: использование PHP из командной строки
- PHP: php_sapi_name
- GNU Bash Reference Manual: The Set Builtin
- GNU diffutils: diff, cmp, GNU grep
- update-alternatives(1), crontab(5), find(1)
- docker container diff, docker container top
- CISA: добавлены две уязвимости в каталог известных эксплуатируемых уязвимостей
Читайте дальше
- Уязвимости и исправленные версии: Две критические уязвимости PHP и экстренные исправленные выпуски (октябрь 2026)
- Проверка на взлом: Как проверить, был ли взломан ваш сайт или сервер
- Подготовка: Основы резервного копирования и восстановления / Чек-лист инвентаризации безопасности
- Термины: Что такое RCE (удалённое выполнение кода)? / Что такое IOC (индикатор компрометации)?