
Proof-of-concept per CVE-2026-40864: bypass XSRF di JupyterHub tramite POST di form cross-origin che sfrutta Sec-Fetch-Mode: no-cors. Include HTML PoC, analisi della causa principale e cronologia della divulgazione.
Sec-Fetch-Mode: no-cors)Gravità: Media (CVSS 5.4)
CWE: CWE-352 — Cross-Site Request Forgery (XSRF)
Interessato: jupyterhub 4.1.0 ≤ versione < 5.4.5 (corretto in 5.4.5)
Avviso: GHSA-m68r-v472-jgq9
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-40864
Credito: Romain Deperne
La protezione XSRF di JupyterHub (rivista nella 4.1.0) utilizzava l'header di richiesta Sec-Fetch-Mode per decidere
se una richiesta fosse same-origin. Trattava Sec-Fetch-Mode: no-cors come same-origin — ma
no-cors è esattamente ciò che un browser invia per un invio di form "semplice" cross-origin. Di conseguenza,
i POST di form HTML cross-origin verso gli endpoint di form dell'Hub (/hub/spawn, /hub/accept-share)
aggiravano completamente il controllo XSRF.
L'API JSON non è interessata (richiede un content type non semplice, che forza un preflight CORS). Solo gli endpoint di form HTML sono raggiungibili in questo modo.
La logica XSRF passa direttamente a "attendibile" per un insieme di stati Sec-Fetch-* pensati per catturare
le navigazioni same-origin. Sec-Fetch-Mode: no-cors era incluso in quell'insieme attendibile. Ma no-cors
è la modalità che il browser assegna a un semplice <form method=POST> inviato verso un'origine diversa —
precisamente il classico vettore CSRF che il token dovrebbe fermare. Quindi qualsiasi endpoint che modifica lo stato e che
accetta un corpo di form semplice, e si affida unicamente a questo controllo XSRF, è falsificabile cross-origin.
Mappando ciò sugli endpoint reali: /hub/spawn (avvia il server della vittima) e /hub/accept-share
(fa accettare alla vittima una condivisione del server dell'attaccante) sono entrambi POST di form dietro questo controllo.
/hub/spawn — una pagina dell'attaccante può avviare il server single-user della vittima senza consenso
(consumo di risorse / stato inatteso; l'attaccante non ottiene accesso a quel server)./hub/accept-share — quando l'attaccante è un utente JupyterHub autorizzato a condividere il proprio
server, può costringere una vittima ad accettare una condivisione, dando alla vittima accesso al server
dell'attaccante (un passo preparatorio per ulteriori scenari di social engineering / data-drop).Usare Sec-Fetch-Mode come oracolo di origine non è affidabile: no-cors non implica same-origin. La correzione
nella 5.4.5 smette di considerare no-cors come same-origin. Gli operatori che non possono aggiornare immediatamente possono scartare
le richieste che trasportano Sec-Fetch-Mode: no-cors a livello di reverse proxy.
poc/csrf_spawn.html — ospitalo su qualsiasi origine dell'attaccante e fai aprire il file a un utente JupyterHub autenticato.
Il form con invio automatico effettua un POST cross-origin verso /hub/spawn (il browser invia
Sec-Fetch-Mode: no-cors); le build vulnerabili lo accettano senza un token _xsrf valido e avviano il
server della vittima. Imposta action su /hub/accept-share per la variante di accettazione della condivisione.
1. Edit TARGET-JUPYTERHUB in poc/csrf_spawn.html
2. Serve the file from an attacker origin (any static host)
3. Open it in a browser already authenticated to the target Hub
4. Observe the victim's server spawn with no XSRF token supplied
Divulgato in modo responsabile. PoC pubblicato dopo il rilascio della correzione.