Chartbrew: Mandantenübergreifende Projektübernahme durch Insecure Direct Object Reference in der Berechtigungsprüfung
Ein authentifizierter Nutzer übergibt seine eigene Team-ID im Request-Body und umgeht damit die Berechtigungsprüfung für alle Projekt-Routen. Die Autorisierung prüft nie, ob das Zielprojekt zum angegebenen Team gehört, sodass jeder registrierte Nutzer fremde Projekte lesen, überschreiben, stehlen oder löschen kann.
Advisory-ID: TP-2026-055
Produkt: Chartbrew (Open-Source-Business-Intelligence-Plattform für Datenvisualisierung, self-hosted)
Schwachstellentyp: Insecure Direct Object Reference / Authorization Bypass Through User-Controlled Key (CWE-639)
CVE: CVE-2026-85712
CVSS 3.1: 8.8 (Hoch) · CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Hersteller-Advisory: GHSA-6gv3-8wxh-6vfq
Betroffene Versionen: <= 5.2.2
Behoben in: noch nicht veröffentlicht (Fix auf master)
Gemeldet: 16. Juli 2026
Zusammenfassung
Chartbrew ist eine self-hosted Open-Source-Business-Intelligence-Plattform für Datenvisualisierung, deren /project/*-Routen eine gemeinsame checkPermissions-Closure zur Autorisierung verwenden. Diese Closure löst das Team des Aufrufers über req.params.team_id || req.body?.team_id auf (server/api/ProjectRoute.js:45). Da keine /project/*-Route einen :team_id-URL-Parameter definiert, stammt der Wert aus dem JSON-Body des Requests. Ein Angreifer setzt team_id auf sein eigenes Team (jede Selbstregistrierung erhält die Rolle teamOwner), der teamOwner/teamAdmin-Zweig gewährt die Aktion, und der Code prüft nie, ob das Zielprojekt tatsächlich zu diesem Team gehört. ProjectController.update schreibt den gesamten Request-Body über db.Project.update(data, {where: {id}}) ohne Feld-Allowlist. Ergebnis: Jeder authentifizierte Nutzer liest, überschreibt, reparentiert (stiehlt), veröffentlicht, entfernt das Passwort von oder löscht jedes Projekt eines beliebigen anderen Mandanten.
Ursache
checkPermissions setzt const teamId = req.params.team_id || req.body?.team_id (server/api/ProjectRoute.js:45), und keine /project/*-Route definiert einen :team_id-Parameter, sodass der Aufrufer teamId über den JSON-Body kontrolliert. getTeamRole(teamId, req.user.id) liefert die teamOwner-Rolle des Angreifers in dessen eigenem Team zurück, und der teamOwner/teamAdmin-Zweig ruft next() auf, ohne project.team_id === teamId zu prüfen. Die Senke ProjectController.update(id, data) schreibt den gesamten Body über db.Project.update(data, {where: {id}}) ohne Feld-Allowlist (server/controllers/ProjectController.js:100-114). Vom Angreifer kontrollierte Spalten umfassen team_id (Umhängen entspricht Diebstahl), public, ghost, passwordProtected, password und headerCode. DELETE /project/:id verwendet dieselbe Closure mit der deleteAny-Aktion.
Proof of Concept
# Bestaetigung, dass Angreifer A ohne den Body-Trick nicht auf B's Projekt zugreifen kann:
GET /project/<Bproj> (Bearer A) → 403
PUT /project/<Bproj> {"dashboardTitle":"x"} (Bearer A) → 403
# PUT mit A's eigener team_id im Body senden:
PUT /project/<Bproj> HTTP/1.1
Authorization: Bearer <A>
{"team_id":<Ateam>,"public":true,"passwordProtected":false,"dashboardTitle":"XVERIFY"}
HTTP/1.1 200 OK
{"id":<Bproj>,"team_id":<Ateam>,"public":true, ...}
# Diebstahl bestaetigen:
GET /project/<Bproj> (Bearer A) → 200 (A besitzt es jetzt)
GET /team (Bearer B) → Projekt nicht mehr aufgelistet
Auswirkung
- Mandantenübergreifendes Lesen jedes Projekts (Charts, Datensätze, Abfragen) durch Umhängen und anschließendes Lesen.
- Beliebiges Überschreiben aller Projekteinstellungen, einschließlich Veröffentlichung und Passwort-Entfernung.
- Projektdiebstahl und über die gemeinsame DELETE-Route mandantenübergreifende Löschung.
- Niedrigste legitime Rolle in der Standardkonfiguration, ein einziger Request, keine Nutzerinteraktion.
Referenzen
Steckt so etwas in Ihrer Software?
Diese Schwachstelle hat unser Team im Rahmen seiner Arbeit gefunden. Lassen Sie Ihre Anwendungen von denselben Spezialisten prüfen, mit einem Penetrationstest von turingpoint.
