Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
wp2shell — PoC per CVE-2026-63030 + CVE-2026-60137, noto anche come WP2Shell | Kitploit
Strumenti/GitHubGitHub/crypto-cat/wp2shell
Scanner di VulnerabilitàAnalisi del CodiceExploitSicurezza WebApprendimento e Formazione
GitHubcrypto-cat/wp2shell

wp2shell

PoC per CVE-2026-63030 + CVE-2026-60137, noto anche come WP2Shell

Vedi Repository
31 mese faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

wp2shell

Esecuzione remota di codice pre-autenticazione per WordPress 6.9.0–6.9.4 e 7.0.0–7.0.1.

Combina CVE-2026-63030 (SQLi da confusione delle route del batch) con CVE-2026-60137 (rientro del changeset del customizer) per ottenere la creazione di un amministratore senza autenticazione e l'esecuzione di comandi del sistema operativo. Nessun cracking delle password richiesto.

wp2shell demo

Un ringraziamento a hashkitten per la scoperta; leggi l'analisi tecnica completa di SLCyber qui.

La vulnerabilità

Il processore batch dell'API REST di WordPress (serve_batch_request_v1) ha un bug di indicizzazione off-by-one: quando wp_parse_url() fallisce su un percorso di sotto-richiesta, il WP_Error risultante viene aggiunto a $validation[] ma non a $matches[]. Questo desincronizza i due array — ogni richiesta successiva viene instradata tramite l'handler sbagliato.

Annidando un batch strutturato con cura all'interno di un altro batch, un attaccante può:

  1. Instradare una richiesta validata dallo schema di un endpoint attraverso il callback di un endpoint completamente diverso
  2. Iniettare SQL non sanificato tramite author__not_in (il cast stringa→array salta absint())
  3. Usare UNION SELECT per avvelenare la cache degli oggetti di WordPress con falsi oggetti post
  4. Attivare una pubblicazione automatica del changeset che eleva i privilegi, quindi rientrare nell'API REST con contesto admin

Una volta completata la configurazione (scoperta del prefisso delle tabelle e dell'ID admin), il payload di escalation si attiva in una singola richiesta HTTP — avvelenamento della cache, escalation dei privilegi e creazione dell'utente avvengono tutti lato server in un unico round-trip.

Come funziona la catena

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)                 │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Avvelenamento della cache (7 post falsi tramite UNION):

  • Un post trigger con uno shortcode [embed] nel suo contenuto
  • Un post changeset (customize_changeset, stato future, data nel passato)
  • Un partner del loop esterno (parent=changeset, che crea Loop 1)
  • Un target oEmbed (ID dinamico anti-ricorsione, parent=changeset, contenuto vuoto)
  • Un post di voce di menu (avvelenato come post_type=nav_menu_item per il controllo is_nav_menu_item)
  • Un post di rientro (post_type=request, post_status=parse, parent=inner)
  • Un partner del loop interno (parent=re-entry, che crea Loop 2)

Flusso di esecuzione:

  1. UNION avvelena la cache degli oggetti con tutti e 7 i post falsi
  2. L'handler dei post renderizza il contenuto del post trigger → lo shortcode [embed] si attiva
  3. La ricerca nella cache oEmbed trova un post di supporto con contenuto vuoto → viene eseguito il fallthrough a wp_update_post
  4. wp_update_post legge il changeset in cache (parent=outer) → il controllo di gerarchia rileva Loop 1
  5. La correzione scrive il changeset nel DB con stato future → conversione automatica in publish
  6. _wp_customize_publish_changeset si attiva → wp_set_current_user(admin_id) → contesto admin attivo
  7. Il changeset elabora nav_menu_item[real_id] — la cache dice type=nav_menu_item → percorso UPDATE
  8. object_id risolve un post in cache con post_parent=re-entry → wp_update_post sul post reale
  9. Il controllo di gerarchia ( non zero) rileva Loop 2 (re-entry ↔ inner)

Una variabile di sessione MySQL anti-ricorsione (@_wp2s) garantisce che la catena si attivi esattamente una volta e non vada in loop.

Funzionalità

  • Tre modalità di estrazione con rilevamento automatico: UNION (1 richiesta/valore), basata su errori tramite EXTRACTVALUE (~30 caratteri/richiesta), ricerca binaria boolean-blind (~7 richieste/carattere)
  • RCE pre-auth completa — nessuna credenziale, nessun cracking, l'escalation si attiva in un singolo round-trip
  • Auto-scoperta — prefisso delle tabelle tramite INFORMATION_SCHEMA, ID utente admin tramite meta delle capability
  • Post-exploitation — webshell come plugin con autenticazione tramite token, shell interattiva con monitoraggio della CWD, lettura/scrittura file
  • Modalità di pulizia — --cleanup elimina l'utente creato e rimuove la webshell all'uscita
  • Zero dipendenze — solo stdlib, file singolo, funziona su Python 3.8+

Installazione

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

Niente pip install, niente virtualenv. È un singolo file.

Utilizzo

Verifica se un target è vulnerabile

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

Estrazione dei dati

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

Sfruttamento completo

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

Shell autenticata (con credenziali esistenti)

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

Requisiti per la RCE completa

I comandi check e read funzionano su qualsiasi target interessato. La catena exploit ha tre requisiti aggiuntivi:

Se il target utilizza Redis o Memcached come cache degli oggetti, split_the_query viene forzato indipendentemente da per_page e le righe UNION vengono scartate durante il recupero dei soli ID. Il comando read funziona comunque (l'estrazione blind non necessita che UNION sopravviva nella cache), ma exploit fallirà.

Versioni interessate

BranchVulnerableFixed
6.9.x6.9.0 – 6.9.46.9.5
7.0.x7.0.0 – 7.0.17.0.2

La patch aggiunge $matches[] = $single_request; per i casi di errore (correggendo l'off-by-one) e un controllo di rientro in serve_request().

Architettura

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

Dettagli tecnici

Perché /wp/v2/widgets come route sorgente?

Il controller Widgets non registra per_page, orderby o author_exclude nello schema del suo endpoint. Questi parametri superano la validazione senza modifiche (i parametri sconosciuti vengono ignorati dal validatore dello schema). Quando la desincronizzazione instrada questa richiesta attraverso il controller Posts, questi valori grezzi fluiscono direttamente in WP_Query.

Perché per_page=500?

class-wp-query.php:3375 — split_the_query richiede !empty($limits) && posts_per_page < 500. Con per_page=500, la condizione 500 < 500 è falsa, quindi split_the_query viene disabilitato. La query completa (inclusa UNION) viene eseguita come unica istruzione e tutte le righe iniettate sopravvivono nel set di risultati e nella cache.

Perché nav_menu_item[real_id] (ID positivo)?

Usare un ID post positivo fa entrare nel percorso UPDATE a nav-menu.php:614, che chiama wp_update_post con un $post_id non zero. Questo è fondamentale perché wp_check_post_hierarchy_for_loops a post.php:8070 esce anticipatamente quando $post_id = 0 (nuovi post). La cache viene avvelenata con post_type=nav_menu_item per quell'ID, così is_nav_menu_item() supera il controllo del tipo a nav-menu.php:426. Il percorso UPDATE attiva quindi il controllo di gerarchia che rileva Loop 2.

Perché due loop di gerarchia?

Il Loop 1 (changeset ↔ outer) attiva la pubblicazione del changeset e imposta il contesto admin. Il Loop 2 (re-entry ↔ inner) si attiva durante la finestra admin (all'interno della chiamata save() dell'impostazione della voce di menu nel loop di pubblicazione del changeset) e attiva parse_request → rientro nell'API REST. I loop sono indipendenti perché la correzione del Loop 2 deve scrivere il post di rientro nel DB durante la finestra admin alla riga 3581 — prima del reset alla riga 3589.

Dichiarazione di non responsabilità

Questo strumento è pubblicato a scopo di test di sicurezza autorizzati e ricerca. Utilizzalo solo su sistemi di tua proprietà o per i quali disponi di un'autorizzazione scritta esplicita al test. L'accesso non autorizzato a sistemi informatici è illegale.

Crediti

Ricerca e sviluppo a cura di CryptoCat.

Scarica lo strumento
$post_id
  • La correzione chiama wp_update_post(re-entry) → scrive type=request, status=parse nel DB
  • wp_transition_post_status attiva do_action("parse_request") → rest_api_loaded() → serve_request()
  • L'API REST rientra e rielabora l'intero batch con privilegi di amministratore
  • POST /wp/v2/users in coda riesce → amministratore creato → die()
  • RequirementWhyDefault WP?
    At least one published postoEmbed needs a local URL to trigger embed processingYes (Hello World)
    No persistent object cacheSplit-the-query must be disabled for UNION rows to surviveYes (file cache default)
    REST API accessibleRe-entry via parse_request needs the REST serverYes
    Direct filesystem writePlugin upload needs FS_METHOD=direct or PHP owning wp-contentYes (most hosts)