
Bypass XSRF di JupyterHub tramite form POST cross-origin (Sec-Fetch-Mode: no-cors) — CWE-352
Sec-Fetch-Mode: no-cors)Severità: Moderate
CWE: CWE-352 — Cross-Site Request Forgery (XSRF)
Versioni interessate: jupyterhub 4.1.0 ≤ versione < 5.4.5 (corretta in 5.4.5)
Advisory: GHSA-m68r-v472-jgq9
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-40864
Crediti: Romain Deperne
La protezione XSRF di JupyterHub (ridisegnata in 4.1.0) usava l'header di richiesta Sec-Fetch-Mode per stabilire
se una richiesta fosse same-origin. Trattava Sec-Fetch-Mode: no-cors come same-origin — ma
no-cors è esattamente ciò che il browser invia per una sottomissione di modulo "semplice" cross-origin. Di
conseguenza, i POST di moduli HTML cross-origin verso gli endpoint a modulo dell'Hub (/hub/spawn, /hub/accept-share)
bypassavano del tutto il controllo XSRF.
L'API JSON non è interessata (richiede un content type non semplice, che forza un preflight CORS). Solo gli endpoint a modulo HTML sono raggiungibili in questo modo.
La logica XSRF si riduce a "attendibile" per un insieme di stati Sec-Fetch-* pensati per catturare
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> che invia verso un'origine diversa —
precisamente il vettore CSRF classico che il token dovrebbe bloccare. Quindi qualsiasi endpoint che modifica lo stato,
accetti un corpo di modulo semplice e si affidi esclusivamente a questo gate XSRF è falsificabile cross-origin.
Mappando questo agli endpoint reali: /hub/spawn (avvia il server dell'utente vittima) e /hub/accept-share
(fa accettare alla vittima una condivisione del server dell'attaccante) sono entrambi POST di moduli dietro questo gate.
/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 iniziale per ulteriori scenari di social engineering / data-drop).Usare Sec-Fetch-Mode come oracolo di origine non è valido: no-cors non implica same-origin. La correzione
in 5.4.5 smette di considerare no-cors come same-origin. Gli operatori che non possono aggiornare subito possono
scartare le richieste con Sec-Fetch-Mode: no-cors al reverse proxy.
poc/csrf_spawn.html — ospitalo su una qualsiasi origine dell'attaccante e fai aprire la pagina a un utente JupyterHub autenticato.
Il modulo ad auto-invio effettua un POST cross-origin verso /hub/spawn (il browser invia
Sec-Fetch-Mode: no-cors); le build vulnerabili lo accettano senza alcun token _xsrf valido e avviano il
server della vittima. Punta action a /hub/accept-share per la variante di accettazione della condivisione.
1. Modificare TARGET-JUPYTERHUB in poc/csrf_spawn.html
2. Servire il file da un'origine dell'attaccante (qualsiasi host statico)
3. Aprirlo in un browser già autenticato verso l'Hub di destinazione
4. Osservare l'avvio del server della vittima senza alcun token XSRF fornito
Divulgato in modo responsabile. PoC pubblicato dopo il rilascio della correzione.