OpenReplay: Integrationen lassen den Server jede Adresse abrufen
Die Slack-, Teams- und Webhook-Endpunkte nehmen jede Adresse an, pruefen nichts und geben die Antwort des internen Dienstes zurueck.
Advisory-ID: TP-2026-089
Produkt: OpenReplay (Session-Replay und Produktanalyse, Python und FastAPI)
Schwachstellentyp: Server-Side Request Forgery (CWE-918)
CVE: beantragt (GitHub CNA), Vergabe ausstehend
CVSS 3.1: 8.5 (Hoch), eigene Einschaetzung; das Hersteller-Advisory fuehrt keine Bewertung
Betroffene Versionen: <= 1.27.0
Hersteller-Advisory: GHSA-7h4c-j67v-5qm2
Behoben in: nicht eindeutig, das Hersteller-Advisory fuehrt 1.27.0 zugleich als betroffen und als behoben
Gemeldet: 29. Mai 2026
Zusammenfassung
OpenReplay laesst Nutzer Integrationen zu Slack, Microsoft Teams und beliebigen Webhooks anlegen und hinterlegt dafuer eine Adresse, die der Server anschliessend aufruft. Keine Stelle im Code lehnt interne, lokale oder Link-Local-Adressen ab, und der HTTP-Klient folgt Weiterleitungen, weshalb auch eine Namensliste nichts auffangen wuerde. Die Slack-Integration feuert ihre Anfrage schon beim Anlegen, und der Statuscode verraet, ob ein Port offen, geschlossen oder gefiltert ist. Der Weg, der eine Notiz an Slack schickt, gibt den Rumpf der Antwort woertlich an den Aufrufer zurueck und macht aus der blinden eine lesende Anfrage. Alle drei Routen verlangen nur eine angemeldete Sitzung in der niedrigsten Rolle, keine Administratorrechte.
Ursache
AddCollaborationSchema.url nimmt jeden Namen an (api/schemas/schemas.py:126), ebenso WebhookSchema.processed_endpoint (:190). Beim Anlegen einer Slack-Integration ruft say_hello sofort requests.post(url=url) auf, ohne Zeitlimit, ohne allow_redirects=False und ohne jede Adresspruefung (api/chalicelib/core/collaborations/collaboration_slack.py:31). Der Lesepfad gibt mit send_raw den Rumpf als {"data": r.text} zurueck (:68), und collaboration_msteams.py:71 verhaelt sich gleich. Der Webhook-Zweig ruft requests.post(url=hook["endpoint"], ...) und legt r.text ab (api/chalicelib/core/webhook.py:175 und :187), waehrend die zugehoerigen Routen allein von OR_context abhaengen (api/routers/core_dynamic.py:165 und :528). Eine Suche im ausgelieferten Code nach ipaddress, is_private, is_loopback, 169.254 oder einer Positivliste liefert keinen Treffer.
Proof of Concept
# Als Mitglied in der niedrigsten Rolle, keine Administratorrechte noetig:
POST /integrations/slack
{"name": "poc", "url": "http://169.254.169.254/latest/meta-data/"}
# Der Server ruft die Adresse beim Anlegen sofort ab.
# Anschliessend ueber den Notiz-Pfad lesen:
POST /<projekt>/notes/<id>/slack/<integrations-id>
-> {"data": "<Antwort des internen Dienstes>"}
Der Statuscode beim Anlegen unterscheidet offene, geschlossene und gefilterte Ports und taugt damit schon ohne den Lesepfad als Scanner fuer das interne Netz.
Auswirkung
- Abruf beliebiger interner HTTP-Dienste aus dem Netz des Servers heraus durch jedes angemeldete Mitglied.
- Auslesen der Antworten im Klartext ueber den Notiz-Pfad, einschliesslich des Metadaten-Dienstes bei einem Betrieb in der Cloud.
- Portabtastung des internen Netzes ueber die drei unterscheidbaren Statuscodes.
- Keine Begrenzung durch eine Namensliste moeglich, da der Klient Weiterleitungen folgt.
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.
