
Plausible Analytics master d44e104 – Hoch – Remote – IDOR – PUT traffic-change-notification site_id Mass Assignment
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · plausible-traffic-notify
Plausible Analytics Community Edition, GitHub-Master d44e104ae3943a9240d5cb6b62e67dff0f069d3a (2026-10-16) – Plausible Insights OÜ
Klasse: IDOR (Mass-Assignment). Reichweite: Remote (Owner / Admin / Editor einer Site, die der Angreifer bereits kontrolliert).
Plausible.Site.TrafficChangeNotification.changeset/2 castet weiterhin :site_id. SiteController.update_traffic_change_notification/2 lädt die Zeile über die autorisierte Pfad-Domain und führt dann Repo.update! auf der Client-Map aus. Die Route PUT /sites/:domain/traffic-change-notification/:type liegt unter :browser + CSRF + AuthorizeSiteAccess [:owner, :admin, :editor, :super_admin]. Das HTML-Formular sendet nur threshold. Phoenix akzeptiert dennoch zusätzliche Schlüssel.
whoareme Bug 02 hat Shield / Goal site_id auf dieselbe Weise gepatcht: socket.assigns.site.id binden, :site_id nicht mehr casten. Dieser Controller war nicht Teil dieses Durchgangs. Beim Aktivieren (POST .../enable) wird site_id: site.id aus den Assigns gesetzt. Der Angreifer aktiviert dies auf seiner eigenen Site und sendet dann PUT mit traffic_change_notification[site_id]=<victim_pk>. Eindeutiger Index (site_id, type). Eine Standardinstallation hat keine Spike/Drop-Zeile, daher gelingt das Update und die Zeile gehört nun dem Opfer. Der Tabellenname ist historisch spike_notifications.
Ein Editor seiner eigenen Site hängt die Spike/Drop-Benachrichtigungszeile auf eine Opfer-Site um. Die Empfänger bleiben das Angreifer-Postfach. Oban mailt dem Angreifer über den Traffic des Opfers.
| ID | noch keine CVE |
| CWE | CWE-915 / CWE-639 |
| CVSS | Hoch: 7.1 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L |
| Produkt | Plausible Analytics |
| Betroffen | GitHub-Master d44e104ae3943a9240d5cb6b62e67dff0f069d3a (2026-10-16). Der letzte CE-Tag v3.2.1 (e4f5a87a5570bd61605217e7ceb376636db8eddb, 2026-05-15) liegt fünf Monate zurück. CE + EE (lib/, nicht extra/lib). |
| Auth | Owner / Admin / Editor einer Site, die sie kontrollieren. Viewer nicht. CSRF erforderlich (IDOR innerhalb derselben Session). |
| Lizenz | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1:18183 (http) 18184 (postgres) 18185 (clickhouse http) nur |
Übriggebliebenes #2 aus Plausible Analytics leftovers. Gleicher Pin, anderes Pack: plausible-editor-invite (People-HTML leakt Transfer-Nanoid an Editoren). Dieses Pack ist das Shield/Goal-Geschwister, das weiterhin :site_id castet.
Owner, Admin oder Editor einer beliebigen Site, die sie bereits kontrollieren. Benötigt ein Session-Cookie und das CSRF-Token aus dieser Session. Sie benötigen keine Mitgliedschaft auf dem Opfer. Sie senden kein PUT auf die Pfad-Domain des Opfers (das ergibt 404). Sie senden ein PUT auf ihre eigene Domain mit einer fremden site_id-Integer-PK.
Privilegientabelle (vollständig). Gleiche Messlatte wie Signal-Server Dropwizard admin: Methode + Pfad, was dieser Aufruf gewährt, Recon vs. Integrität vs. Verfügbarkeit, im Lab nachgewiesen vs. abgeleitet, angrenzende Oberfläche, die geschlossen bleibt.
| Achse | Methode + Pfad | Gewährt | Provenienz |
|---|---|---|---|
| Integrität | POST /sites/:attacker_domain/traffic-change-notification/:type/enable, dann PUT /sites/:attacker_domain/traffic-change-notification/:type mit traffic_change_notification[site_id]=<victim_pk> | Hängt die spike_notifications-Zeile des Angreifers auf victim.sites.id um. Empfänger bleiben die Angreiferadresse. Besitzt den eindeutigen (site_id, type)-Slot. Schwellenwert wird vom Angreifer gewählt (im Lab wurde 1 verwendet). | Im Lab nachgewiesen. Zeile 1 verschob site_id 2 → 1, Empfänger {[email protected]} |
| Recon | Oban-TrafficChangeNotifier-E-Mail an notification.recipients | Betreff Traffic spike on <victim.domain>. Inhalt: aktuelle Besucherzahl, Top-3-Quellen, Top-3-Seiten. Erster Versand erfolgt sofort (last_sent nil). Wiederholung höchstens alle 12 Stunden. Schwellenwert 1 erzeugt eine Mail, sobald das Opfer einen aktuellen Besucher hat. Dashboard-HTML-/Installationslinks in der Mail werden weggelassen, es sei denn, der Empfänger ist Teammitglied oder Gast (der Angreifer ist es nicht). | Abgeleitet aus dem Notifier-Quellcode. Mail im Lab nicht erfasst (MAILER_ADAPTER=Bamboo.LocalAdapter) |
| Verfügbarkeit | Opfer POST .../enable desselben Typs | Eindeutiges (site_id, type) lehnt ab. Das Opfer kann die Funktion nicht aktivieren, bis die eingeschleuste Zeile entfernt ist. | Quellcode (unique_constraint) |
| Verfügbarkeit | Opfer disable (delete_all nach site_id) | Das Opfer kann die eingeschleuste Zeile löschen, wenn es den zusätzlichen Empfänger bemerkt. | Quellcode (disable_traffic_change_notification) |
Bleibt bei diesem Pin geschlossen
| Achse | Methode + Pfad | Warum geschlossen |
|---|---|---|
| Integrität | PUT /sites/:victim_domain/traffic-change-notification/:type | Pfad-Domain unterliegt AuthorizeSiteAccess. Der Angreifer ist kein Mitglied. Im Lab nachgewiesen: 404. attacker-not-member-of-victim=1 |
| Integrität | Schreiben auf Shield / Goal / Funnel / Segment / Shared-Link | whoareme Bug 02 hat diese cast :site_id-Handler gepatcht. Sie binden Assigns. |
| Recon | ClickHouse-Dump, Stats-API, Dashboard-HTML | Keine Mitgliedschaft, kein Shared Link. Die Notifier-Mail lässt Dashboard-Links für Nichtmitglieder weg. |
| Integrität | Tracker-Ingest | Bereits unauth POST /api/event mit einer vom Client gewählten Domain. Nicht dieser Überrest. |
| Integrität | Repo.update!, wenn das Opfer diesen Typ bereits hat | 500 bei eindeutigem (site_id, type). Standardinstallation hat keine Zeile, daher funktioniert das erste Einschleusen. |
| Integrität | Viewer der Angreifer-Site | AuthorizeSiteAccess ist Owner/Admin/Editor. Viewer kann nicht. |
| Integrität | OS-Shell, Super-Admin-LiveView | Geschlossen. Keine Shell. |
Ich behaupte nicht, einen ClickHouse-Dump, eine Reverse Shell oder einen Dashboard-Login als das Opfer zu haben. Der Dump ist Traffic-Recon in einer Mail, die der Angreifer ohnehin erhält: Domain, Live-Besucher, Top-Quellen, Top-Seiten. site_id ist der ganzzahlige PK (aufzählbar; whoareme verwendete site_id=1). Das Dashboard-data-* gibt ihn nicht aus.