
A Documentation of CVE-2025-68116
Auteur : @x0root
Vulnérabilité : Cross-Site Scripting (XSS) stocké via des téléversements interprétables par le navigateur (SVG / HTML)
Logiciel concerné : FileRise (< 2.7.1)
Version corrigée : 2.7.1
CVE officielle (demandée via GHSA) : CVE-2025-68116 (suivi/avis : GHSA-35pp-ggh6-c59c)
Avis connexe antérieur (atténuation originale contournée) : GHSA-qrcv-vjvf-fr29
Évaluation CVSS :
L’évaluation du signaleur évalue les Privilèges Requis (PR) au moment de l’exploitation, et non au moment du dépôt de la vulnérabilité.
L’exploitation se produit lorsqu’une victime accède à un lien de partage public généré, ce qui ne nécessite aucune authentification ni privilège (PR:N).
L’évaluation du CNA évalue PR en fonction de la capacité à téléverser un fichier malveillant. Cependant, CVSS v3.1 définit les Privilèges Requis (PR) comme les privilèges qu’un attaquant doit posséder au moment où la vulnérabilité est exploitée, et non les privilèges nécessaires pour placer ou préparer la condition vulnérable.
Par conséquent, PR:N reflète plus précisément les conditions d’exploitation réelles, ce qui donne une classification de sévérité Critique (9,6).
Note : GHSA-qrcv-vjvf-fr29 a introduit une atténuation qui empêchait le rendu des SVG dans l’interface web de FileRise (volet de prévisualisation). Ce rapport documente un contournement de cette atténuation — spécifiquement les points de terminaison backend de partage/téléchargement — qui est suivi sous GHSA-35pp-ggh6-c59c / CVE-2025-68116.
Ce document constitue un enregistrement technique complet de CVE-2025-68116 : un XSS stocké dans FileRise qui a persisté après une atténuation précédente et a finalement été corrigé dans la v2.7.1. Il comprend la découverte, les preuves de concept d’exploitation, les correctifs infructueux répétés, une analyse précise du flux de contrôle de la cause racine (avec preuves), la vérification finale du correctif, ainsi qu’une analyse des caractéristiques d’exploitabilité pertinentes pour l’évaluation CVSS. Tout le contenu ci-dessous est basé sur des tests reproduits, une inspection du contrôleur et le fil de discussion public de l’avis.
Un avis antérieur, GHSA-qrcv-vjvf-fr29, traitait du XSS stocké via les téléversements SVG en bloquant le rendu en ligne dans l’interface web de FileRise. Cette atténuation n’a pas traité la manière dont les fichiers SVG étaient servis par les points de terminaison backend tels que :
/api/file/download.php/api/file/share.phpCVE-2025-68116 (suivi sous GHSA-35pp-ggh6-c59c) documente un contournement de l’atténuation GHSA-qrcv-vjvf-fr29 : un attaquant peut stocker un SVG conçu et le livrer aux victimes via des liens de partage publics ou certains comportements de téléchargement, entraînant l’exécution de scripts dans l’origine FileRise.
Pour valider si le backend exposait encore les SVG de manière interprétable, j’ai téléversé un SVG PoC simple :
L’accès au fichier via :
/api/file/download.php?…/api/file/share.php?token=…a entraîné l’exécution de alert(). L’atténuation originale de GHSA-qrcv-vjvf-fr29 (blocage de la prévisualisation dans l’interface) a été contournée par un accès direct à ces points de terminaison.
Un alert() est une preuve de concept ; j’ai testé un impact significatif en faisant interagir la charge utile avec les API internes.
Charge utile de test utilisée :
<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>
Lorsqu’un administrateur connecté ouvrait un lien de partage contenant ce SVG, le script s’exécutait et effectuait des requêtes API authentifiées. Les effets observés comprenaient :
{"csrf_expired":true,"csrf_token":"..."})Classification de l’impact démontrée lors des tests :
J’ai signalé le problème de manière privée. Le mainteneur a publié plusieurs correctifs progressifs :
Tout au long des versions v2.6.0 → v2.7.0, le point de terminaison du lien de partage continuait de servir le SVG d’une manière permettant le rendu en ligne et l’exécution de scripts. L’analyse de la cause racine ci-dessous explique pourquoi les correctifs précédents n’ont pas réussi à fermer complètement la vecteur.
La cause sous-jacente n’était pas un seul en-tête manquant mais l’ordre du flux de contrôle et de la sortie dans shareFile() (contrôleur), ce qui empêchait l’application des en-têtes de sécurité dans de nombreux chemins d’exécution. Deux classes de problèmes étaient présentes :
exit; précoces qui court-circuitaient la fonction avant que les en-têtes de sécurité ne soient définis.J’ai utilisé un scan awk pour lister les occurrences de header() et exit; dans shareFile() jusqu’à l’appel readfile() :
Commande : awk '/function shareFile(/ {flag=1} /readfile(/ {flag=0} flag && /(header|exit;)/ {printf "%4d | %s\n", NR, $0}' src/controllers/FileController.php
Sortie observée (abrégée de mon exécution) :
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;
Les en-têtes de sécurité (la logique de durcissement) commencent vers la ligne ~1743 :
1743 | header('X-Content-Type-Options: nosniff'); ... 1770 | header("Content-Disposition: attachment; ...");
Parce que la fonction émet des en-têtes + exit; plus tôt dans de nombreux chemins, ces requêtes n’atteignaient jamais le code de durcissement qui définit Content-Disposition, nosniff ou le type restrictif.
Dans le flux de partage protégé par mot de passe, la fonction émettait le HTML de l’invite de mot de passe tôt :
if (!empty($record['password']) && empty($providedPass)) { header("Content-Type: text/html; charset=utf-8"); ... exit; }
Ce chemin envoie Content-Type: text/html et quitte avant la logique de durcissement des SVG, provoquant un rendu en ligne dans les navigateurs pour les partages protégés par mot de passe où aucun mot de passe n’était fourni.
La vulnérabilité ne se limitait pas aux flux protégés par mot de passe. Une requête de partage sans mot de passe renvoyait également text/html dans mes tests :
Commande : curl -svI "http://127.0.0.1:8080/api/file/share.php?token=437d7913884ace4b94fab8ce745a686a" 2>&1 | grep -iE "content-type"
Observé : < Content-Type: text/html; charset=UTF-8
Cela confirme que même dans le cas général (sans mot de passe), la réponse était text/html et le SVG s’affichait en ligne.
J’ai capturé un fetch brut du point de terminaison de partage qui incluait des avertissements PHP émis avant le durcissement des en-têtes. Extrait (abrégé) :
Commande : curl -s "http://127.0.0.1:8080/api/file/share.php?token=437d7913884ace4b94fab8ce745a686a" | head -n 30
Sortie brute observée (abrégée) :
Deprecated: Constant FILTER_SANITIZE_STRING is deprecated in /data/data/com.termux/files/home/FileRise/src/controllers/FileController.php on line 1644
Ces avertissements montrent que la sortie a été produite (notices obsolètes) avant le durcissement des en-têtes, rendant impossible pour les appels ultérieurs à header() de prendre effet dans ces exécutions.
nosniff existait, mais n’était pas atteint sur de nombreux chemins de code en raison de sorties précoces et de la sortie.Suite aux rapports sur la cause racine, le mainteneur a appliqué des modifications qui traitaient l’ordre du flux de contrôle et de la sortie. Dans la v2.7.1 :
exit; précédents qui contournaient le durcissement ont été corrigés/traités.Vérification finale (mon test sur v2.7.1) :
Commande : curl -svI "http://127.0.0.1:8080/api/file/share.php?token=fc911e48b0a30e9417a9020ef959784d" 2>&1 | grep -iE "content-type|content-disposition"
Observé : < Content-Type: application/octet-stream < Content-Disposition: attachment; filename="xss-image.svg"; filename*=UTF-8''xss-image.svg
Résultat : Le navigateur a forcé un téléchargement ; le SVG ne s’est pas affiché en ligne et la charge utile XSS ne s’est pas exécutée. Je considère que la v2.7.1 a résolu le problème dans mon environnement.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:L — Score : 9,6 (Critique)
J’ai documenté cette justification dans le fil de discussion de l’avis et demandé que PR:N soit utilisé.
Le vecteur CVSS du CNA reflète un modèle de menace restreint.
Les tests empiriques démontrent un chemin d’exploitation plus sévère et reproductible, ce qui correspond à un score de base CVSS 3.1 plus élevé selon les règles de notation standard.
Les évaluateurs indépendants sont encouragés à évaluer la sévérité en utilisant les conditions d’exploitation observées décrites dans ce document, afin de garantir que la sévérité publique reflète l’impact réel plutôt qu’une ligne de base étroitement cadrée.
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;
Les en-têtes de sécurité commencent vers ~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
...suivie de la charge utile SVG imprimée et rendue en ligne.
~/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