
Скрипт только для чтения для триажа сайтов WordPress после PHP2Shell (CVE-2026-109618, CVE-2026-109619). Проверяет признаки компрометации и записывает отчёт по каждому сайту.
Скрипт для триажа только для чтения для сайтов WordPress после PHP2Shell (CVE-2026-109618 и CVE-2026-109619) — пары критических уязвимостей удалённого выполнения кода в движке PHP, раскрытых 21 октября 2026 года.
Установка патча PHP закрывает дверь, но ханипоты Amazon зафиксировали попытку эксплуатации 17 октября, за четыре дня до публичного раскрытия, пока уязвимости всё ещё были под эмбарго. Это самая ранняя активность, которую кто-либо опубликовал, а не день начала атак, поэтому считайте это нижней границей. Любой сайт, который был доступен на неисправленном PHP в течение этого окна, может уже содержать бэкдор, и обновление PHP его не удалит. Этот скрипт проверяет один или несколько сайтов WordPress на то, что злоумышленник с выполнением кода обычно оставляет после себя, и записывает отчёт в формате Markdown для каждого.
Третья уязвимость, CVE-2026-115560, была объявлена 23 октября. Для неё
пока нет исправленной сборки: октябрьские точечные релизы (8.2.35, 8.3.36,
8.4.27, 8.5.12) её не покрывают, а исправление ожидается со следующим
регулярным релизом в ноябре. php.net просит всех установить
session.upload_progress.enabled=0 в php.ini или пуле PHP-FPM до этого
момента. Скрипт проверяет, сделано ли это.
Он никогда ничего не изменяет и не удаляет. Он перечисляет, проверяет и сообщает. Что делать с находками — решать вам.
| Область | Проверка |
|---|---|
| PHP | Установленная версия CLI против исправленных релизов (8.2.35, 8.3.36, 8.4.27, 8.5.12). Отмечает ветки с истёкшим сроком поддержки, для которых php.net не выпустил релиз; см. Старые ветки PHP. |
| Прогресс загрузки | Установлено ли session.upload_progress.enabled=0 — мера против CVE-2026-115560 — там, где это имеет значение: собственное представление PHP-FPM (php-fpm -i), каждый читаемый php.ini, conf.d, пул FPM и конфигурация Apache. .user.ini и .htaccess уровня сайта сообщаются, но не засчитываются, поскольку php.net говорит, что они не работают для этой настройки. |
| Целостность ядра | wp core verify-checksums против wordpress.org |
| Целостность плагинов | wp plugin verify-checksums --all, а также перечисление премиум/пользовательских плагинов, которые невозможно проверить |
| Загрузки | Любой PHP-исполняемый файл в wp-content/uploads (игнорирует заглушки «Silence is golden») |
| Недавние изменения | Файлы PHP и конфигурации, изменённые с начала окна (по умолчанию 2026-10-17). Пакеты переводов WordPress (wp-content/languages/**/*.l10n.php) учитываются отдельно после прохождения строгой проверки формы; не прошедший её помечается. |
| Prepend-хуки | auto_prepend_file / auto_append_file в .user.ini, .htaccess, php.ini |
| Must-use плагины | Содержимое wp-content/mu-plugins (выполняется при каждом запросе, скрыто от обычного списка плагинов) |
| Шаблоны кода | Классические сигнатуры веб-шеллов (eval(base64_decode(...)), $_POST[...](), и т. д.) в wp-content |
| Пользователи | Все администраторы и любые учётные записи, зарегистрированные внутри окна |
| База данных | siteurl/home, опции и записи, содержащие внедрённые скрипты или обфусцированный код |
| Cron | Запланированные события WP-Cron |
| Журналы | Журналы доступа: ответы 5xx в день для окна против базовой линии до 7 дней перед ним, а также URL, возвращающие 5xx в окне. Журналы ошибок и PHP-FPM: падения воркеров (segfault/bus error). |
| Сервер | crontab пользователя сайта и SSH authorized_keys |
| Docker | Каждый запущенный контейнер с бинарником php: версия PHP внутри него и работает ли контейнер всё ещё на более старой сборке, чем образ на диске (загружен, но не пересоздан) |
WP-CLI запускается с --skip-plugins --skip-themes, чтобы плагин или тема с
бэкдором не могли выполниться во время проверки.
На каждой проверяемой машине:
bash 3.2+, find, grep и sort -V (стандартно на хостах Linux; также работает со стандартной рабочей станции macOS)php в PATH для проверки версииДля удалённых сайтов: доступ по SSH-ключу с машины, на которой вы запускаете скрипт. Скрипт запускает сам себя по SSH; на удалённом хосте ничего не нужно устанавливать сверх вышеперечисленного.
curl -O https://raw.githubusercontent.com/brentmartinmiller/php2shell-check/main/php2shell-check.sh
chmod +x php2shell-check.sh
# One site
./php2shell-check.sh /var/www/example.com
less php2shell-reports/*.md
# Every site on the box
printf '%s\n' /var/www/*/public_html > sites.txt
./php2shell-check.sh sites.txt
cat php2shell-reports/SUMMARY.md
Запускайте его от имени пользователя, которому принадлежат файлы сайта, где это возможно. Он работает и как root
(WP-CLI получает --allow-root), но тогда проверки crontab и SSH-ключей инспектируют
данные root, а не веб-пользователя.
git clone https://github.com/brentmartinmiller/php2shell-check.git
cd php2shell-check
cp sites.txt.example sites.txt # edit: one site per line
./php2shell-check.sh sites.txt
Для каждой записи user@host:path скрипт подключается по SSH, запускает себя там и возвращает отчёт в php2shell-reports/ на вашей машине. На удалённом хосте ничего не устанавливается. SSH работает с BatchMode=yes, поэтому требуется доступ по ключу, работающий без запроса пароля.
Большинство общих хостов (Hostinger, SiteGround, Bluehost и подобные) предлагают SSH, но часто не имеют wp в пути. Проверьте с помощью which wp. Если его нет, скачайте WP-CLI один раз в свой домашний каталог и укажите на него скрипту:
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
WP="php $HOME/wp-cli.phar" ./php2shell-check.sh domains/example.com/public_html
Без WP-CLI проверки файловой системы всё равно выполняются, а в отчёте отмечается, что было пропущено. В любом случае уточните у хостера напрямую, когда он развернул пропатченную версию PHP, поскольку это единственное, что проверка на уровне аккаунта увидеть не может.
В sites.txt по одному сайту на строку — либо локальный путь, либо user@host:path.
Относительные удалённые пути отсчитываются от домашнего каталога SSH-пользователя. Строки,
начинающиеся с #, игнорируются. См. sites.txt.example. Файл находится в
.gitignore, чтобы имена хостов клиентов не попадали в коммит.
Нестандартные SSH-порты, ключи и jump-хосты указываются в ~/.ssh/config:
Host client-a
HostName 203.0.113.10
Port 2222
User deploy
IdentityFile ~/.ssh/client-a
Затем укажите сайт как client-a:/var/www/html.
php -v на хосте Docker ничего не говорит о контейнерах, а загрузка
фиксированного образа ничего не исправляет, пока контейнер не будет пересоздан из него. Сайт одной
команды простоял на 8.3.35 три дня после загрузки именно по этой
причине.
Когда скрипт запускается на хосте, где работает docker ps, в отчёт добавляется
раздел Docker containers со списком всех запущенных контейнеров, в которых есть
бинарный файл php, версией PHP внутри него и признаком того, устарел ли контейнер
(более новая сборка его образа есть на диске, но контейнер так и не был
пересоздан). Пересоздайте с помощью docker compose up -d или остановите, удалите и запустите
контейнер заново.
Чтобы выполнить полную проверку внутри контейнера, укажите его как цель:
docker:wordpress-php-1:/var/www/html