Сергей Попов
Администратор
- 30.12.2015
- 6 177
- 6 946
- Специализация
- OSINT
- Веб-безопасность
- Статус верификации
- ✓ Verified
За первые 72 часа после раскрытия wp2shell watchTowr зафиксировал десятки тысяч попыток эксплуатации через свои honeypots, а CISA внесла обе CVE в каталог Known Exploited Vulnerabilities уже 21 июля - через четыре дня после патча. Один анонимный POST-запрос к дефолтной инсталляции WordPress. Ни логина, ни плагинов, ни специальной конфигурации - и атакующий получает шелл. Разбираю wp2shell как пентестер: полную цепочку от type juggling в
WP_Query до self-destructing plugin, с конкретикой по воспроизведению и защите от массовых сканирований.Бизнес-логика атаки: зачем злоумышленнику wp2shell
wp2shell - не академическая уязвимость, а готовый примитив для массовых кампаний. Цепочка CVE-2026-63030 (CVSS 9.8, Critical, CWE-436) + CVE-2026-60137 (CVSS 5.9, Medium, CWE-89) даёт pre-auth RCE на CMS, которая, по оценкам Searchlight Cyber, крутится на более чем 500 миллионах сайтов. Подробнее - в нашем обзоре cve эксплойт разработка.Финальный импакт зависит от того, кто зашёл:
- Криптомайнеры и ботнеты - массовое сканирование, автоматическая установка майнера или включение в DDoS-ботнет
- Initial Access Brokers - получение шелла, продажа доступа ransomware-группировкам
- Целевые атаки - компрометация конкретного сайта, exfiltration данных, supply chain через trusted domain
EPSS-скоры подтверждают масштаб: CVE-2026-63030 - 0.9792 (percentile 0.9990, абсолютный top), CVE-2026-60137 - 0.7797 (percentile 0.9952, top 1%). Обе CVE получили CISA SSVC-решение «Act» - exploitation active, automatable: yes, technical impact: total.
С точки зрения OWASP, цепочка бьёт сразу по трём категориям: A03:2021 - Injection (SQL Injection в WP_Query), A01:2021 - Broken Access Control (обход авторизации через route confusion) и A07:2021 - Identification and Authentication Failures (batch endpoint позволяет анонимному вызову выполниться в привилегированном контексте).
Место wp2shell exploit в kill chain
[Применимо: внешний пентест, black box, дефолтная конфигурация WordPress 6.9.x / 7.0.x]Вся цепочка эксплойтов WordPress укладывается в один HTTP-запрос. Маппинг на MITRE ATT&CK:
| Этап | Техника MITRE ATT&CK | Что происходит |
|---|---|---|
| Reconnaissance | Vulnerability Scanning (T1595.002) | Массовое сканирование /wp-json/batch/v1 |
| Initial Access | Exploit Public-Facing Application (T1190) | Route confusion + SQLi через batch endpoint |
| Execution | Unix Shell (T1059.004) | Self-destructing plugin выполняет системные команды |
| Persistence | Web Shell (T1505.003) | Запись веб-шелла в wp-content/uploads |
| Privilege Escalation | Valid Accounts (T1078) | Создание rogue admin-аккаунта через forged changeset |
| Command and Control | Ingress Tool Transfer (T1105) | Загрузка secondary tools (Overlord RAT и аналоги) |
Принципиальное отличие от типичных WordPress-багов: цепочка живёт в ядре, не в плагине. «Мы не используем этот плагин» здесь не прокатит - endpoint
/wp-json/batch/v1 включён по умолчанию с WordPress 5.6.CVE-2026-60137: SQL инъекция WordPress через author__not_in
Предусловия: WordPress 6.8.0+. Без CVE-2026-63030 требует аутентификацию - bounded injection.Не работает если: целевой сайт на WordPress < 6.8 или уже обновлён до 6.8.6 / 6.9.5 / 7.0.2.
NVD CVSS: 5.9 (MEDIUM), вектор
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N. Score 5.9 из-за AC:H - в изоляции SQLi требует аутентификацию, что повышает сложность. CWE-89: Improper Neutralization of Special Elements used in an SQL Command.Механика type juggling
КлассWP_Query принимает параметр author__not_in, рассчитанный на массив целых чисел. Санитизация предполагает, что на вход придёт array - каждый элемент приводится к int и безопасно конкатенируется в NOT IN (...) clause.Проблема - классический PHP type juggling. Передаёте строку вместо массива: проверка типа проваливается тихо, путь санитизации для массива пропускается, и raw-значение попадает прямо в
WHERE-clause SQL-запроса без экранирования. По документам - массив обязателен. На практике - PHP молча проглотит строку и пустит её в запрос.Payload использует UNION-based injection. REST-контроллер экспонирует публичный параметр
author_exclude и маппит его на WP_Query::author__not_in. REST-схема объявляет author_exclude как array of integers, и при нормальном routing запрос валидируется по этой схеме до того, как попадёт в WP_Query. С per_page=-1 отключается пагинация, гарантируя выполнение полного SQL-запроса. UNION SELECT подбирается под 23 колонки таблицы wp_posts.Ограничения без CVE-2026-63030
REST-маршруты, экспонирующиеauthor__not_in, требуют аутентифицированную сессию. Анонимный запрос на /wp-json/wp/v2/posts с injected author_exclude вернёт HTTP 401 до построения запроса. На ветке 6.8.x (до 6.8.6) уязвимость есть, но full chain RCE невозможен - route confusion из CVE-2026-63030 появилась только в 6.9.CVE-2026-63030: route confusion и обход авторизации batch endpoint
Предусловия: WordPress 6.9.0+. Batch endpoint/wp-json/batch/v1 доступен анонимно.Не работает если: batch endpoint заблокирован на уровне WAF/веб-сервера, или WordPress обновлён до 6.9.5 / 7.0.2.
NVD CVSS: 9.8 (CRITICAL), вектор
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H. Все три impact-метрики High: Confidentiality, Integrity, Availability. CWE-436: Interpretation Conflict.Десинхронизация параллельных массивов
Batch endpoint принимает JSON с массивом sub-requests и выполняет их последовательно. Внутри WordPress поддерживает параллельные массивы:$requests (распарсенные объекты), $matches (кортежи route + handler), $validation (результаты валидации).Баг, введённый в 6.9: когда sub-request генерирует WP_Error, он добавляется в
$validation, но не в $matches. С этого момента $matches на один элемент короче. Второй цикл обрабатывает $requests по индексу $i и читает $matches[$i] - и каждый валидный запрос после ошибки получает handler от следующего запроса. Классический off-by-one, только в параллельных массивах.Результат: sub-request N выполняется под permission context sub-request N-1. Привилегированный запрос исполняется под обработчиком публичного маршрута - без проверки прав. По данным Greenbone, атакующий рекурсивно вызывает batch endpoint: первый десинк обходит авторизацию, второй - валидацию параметров. Нормально недопустимый GET с невалидированным input достигает posts handler.
Оба варианта маршрута уязвимы:
/wp-json/batch/v1 и ?rest_route=/batch/v1. WAF-правило, покрывающее только /wp-json-путь, оставляет query-string вариант открытым. Я видел такую ошибку на нескольких проектах - и каждый раз удивлялся, что никто не проверял ?rest_route=.Цепочка эксплойтов WordPress: от bounded SQLi до загрузки webshell
Вся последовательность укладывается в один HTTP POST-запрос. Каждый шаг злоупотребляет легитимным механизмом WordPress - ни одного «грязного» хака, только creative abuse штатных функций.Шаг 1: Route confusion. Batch endpoint получает вложенный malformed batch request. Десинхронизация массивов позволяет публичному маршруту «прикрыть» привилегированный.
Шаг 2: SQL Injection. Неотвалидированный
author_exclude (строка вместо массива) достигает WP_Query::author__not_in. UNION SELECT inject возвращает контролируемые строки данных.Шаг 3: Object hydration и cache poisoning.
WP_Query преобразует каждую возвращённую строку БД в WP_Post объект и кэширует его в request-local object cache. Последующие вызовы get_post($id) возвращают отравленный объект с полями, выбранными атакующим.Шаг 4: oEmbed write path. WordPress решает, что кэш oEmbed устарел, и пересохраняет пост, подтягивая недостающие поля из
get_post(). Объект отравлен - атакующий записывает контролируемые значения status, type, parent в БД. Predictable post_name вычисляется как md5(url + attributes).Шаг 5: Customize changeset abuse. Forged
customize_changeset row содержит user_id существующего администратора (обычно ID 1). При публикации changeset WordPress временно переключает текущего пользователя на этого администратора. Parent-loop repair (WordPress чинит запрещённые parent-циклы, когда пост становится собственным предком) создаёт вложенный save, который выполняется внутри admin-контекста.Шаг 6: Admin creation + self-destructing plugin. В контексте администратора batch request содержит
POST /wp/v2/users для создания нового admin-аккаунта. Затем inline plugin выполняет системную команду (обычно запись persistent webshell в wp-content/uploads), вызывает unregister_activation_hook и удаляет себя из wp_options. Файл в wp-content/plugins не остаётся - плагин-самоубийца.Условие persistent object cache
По данным Cloudflare (подтверждено Rapid7), code path эксплуатации работает только когда persistent object cache (Redis, Memcached) не используется. Дефолтная инсталляция не имеет persistent cache - значит, дефолтная инсталляция уязвима. Сайт с Redis/Memcached может быть вне этого конкретного пути, но это побочный эффект, а не фикс - SQLi остаётся.WordPress пентест: воспроизведение и детект wp2shell
📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме
-payload, обёрнутого в derived table для обхода оптимизаций MySQL. Если запрос со
SLEEP(5) стабильно отвечает на 5 секунд дольше контрольного - injection sink достижим. Полные payload-ы лежат в репозитории wp2shell-lab.Детект через Nuclei
ProjectDiscovery выпустили Nuclei-шаблон для CVE-2026-63030. Для массового сканирования периметра хватитnuclei -t http/cves/2026/CVE-2026-63030.yaml -l targets.txt.Ограничения воспроизведения
- Persistent object cache (Redis, Memcached) может блокировать path к RCE, но не к SQLi
- По данным Eye Security, payload путешествует внутри batch POST body, который «rarely appears in access logs» - детект по access-логам ненадёжен
- Self-destructing plugin удаляет себя: файл в
wp-content/pluginsне остаётся, если payload не пишет отдельный persistent webshell
WordPress массовое сканирование: защита и IoC
По данным KEVIntel, 13 уникальных IP из Швейцарии, Германии, Великобритании, Индонезии, Литвы, Нидерландов и Сингапура были связаны с эксплуатацией CVE-2026-63030. Атакующие используют blind, UNION-based и Boolean-based payloads - весь джентльменский набор SQL injection. Ryan Dewhurst (KEVIntel) отметил, что AI-assisted анализ делает воспроизведение уязвимости и разработку PoC «trivial» - порог входа для атакующих минимален.Версии и remediation matrix
| Ветка WordPress | Уязвимость | Исправлено в | Уровень риска |
|---|---|---|---|
| 6.8.0 - 6.8.5 | CVE-2026-60137 (SQLi only) | 6.8.6 | Средний - RCE невозможен |
| 6.9.0 - 6.9.4 | Full chain (SQLi + route confusion = RCE) | 6.9.5 | Критический - pre-auth RCE |
| 7.0.0 - 7.0.1 | Full chain (SQLi + route confusion = RCE) | 7.0.2 | Критический - pre-auth RCE |
| ниже 6.8 | Не затронуты | - | - |
CISA KEV deadline для CVE-2026-63030 - 24 июля, три дня после добавления. WordPress включил forced auto-update, но не подтвердил, достигает ли push сайты с отключёнными автообновлениями. А таких - прилично.
Контрмеры против конкретных шагов цепочки
Каждая контрмера привязана к конкретному шагу wp2shell, разобранному выше.Против Шага 1 (route confusion) - блокировка batch endpoint. На Nginx:
location ~* /wp-json/batch/v1 { return 403; } и проверка $args на rest_route=/batch/v1. Для ModSecurity - правило на REQUEST_URI и QUERY_STRING с pattern /batch/v1. Cloudflare, по данным The Hacker News, уже развернул managed WAF rules. Но есть подвох: полная блокировка ломает Gutenberg - block editor использует batch endpoint для оптимизации запросов. Компромисс - must-use plugin, блокирующий анонимные запросы и пропускающий авторизованные:
PHP:
add_filter('rest_pre_dispatch', function($result, $server, $request) {
if (strpos($request->get_route(), '/batch/v1') !== false
&& !is_user_logged_in()) {
return new WP_Error('rest_forbidden', '', ['status' => 403]);
}
return $result;
}, 10, 3);
- Новые admin-аккаунты:
SELECT * FROM wp_users u JOIN wp_usermeta m ON u.ID = m.user_id WHERE m.meta_key = 'wp_capabilities' AND m.meta_value LIKE '%administrator%'- сверить со списком известных администраторов - PHP-файлы в
wp-content/uploads- не должны быть там по умолчанию - Подозрительные строки в
wp_postsс типомcustomize_changesetи чужимuser_id - POST-запросы к
/batch/v1в access-логах (учтите: payload в POST body может не логироваться)
wp-config.php (database credentials, auth keys, salts). Даже после обновления - ротируйте database пароли и WordPress authentication keys в wp-config.php. Wiz также зафиксировал попытки доступа к admin-панели через harvested credentials - одним патчем тут не отделаешься.Почему классические сканеры пропускают wp2shell
Version scanners читают номер версии, который тривиально подменяется через тему или reverse proxy. Annual pentest фиксирует безопасность на один момент - баг, опубликованный 17 июля, не существует в отчёте от марта. По опыту пентестов WordPress-инфраструктур: уязвимый инстанс чаще всего находится на staging-поддомене или dev-окружении, которое не попало в scope. Тот самыйdev.company.ru, про который все забыли.Обнаружение wp2shell не требует деструктивного эксплойта. Оно требует полного маппинга периметра, идентификации всех WordPress-инсталляций и тестирования batch endpoint - непрерывно, а не раз в год. IBM X-Force фиксирует среднее время между публикацией CVE и устранением в организациях: 29 месяцев. Для wp2shell такая задержка фатальна - эксплуатация пошла в первые часы.
Я разбирал десятки WordPress-цепочек за последние годы - от SQLi в плагинах до десериализации. wp2shell принципиально другой по масштабу: не плагин с 50 тысячами установок, а ядро, покрывающее более 40% веба. При этом цепочка элегантна: каждый шаг злоупотребляет легитимным механизмом WordPress - oEmbed caching, customize changeset, parent-loop repair. Ни одного «грязного» хака, только creative abuse штатных функций.
CVSS-скоры здесь обманчивы, и это важный урок. CVE-2026-63030 (route confusion) получила 9.8 Critical, а CVE-2026-60137 (SQLi) - всего 5.9 Medium. Но в изоляции route confusion - «всего лишь» interpretation conflict, а SQLi без route confusion не выходит за границы аутентифицированного контекста. Именно комбинация превращает два бага разной severity в pre-auth RCE. Вывод для пентестера: не отбрасывайте баги по отдельному CVSS-скору - ищите chain potential. Bounded SQLi, который скоринговая модель оценивает как Medium, плюс parsing flaw, которая сама по себе не даёт ничего кроме confused routing, - вместе дают полный контроль над сервером. Если на вашем периметре стоят необновлённые WordPress-инсталляции и хочется самому пройти всю цепочку от batch desync до RCE - на HackerLab есть лаба, где подобный chain-exploitation разворачивается end-to-end без подсказок.