
Verificatore proof-of-concept solo locale per CVE-2026-100671, che riproduce la divulgazione e il replay del cookie di sessione della cache delle pagine Twig di Grav su target loopback.
Verificatore lato attaccante minimale e limitato al target per il takeover di sessione tramite content-Twig/page-cache di Grav, corretto in Grav 2.0.25.
Avvisi:
L'attaccante non possiede il nome utente o la password dell'amministratore.
La catena reale è:
L'azione dell'amministratore avviene al di fuori di questo script. exploit.py non accetta
le impostazioni GRAV_ADMIN_USERNAME o GRAV_ADMIN_PASSWORD.
Il loopback è il confine di sicurezza predefinito. Un target non loopback richiede il
flag esplicito --allow-remote, destinato a una macchina HTB, TryHackMe o di laboratorio di tua proprietà
e autorizzata.
Il client segue i redirect solo quando rimangono sull'origine del target selezionato,
utilizza client HTTP della libreria standard di Python, applica timeout alle richieste e stampa
il cookie catturato solo quando viene fornito esplicitamente --show-cookie.
Utilizzare solo contro un'istanza Grav di tua proprietà o che sei esplicitamente autorizzato a testare. Lo script crea una pagina di prova pubblica. Rimuovere quella pagina dopo il test se il target conserva i contenuti tra un'esecuzione e l'altra.
get_cookie() legge lato server il cookie di sessione dell'amministratore in visita.L'account di scrittura delle pagine necessita solo del percorso di scrittura a privilegi bassi. Non necessita di
api.pages.read, admin.*, accesso super-admin o credenziali dell'amministratore.
2.0.19 fino a 2.0.24: vulnerabile con la configurazione predefinita di content-Twig
fornita.2.0.0 fino a 2.0.18, e 1.7.x: interessate solo quando il Twig dei contenuti
era esplicitamente abilitato.2.0.25: corretto.Eseguire un target Grav con:
Questo repository di due file non fornisce Grav, PHP, plugin, account o un bot amministratore. La visita della vittima rimane un'azione esterna.
Impostare solo la credenziale o il token dell'autore a privilegi bassi:
export GRAV_TARGET=http://127.0.0.1:8080
export GRAV_WRITER_USERNAME=low
export GRAV_WRITER_PASSWORD='local-writer-password'
python3 exploit.py
Lo script crea una route pubblica casuale, ne stampa l'URL e attende. Fai visitare quell'URL una volta all'amministratore, quindi premi Invio. Lo script esegue la raccolta anonima e il replay locale dell'API.
Usa invece un token API writer esistente:
export GRAV_WRITER_TOKEN='local-low-privilege-token'
python3 exploit.py
Utile per un terminale non interattivo o per un browser della vittima separato.
Prepara la pagina:
python3 exploit.py --prepare-only
Fai visitare l'URL stampato a un amministratore. Poi raccogli e riproduci usando la route stampata; per questa seconda fase non è necessaria alcuna credenziale:
python3 exploit.py --collect-only --route /cookie-proof-XXXXXXXX --show-cookie
--show-cookie stampa il cookie di sessione catturato per il debug locale/authorized lab:
captured_cookie=grav-site-<hash>=<session-value>
Il valore è una credenziale attiva. Non incollarlo su GitHub, issue, chat,
screenshot o log. Senza --show-cookie, lo script dimostra lo stesso
impatto tramite lo stato di replay, mantenendo il valore fuori dall'output.
Se la raccolta avviene prima della visita dell'amministratore, la richiesta dell'attaccante potrebbe popolare la cache con un valore vuoto. Preparare una nuova route casuale e ripetere.
Non codificare un IP macchina nel repository. Fornire il target corrente a runtime:
export GRAV_WRITER_USERNAME=low
export GRAV_WRITER_PASSWORD='writer-password'
python3 exploit.py \
--target http://10.10.10.25:8080 \
--allow-remote \
--prepare-only
Dopo che l'amministratore/bot visita l'URL stampato, raccogli e riproduci:
python3 exploit.py \
--target http://10.10.10.25:8080 \
--allow-remote \
--collect-only \
--route /cookie-proof-XXXXXXXX \
--show-cookie
--base-url rimane un alias di compatibilità per --target. HTTP remoto stampa
un avviso di testo in chiaro; usa HTTPS dove la macchina lo supporta.
page_create_status=201
victim_action_url=http://127.0.0.1:8080/cookie-proof-XXXXXXXX
anonymous_page_status=200
cookie_name_disclosed=yes
cached_cookie_visible_to_attacker=yes
captured_cookie=grav-site-<hash>=<session-value>
replayed_cookie_api_status=200
result=VULNERABLE
La riga captured_cookie compare solo con --show-cookie.
Esegui lo stesso flusso a due fasi contro Grav 2.0.25 o successivo, poi raccogli con:
python3 exploit.py --collect-only --route /cookie-proof-XXXXXXXX --expect fixed
Comportamento corretto atteso: nessun valore di sessione visibile all'attaccante e nessun replay di sessione riuscito. Un risultato corretto con la correzione applicata termina con 0 usando --expect fixed.
Aggiornare a Grav 2.0.25 o versione successiva. Per le distribuzioni più datate, l'advisory elenca questi controlli temporanei:
security.twig_content.process_enabled: false; oppuresystem.pages.never_cache_twig: true;Non considerare HttpOnly, Secure o SameSite come una correzione: la lettura del cookie vulnerabile avviene lato server durante il rendering di Twig.
Segnalato da canhieu. Questo repository è un supporto alla riproducibilità per l'advisory già pubblico, non uno scanner remoto né uno strumento di raccolta di credenziali.