На проверке WordPress RCE 2026: разбор цепочки wp2shell от SQL Injection до веб-шелла

Сергей Попов

Администратор
30.12.2015
6 175
6 946
Специализация
  1. OSINT
  2. Веб-безопасность
Статус верификации
  1. ✓ Verified
Расколотый керамический логотип WordPress с обнажённой платой внутри и выжженной дорожкой от SQL-запроса к красной точке веб-шелла. Лазерная гравировка с идентификаторами CVE на уцелевшей половине,...


За первые 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
По данным The Hacker News, после эксплуатации наблюдалось создание более 100 backdoor-аккаунтов администраторов, развёртывание фейковых плагинов для code execution и попытки установки Overlord RAT - Golang-based remote access trojan. По данным Wiz, 60% организаций на WordPress изначально имели хотя бы один уязвимый экземпляр, а 25% выставляли уязвимый сервер прямо в интернет.

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Что происходит
ReconnaissanceVulnerability Scanning (T1595.002)Массовое сканирование /wp-json/batch/v1
Initial AccessExploit Public-Facing Application (T1190)Route confusion + SQLi через batch endpoint
ExecutionUnix Shell (T1059.004)Self-destructing plugin выполняет системные команды
PersistenceWeb Shell (T1505.003)Запись веб-шелла в wp-content/uploads
Privilege EscalationValid Accounts (T1078)Создание rogue admin-аккаунта через forged changeset
Command and ControlIngress 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.5CVE-2026-60137 (SQLi only)6.8.6Средний - RCE невозможен
6.9.0 - 6.9.4Full chain (SQLi + route confusion = RCE)6.9.5Критический - pre-auth RCE
7.0.0 - 7.0.1Full 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);
Против Шага 6 (admin creation + webshell) - проверка IoC. Что искать:
  • Новые 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 может не логироваться)
После патча. По данным Wiz, наблюдались LFI-атаки для exfiltration 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 без подсказок.
 
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

Похожие темы

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🧭 Навигатор · ИБ 2026
Не знаешь, какой трек твой?
5 направлений ИБ, реальные зарплаты и точка входа для каждого — в одном треде.
JuniorSenior+
100K → 600K+ ₽ /мес
Открыть навигатор →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab