
Una documentazione di CVE-2025-68116
Autore: @x0root
Vulnerabilità: Cross-Site Scripting persistente (XSS) tramite caricamenti renderizzabili dal browser (SVG / HTML)
Software interessato: FileRise (< 2.7.1)
Versione con patch: 2.7.1
CVE ufficiale (richiesto tramite GHSA): CVE-2025-68116 (tracciamento/advisory: GHSA-35pp-ggh6-c59c)
Advisory precedente correlato (mitigazione originale che è stata aggirata): GHSA-qrcv-vjvf-fr29
Valutazione CVSS:
La valutazione del reporter considera i Privilegi Richiesti (PR) nel momento dello sfruttamento, non nel momento in cui la vulnerabilità viene introdotta.
Lo sfruttamento avviene quando una vittima accede a un link di condivisione pubblico generato, il che non richiede autenticazione né privilegi (PR:N).
La valutazione del CNA valuta i PR in base alla capacità di caricare un file dannoso. Tuttavia, CVSS v3.1 definisce i Privilegi Richiesti (PR) come i privilegi che un attaccante deve possedere nel momento in cui la vulnerabilità viene sfruttata, non i privilegi necessari per introdurre o predisporre la condizione vulnerabile.
Di conseguenza, PR:N riflette in modo più accurato le condizioni di sfruttamento nel mondo reale, portando a una classificazione di gravità Critica (9.6).
Nota: GHSA-qrcv-vjvf-fr29 ha introdotto una mitigazione che impediva il rendering degli SVG all'interno dell'interfaccia web di FileRise (riquadro di anteprima). Questo report documenta un bypass di tale mitigazione — in particolare gli endpoint backend di condivisione/download — tracciato come GHSA-35pp-ggh6-c59c / CVE-2025-68116.
Questo documento è una registrazione tecnica completa di CVE-2025-68116: una XSS persistente in FileRise che è persistita dopo una mitigazione precedente ed è stata infine corretta nella v2.7.1. Include la scoperta, le prove di concetto di sfruttamento, i ripetuti tentativi di correzione falliti, una precisa analisi del flusso di controllo della causa principale (con evidenze), la verifica finale della patch e un'analisi delle caratteristiche di sfruttabilità rilevanti per la valutazione CVSS. Tutto il contenuto seguente si basa su test riprodotti, ispezione del controller e sul thread pubblico dell'advisory.
Un advisory precedente, GHSA-qrcv-vjvf-fr29, affrontava la XSS persistente tramite caricamenti di SVG bloccando il rendering inline nell'interfaccia web di FileRise. Quella mitigazione non affrontava il modo in cui i file SVG venivano serviti dagli endpoint backend come:
/api/file/download.php/api/file/share.phpCVE-2025-68116 (tracciato come GHSA-35pp-ggh6-c59c) documenta un bypass della mitigazione GHSA-qrcv-vjvf-fr29: un attaccante può memorizzare un SVG appositamente creato e consegnarlo alle vittime tramite link di condivisione pubblici o determinati comportamenti di download, portando all'esecuzione di script nell'origine di FileRise.
Per verificare se il backend esponesse ancora gli SVG in modo renderizzabile, ho caricato un semplice SVG di prova (PoC):
Accedendo al file tramite:
/api/file/download.php?…/api/file/share.php?token=…si è verificata l'esecuzione di alert(). La mitigazione originale di GHSA-qrcv-vjvf-fr29 (blocco dell'anteprima nell'interfaccia) è stata aggirata tramite accesso diretto a questi endpoint.
Un alert() è una prova di concetto; ho testato un impatto concreto facendo interagire il payload con le API interne.
Payload di test utilizzato:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<script type="text/javascript">
fetch('/api/upload/upload.php')
.then(response => response.text())
.then(data => alert('API Response: ' + data));
</script>
</svg>
Quando un amministratore connesso apriva un link di condivisione contenente questo SVG, lo script veniva eseguito e effettuava richieste API autenticate. Gli effetti osservati includevano:
{"csrf_expired":true,"csrf_token":"..."})Classificazione dell'impatto dimostrata durante i test:
Ho segnalato il problema privatamente. Il maintainer ha rilasciato diverse correzioni incrementali:
Durante il periodo v2.6.0 → v2.7.0, l'endpoint dei link di condivisione ha continuato a servire l'SVG in un modo che consentiva il rendering inline e l'esecuzione di script. L'analisi della causa principale di seguito spiega perché le correzioni precedenti non sono riuscite a chiudere completamente il vettore.
La causa sottostante non era un singolo header mancante, ma il flusso di controllo e l'ordine di emissione dell'output all'interno di shareFile() (controller), che impedivano l'applicazione degli header di sicurezza in molti percorsi di esecuzione. Erano presenti due classi di problemi:
exit; anticipati che interrompevano prematuramente la funzione prima che gli header di sicurezza venissero impostati.Ho usato una scansione awk per elencare le occorrenze di header() e exit; all'interno di shareFile() fino alla chiamata readfile():
Comando: awk '/function shareFile(/ {flag=1} /readfile(/ {flag=0} flag && /(header|exit;)/ {printf "%4d | %s\n", NR, $0}' src/controllers/FileController.php
Output osservato (ridotto dalla mia esecuzione):
1649 | header('Content-Type: application/json; charset=utf-8'); 1651 | exit; 1657 | header('Content-Type: application/json; charset=utf-8'); 1659 | exit; 1664 | header('Content-Type: application/json; charset=utf-8'); 1666 | exit; 1670 | header("Content-Type: text/html; charset=utf-8"); 1693 | exit; 1699 | header('Content-Type: application/json; charset=utf-8'); 1701 | exit; 1719 | header('Content-Type: application/json; charset=utf-8'); 1721 | exit; 1725 | header('Content-Type: application/json; charset=utf-8'); 1727 | exit;
Gli header di sicurezza (la logica di indurimento) iniziano alla riga ~1743:
1743 | header('X-Content-Type-Options: nosniff'); ... 1770 | header("Content-Disposition: attachment; ...");
Poiché la funzione emette header + exit; prima in molti percorsi, quelle richieste non raggiungevano mai il codice di indurimento che imposta Content-Disposition, nosniff o il tipo restrittivo.
Nel flusso di condivisione protetto da password, la funzione emetteva precocemente l'HTML del prompt della password:
if (!empty($record['password']) && empty($providedPass)) { header("Content-Type: text/html; charset=utf-8"); ... exit; }
Questo percorso invia Content-Type: text/html ed esce prima della logica di indurimento per gli SVG, causando il rendering inline nei browser per le condivisioni protette da password quando la password non veniva fornita.
La vulnerabilità non era limitata ai flussi protetti da password. Anche una richiesta di condivisione senza password restituiva text/html nei miei test:
Comando: curl -svI "http://127.0.0.1:8080/api/file/share.php?token=437d7913884ace4b94fab8ce745a686a" 2>&1 | grep -iE "content-type"
Osservato: < Content-Type: text/html; charset=UTF-8
Ciò conferma che anche nel caso generale (senza password) la risposta era text/html e l'SVG veniva renderizzato inline.
Ho catturato una fetch grezza dell'endpoint di condivisione che includeva warning PHP emessi prima dell'indurimento degli header. Istantanea (ridotta):
Comando: curl -s "http://127.0.0.1:8080/api/file/share.php?token=437d7913884ace4b94fab8ce745a686a" | head -n 30
Output grezzo osservato (ridotto):
Deprecated: Constant FILTER_SANITIZE_STRING is deprecated in /data/data/com.termux/files/home/FileRise/src/controllers/FileController.php on line 1644
Questi warning mostrano che l'output (notice di deprecazione) veniva prodotto prima dell'indurimento degli header, rendendo impossibile per le successive chiamate a header() avere effetto in quelle esecuzioni.
nosniff, ma non veniva raggiunto su molti percorsi di codice a causa delle uscite anticipate e dell'output.In seguito ai report sulla causa principale, il maintainer ha applicato modifiche che affrontavano il flusso di controllo e l'ordine di emissione dell'output. Nella v2.7.1:
exit; che aggiravano l'indurimento sono stati corretti/gestiti.Verifica finale (il mio test sulla v2.7.1):
Comando: curl -svI "http://127.0.0.1:8080/api/file/share.php?token=fc911e48b0a30e9417a9020ef959784d" 2>&1 | grep -iE "content-type|content-disposition"
Osservato: < Content-Type: application/octet-stream < Content-Disposition: attachment; filename="xss-image.svg"; filename*=UTF-8''xss-image.svg
Risultato: il browser è stato forzato al download; l'SVG non è stato renderizzato inline e il payload XSS non è stato eseguito. Considero la v2.7.1 risolutiva del problema nel mio ambiente.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:L — Punteggio: 9.6 (Critica)
Ho documentato questa motivazione nel thread dell'advisory e ho richiesto che venisse usato PR:N.
Il maintainer ha valutato i Privilegi Richiesti come Bassi (PR:L) nell'advisory ufficiale, sostenendo che caricare/introdurre il file dannoso richiede un account o un token abilitato al caricamento (una capacità preesistente).
Ha considerato quel requisito come parte dei privilegi pre-sfruttamento e quindi ha usato PR:L; il testo dell'advisory nota comunque che gli URL di condivisione risultanti possono essere aperti da destinatari non autenticati.
Il maintainer ha proceduto a richiedere il CVE tramite GitHub e ha pubblicato l'advisory GHSA con PR:L (Alta 8.9).
Il CVE è stato richiesto tramite GitHub come parte del processo GHSA e pubblicato con PR:L. Questo documento conserva l'analisi tecnica del reporter sulle caratteristiche di sfruttabilità per completezza e riferimento futuro
Il vettore CVSS del CNA riflette un modello di minaccia ristretto.
I test empirici dimostrano un percorso di sfruttamento più grave e riproducibile, che si allinea con un punteggio base CVSS 3.1 più alto secondo le regole di punteggio standard.
I valutatori indipendenti sono incoraggiati a valutare la gravità utilizzando le condizioni di sfruttamento osservate descritte in questo documento, assicurando che la gravità pubblica rifletta l'impatto reale piuttosto che una baseline dall'ambito ristretto.
1649 | header('Content-Type: application/json; charset=utf-8'); 1651 | exit; 1657 | header('Content-Type: application/json; charset=utf-8'); 1659 | exit; 1664 | header('Content-Type: application/json; charset=utf-8'); 1666 | exit; 1670 | header("Content-Type: text/html; charset=utf-8"); 1693 | exit; 1699 | header('Content-Type: application/json; charset=utf-8'); 1701 | exit; 1719 | header('Content-Type: application/json; charset=utf-8'); 1721 | exit; 1725 | header('Content-Type: application/json; charset=utf-8'); 1727 | exit;
Gli header di sicurezza iniziano a ~1743: 1743 | header('X-Content-Type-Options: nosniff'); ... 1770 | header("Content-Disposition: attachment; ...");
~/FileRise $ curl -svI "http://127.0.0.1:8080/api/file/share.php?token=437d7913884ace4b94fab8ce745a686a" 2>&1 | grep -iE "content-type" < Content-Type: text/html; charset=UTF-8
Deprecated: Constant FILTER_SANITIZE_STRING is deprecated in /data/data/com.termux/files/home/FileRise/src/controllers/FileController.php on line 1644
...followed by the SVG payload being printed and rendered inline.
~/FileRise $ curl -svI "http://127.0.0.1:8080/api/file/share.php?token=fc911e48b0a30e9417a9020ef959784d" 2>&1 | grep -iE "content-type|content-disposition" < Content-Type: application/octet-stream < Content-Disposition: attachment; filename="xss-image.svg"; filename*=UTF-8''xss-image.svg