
A Documentation of CVE-2025-68116
Autor: @x0root
Sicherheitslücke: Persistentes Cross-Site-Scripting (XSS) durch browser-renderbare Uploads (SVG / HTML)
Betroffene Software: FileRise (< 2.7.1)
Gepatchte Version: 2.7.1
Offizielle CVE (beantragt via GHSA): CVE-2025-68116 (Tracking/Advisory: GHSA-35pp-ggh6-c59c)
Vorhergehendes zugehöriges Advisory (ursprüngliche Absicherung, die umgangen wurde): GHSA-qrcv-vjvf-fr29
CVSS-Bewertung:
Die Bewertung des Reporters bewertet die erforderlichen Berechtigungen (Privileges Required, PR) zum Zeitpunkt der Ausnutzung, nicht zum Zeitpunkt des Einbringens der Sicherheitslücke.
Die Ausnutzung erfolgt, wenn ein Opfer auf einen generierten öffentlichen Freigabelink zugreift, was keine Authentifizierung oder Berechtigungen erfordert (PR:N).
Die CNA-Bewertung bewertet PR basierend auf der Fähigkeit, eine schädliche Datei hochzuladen. Jedoch definiert CVSS v3.1 die erforderlichen Berechtigungen (PR) als die Berechtigungen, die ein Angreifer zum Zeitpunkt der Ausnutzung der Sicherheitslücke besitzen muss, nicht die Berechtigungen, die erforderlich sind, um die verwundbare Bedingung zu platzieren oder vorzubereiten.
Daher spiegelt PR:N die realen Ausnutzungsbedingungen genauer wider, was zu einer Klassifizierung als Kritisch (9,6) führt.
Hinweis: GHSA-qrcv-vjvf-fr29 führte eine Absicherung ein, die verhinderte, dass SVGs innerhalb der FileRise-Weboberfläche (Vorschaufenster) gerendert wurden. Dieser Bericht dokumentiert eine Umgehung dieser Absicherung – insbesondere die Backend-Freigabe/Download-Endpunkte – die als GHSA-35pp-ggh6-c59c / CVE-2025-68116 verfolgt wird.
Dieses Dokument ist eine vollständige technische Aufzeichnung von CVE-2025-68116: einem persistenten XSS in FileRise, das nach einer früheren Absicherung bestehen blieb und schließlich in v2.7.1 behoben wurde. Es umfasst die Entdeckung, Proof-of-Concepts zur Ausnutzung, wiederholte fehlgeschlagene Korrekturen, eine präzise Root-Cause-Kontrollflussanalyse (mit Belegen), die endgültige Verifizierung des Patches und eine Analyse der für die CVSS-Bewertung relevanten Ausnutzbarkeitsmerkmale. Alle nachfolgenden Inhalte basieren auf reproduzierten Tests, Controller-Inspektion und dem öffentlichen Advisory-Thread.
Ein vorhergehendes Advisory, GHSA-qrcv-vjvf-fr29, adressierte persistentes XSS über SVG-Uploads, indem es die Inline-Rendering in der FileRise-Weboberfläche blockierte. Diese Absicherung adressierte nicht, wie SVG-Dateien von Backend-Endpunkten wie den folgenden ausgeliefert wurden:
/api/file/download.php/api/file/share.phpCVE-2025-68116 (verfolgt als GHSA-35pp-ggh6-c59c) dokumentiert eine Umgehung der GHSA-qrcv-vjvf-fr29-Absicherung: Ein Angreifer kann ein präpariertes SVG speichern und es Opfern über öffentliche Freigabelinks oder bestimmte Download-Verhalten zustellen, was zur Skriptausführung im FileRise-Ursprung führt.
Um zu validieren, ob das Backend SVGs immer noch in einer renderbaren Weise auslieferte, lud ich ein einfaches PoC-SVG hoch:
Der Zugriff auf die Datei über:
/api/file/download.php?…/api/file/share.php?token=…führte zur Ausführung von alert(). Die ursprüngliche GHSA-qrcv-vjvf-fr29-Absicherung (UI-Vorschau-Block) wurde durch direkten Zugriff auf diese Endpunkte umgangen.
Ein alert() ist ein PoC; ich testete auf sinnvolle Auswirkungen, indem die Nutzlast mit internen APIs interagierte.
Verwendete Testnutzlast:
<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>
Wenn ein eingeloggter Administrator einen Freigabelink öffnete, der dieses SVG enthielt, wurde das Skript ausgeführt und stellte authentifizierte API-Anfragen. Beobachtete Effekte umfassten:
{"csrf_expired":true,"csrf_token":"..."})Während der Tests demonstrierte Impact-Klassifizierung:
Ich meldete das Problem privat. Der Maintainer veröffentlichte mehrere inkrementelle Korrekturen:
Während v2.6.0 → v2.7.0 lieferte der Freigabelink-Endpunkt das SVG weiterhin so aus, dass Inline-Rendering und Skriptausführung möglich waren. Die folgende Root-Cause-Analyse erklärt, warum die früheren Korrekturen den Vektor nicht vollständig schließen konnten.
Die zugrunde liegende Ursache war nicht ein einzelner fehlender Header, sondern Kontrollfluss und Ausgabereihenfolge innerhalb von shareFile() (Controller), die verhinderten, dass die Sicherheitsheader in vielen Ausführungspfaden angewendet wurden. Zwei Problemklassen waren vorhanden:
exit;-Punkte, die die Funktion kurzschlossen, bevor Sicherheitsheader gesetzt wurden.Ich verwendete einen awk-Scan, um header()- und exit;-Vorkommen innerhalb von shareFile() bis zum readfile()-Aufruf aufzulisten:
Befehl: awk '/function shareFile(/ {flag=1} /readfile(/ {flag=0} flag && /(header|exit;)/ {printf "%4d | %s\n", NR, $0}' src/controllers/FileController.php
Beobachtete Ausgabe (gekürzt aus meinem Lauf):
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;
Sicherheitsheader (die Härtungslogik) beginnen bei Zeile ~1743:
1743 | header('X-Content-Type-Options: nosniff'); ... 1770 | header("Content-Disposition: attachment; ...");
Da die Funktion in vielen Pfaden früher Header + exit; ausgibt, erreichten diese Anfragen nie den Härtungscode, der Content-Disposition, nosniff oder den restriktiven Typ setzt.
Im passwortgeschützten Freigabeablauf gab die Funktion das Passwortabfrage-HTML früh aus:
if (!empty($record['password']) && empty($providedPass)) { header("Content-Type: text/html; charset=utf-8"); ... exit; }
Dieser Pfad sendet Content-Type: text/html und beendet vor der SVG-Härtungslogik, was bei passwortgeschützten Freigaben, bei denen kein Passwort angegeben wurde, zu Inline-Rendering im Browser führt.
Die Sicherheitslücke war nicht auf passwortgeschützte Abläufe beschränkt. Eine nicht-passwortgeschützte Freigabeanfrage gab in meinen Tests ebenfalls text/html zurück:
Befehl: curl -svI "http://127.0.0.1:8080/api/file/share.php?token=437d7913884ace4b94fab8ce745a686a" 2>&1 | grep -iE "content-type"
Beobachtet: < Content-Type: text/html; charset=UTF-8
Dies bestätigt, dass selbst im allgemeinen Fall (kein Passwort) die Antwort text/html war und das SVG inline gerendert wurde.
Ich erfasste einen rohen Fetch des Freigabe-Endpunkts, der PHP-Warnungen enthielt, die vor der Header-Härtung ausgegeben wurden. Schnappschuss (gekürzt):
Befehl: curl -s "http://127.0.0.1:8080/api/file/share.php?token=437d7913884ace4b94fab8ce745a686a" | head -n 30
Beobachtete Rohausgabe (gekürzt):
Deprecated: Constant FILTER_SANITIZE_STRING is deprecated in /data/data/com.termux/files/home/FileRise/src/controllers/FileController.php on line 1644
Diese Warnungen zeigen, dass vor der Header-Härtung Ausgabe erzeugt wurde (veraltete Hinweise), was es unmöglich machte, dass nachfolgende header()-Aufrufe in diesen Läufen wirksam wurden.
nosniff setzt, existierte, wurde jedoch aufgrund früher Ausstiege und Ausgabe in vielen Codepfaden nicht erreicht.Nach den Root-Cause-Berichten wandte der Maintainer Änderungen an, die den Kontrollfluss und die Ausgabereihenfolge adressierten. In v2.7.1:
exit;-Punkte, die die Härtung umgingen, wurden korrigiert/behandelt.Endgültige Verifizierung (mein Test auf v2.7.1):
Befehl: curl -svI "http://127.0.0.1:8080/api/file/share.php?token=fc911e48b0a30e9417a9020ef959784d" 2>&1 | grep -iE "content-type|content-disposition"
Beobachtet: < Content-Type: application/octet-stream < Content-Disposition: attachment; filename="xss-image.svg"; filename*=UTF-8''xss-image.svg
Ergebnis: Browser erzwang einen Download; das SVG wurde nicht inline gerendert und die XSS-Nutzlast wurde nicht ausgeführt. Ich betrachte v2.7.1 als das Problem in meiner Umgebung behoben.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:L — Score: 9,6 (Kritisch)
Ich dokumentierte diese Begründung im Advisory-Thread und beantragte die Verwendung von PR:N.
Der CVSS-Vektor des CNA spiegelt ein eingeschränktes Bedrohungsmodell wider.
Empirische Tests zeigen einen schwerwiegenderen und reproduzierbaren Ausnutzungspfad, der mit einem höheren CVSS 3.1-Basiswert unter den standardmäßigen Bewertungsregeln übereinstimmt.
Unabhängige Bewerter werden ermutigt, den Schweregrad unter Verwendung der in diesem Dokument beschriebenen beobachteten Ausnutzungsbedingungen zu bewerten, um sicherzustellen, dass der öffentliche Schweregrad die reale Auswirkung widerspiegelt, anstatt einer eng gefassten Basislinie.
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;
Sicherheitsheader beginnen ab ~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
...gefolgt von der SVG-Nutzlast, die ausgegeben und inline gerendert wurde.
~/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