postMessage ist eine API-Grenze: Iframe- und Popup-Kommunikation sicher prüfen
Viele Websites tauschen Daten mit iframes, Login-Popups, Zahlungsfenstern und Widgets über window.postMessage aus. So prüfen Sie targetOrigin, event.origin, event.source, Nachrichtenschemata und privilegierte Aktionen.
Ein eingebettetes Termin-Widget meldet: „Buchung abgeschlossen“. Ein Zahlungsfenster schickt einen Status an die Hauptseite. Ein Login-Popup teilt mit, dass die Anmeldung fertig ist. Ein Editor läuft in einem iframe und sendet seinen Inhalt an das Dashboard.
Für solche Fälle gibt es window.postMessage(). Die Browser-API ist ausdrücklich dafür gedacht, kontrolliert zwischen verschiedenen Window-Kontexten zu kommunizieren – auch über Origin-Grenzen hinweg. Genau deshalb sollte man sie nicht wie einen harmlosen Event-Bus behandeln.
Sobald eine Nachricht eine Aktion auslösen, Daten verändern oder vertrauliche Informationen übertragen kann, ist sie eine API-Grenze im Browser.
Die entscheidende Audit-Frage lautet daher nicht nur: „Funktioniert das Widget?“ Sondern: „Wer darf wem welche Nachricht schicken, und was darf diese Nachricht bewirken?“
Das Sicherheitsmodell in zwei Richtungen
Bei postMessage gibt es zwei unterschiedliche Vertrauensentscheidungen.
Die sendende Seite entscheidet, an welchen Origin eine Nachricht geliefert werden darf. Dafür dient targetOrigin. WHATWG beschreibt, dass eine Nachricht verworfen wird, wenn der aktuelle Origin des Zielfensters nicht zum angegebenen Ziel-Origin passt. Der Wert * hebt diese Einschränkung auf.
Die empfangende Seite entscheidet, welchem Absender sie vertraut. Dafür stehen im MessageEvent insbesondere event.origin und gegebenenfalls event.source zur Verfügung. MDN empfiehlt ausdrücklich, die Identität des Senders zu prüfen und anschließend auch die Struktur der empfangenen Daten zu validieren.
Beide Richtungen sind nötig. Ein sicherer Empfänger hilft nicht, wenn der Sender sensible Daten mit targetOrigin: "*" an ein Fenster schickt, das inzwischen auf eine fremde Seite navigiert wurde. Und ein exakter targetOrigin hilft nicht, wenn der Empfänger anschließend jede beliebige eingehende Nachricht akzeptiert.
Red Flag 1: targetOrigin ist überall *
* ist bequem, weil Nachrichten unabhängig vom aktuellen Ziel-Origin zugestellt werden können. Genau das ist aber der Nachteil.
MDN empfiehlt einen konkreten targetOrigin, wenn der erwartete Empfänger bekannt ist. Der Match ist exakt und umfasst Schema, Hostname und gegebenenfalls Port.
Warum ist das relevant? Ein Window-Objekt kann während seiner Lebenszeit zu einem anderen Dokument navigieren. Ein Popup startet beispielsweise auf Ihrer eigenen Domain, wird später weitergeleitet und zeigt am Ende eine fremde Seite. Wer danach weiterhin vertrauliche Nachrichten mit * sendet, verlässt sich nicht mehr auf die Identität des Empfängers.
Prüfen Sie deshalb jeden postMessage-Aufruf:
- Ist der Empfänger tatsächlich variabel oder kennen wir seinen Origin?
- Wird ein konkreter HTTPS-Origin verwendet?
- Kommt der Wert aus vertrauenswürdiger Konfiguration oder ungeprüft aus URL-Parametern?
- Kann sich das Zielfenster vor dem Senden auf einen anderen Origin bewegen?
- Enthält die Nachricht Daten, die ein fremdes Dokument nicht sehen sollte?
* ist kein automatisch kritischer Fehler. Es ist aber eine bewusste Freigabe an jeden aktuellen Origin des Zielkontexts und sollte genau so begründet werden können.
Red Flag 2: Der Empfänger prüft event.origin gar nicht
Ein häufiger Listener sieht konzeptionell so aus: Nachricht empfangen, event.data.type prüfen, Aktion ausführen.
Damit fehlt die wichtigste Identitätsprüfung. OWASP weist darauf hin, dass empfangende Seiten den Origin des Senders prüfen sollen. MDN ergänzt, dass je nach Architektur auch event.source relevant sein kann.
Das ist wichtig, weil nicht nur Ihr vorgesehenes Widget versuchen kann, eine Nachricht zu schicken. Fenster innerhalb einer bekannten Fenster- oder iframe-Hierarchie können Referenzen aufeinander erhalten und Nachrichten senden. Der Listener muss daher selbst entscheiden, welcher Absender akzeptiert wird.
Eine gute Prüfung verwendet exakte Origins wie https://payments.example.com. Unscharfe String-Checks wie origin.includes("example.com") oder origin.endsWith("example.com") sind gefährlich, weil Domains wie example.com.attacker.invalid beziehungsweise ähnlich konstruierte Namen unerwartet matchen können. OWASP empfiehlt ausdrücklich einen exakten Vergleich mit den erwarteten vollständig qualifizierten Domains.
Red Flag 3: Der Origin stimmt, also gilt die Nachricht als vertrauenswürdig
event.origin beantwortet eine Herkunftsfrage. Es validiert nicht automatisch den Inhalt.
Wenn auf einem erlaubten Origin ein XSS-Fehler existiert, ein Drittanbieter-Script kompromittiert wird oder mehrere Anwendungen denselben Origin teilen, kann dort ausgeführter Code ebenfalls Nachrichten senden. OWASP empfiehlt deshalb zusätzlich, event.data als nicht vertrauenswürdige Eingabe zu behandeln und zu validieren.
Ein belastbares Nachrichtenprotokoll definiert zum Beispiel:
- erlaubte Nachrichtentypen;
- Pflichtfelder je Typ;
- Datentypen und maximale Längen;
- erlaubte Werte oder Enums;
- Versionsnummern;
- Request- oder Correlation-IDs;
- welche Zustände eine Nachricht verändern darf;
- welche Antworten auf welche Anfragen zulässig sind.
Die sichere Frage lautet nicht: „Kam das Objekt von unserer Partnerdomain?“ Sondern: „Kam es vom erwarteten Kontext, entspricht es unserem Protokoll und darf dieser konkrete Absender diese konkrete Operation auslösen?“
Red Flag 4: Nachrichtendaten landen direkt in HTML oder Code
Cross-window Messaging überträgt Daten. Es sollte keine Abkürzung zur Code-Ausführung sein.
OWASP warnt ausdrücklich davor, empfangene Nachrichtendaten mit eval() auszuführen oder ungeprüft über innerHTML in den DOM zu schreiben. Ein PostMessage-Kanal kann sonst zu einem DOM-XSS-Pfad werden.
Wenn Text angezeigt werden soll, ist textContent oft die passendere Primitive. Wenn strukturierte Daten gerendert werden, sollte das normale Template- oder Komponentenmodell verwendet werden. Wenn tatsächlich HTML übertragen werden muss, braucht dieser Pfad dieselben Sanitization- und Trusted-Content-Regeln wie jeder andere HTML-Eingang.
Die Tatsache, dass Daten nicht aus einem Formular, sondern aus einem iframe kommen, macht sie nicht automatisch sicher.
Red Flag 5: Eine Nachricht darf privilegierte Aktionen auslösen
Besonders interessant wird der Audit, wenn eine Message mehr tut als UI-Zustand zu synchronisieren. Beispiele:
- „Bestellung abschließen“;
- „Dokument löschen“;
- „Account verbinden“;
- „Adresse übernehmen“;
- „Token speichern“;
- „Admin-Ansicht öffnen“;
- „Zahlungsstatus = erfolgreich“ setzen.
Dann ist der Message-Handler funktional ein API-Endpunkt.
Eine Browser-Nachricht sollte nie die einzige Autorisierung für eine serverseitig relevante Aktion sein. Wenn der Handler anschließend einen Request an Ihre API sendet, muss der Server weiterhin Session, Rolle, Berechtigung und fachliche Voraussetzungen prüfen. Eine Message wie { type: "payment-success" } ist kein Zahlungsnachweis. Eine Message wie { type: "delete-user" } ist keine Berechtigung.
Das ist ein wichtiger Unterschied zwischen Signal und Beweis. Der Browser kann ein Signal liefern, dass etwas passiert sein könnte. Kritische Zustände sollten aus der zuständigen vertrauenswürdigen Quelle bestätigt werden.
event.source ist die oft vergessene zweite Identität
Stellen Sie sich eine Seite mit mehreren iframes vom gleichen Origin vor. Ein reiner Origin-Check kann dann nicht unterscheiden, welches konkrete Fenster die Nachricht gesendet hat.
event.source enthält eine Referenz auf das sendende Window. In einer kontrollierten Architektur kann der Empfänger prüfen, ob die Nachricht beispielsweise wirklich von paymentFrame.contentWindow kam und nicht von einem anderen gleich-originigen Frame.
Nicht jede Integration braucht diese zusätzliche Prüfung. Aber sobald mehrere Fenster denselben erlaubten Origin teilen oder unterschiedliche Fähigkeiten besitzen, ist sie eine wertvolle zweite Grenze.
Ein robustes Nachrichtenprotokoll statt losem Event-Nebel
Viele PostMessage-Integrationen wachsen organisch. Erst gibt es ready, dann resize, später success, close, setToken, refresh und irgendwann kennt niemand mehr die erlaubten Zustände.
Behandeln Sie die Schnittstelle stattdessen wie eine kleine API.
1. Origins zentral definieren
Erwartete Origins sollten aus kontrollierter Konfiguration stammen. Entwicklungs-, Staging- und Produktionswerte dürfen unterschiedlich sein, aber nicht aus beliebigen Request- oder Query-Parametern übernommen werden.
2. Nachrichten versionieren
Ein Feld wie version: 1 hilft, alte und neue Widget-Versionen kontrolliert parallel zu betreiben. Ohne Versionierung werden Felder oft stillschweigend umgedeutet.
3. Ein diskriminiertes Schema verwenden
Jede Nachricht hat einen klaren Typ, zum Beispiel widget.ready, widget.resize oder checkout.completed, und dafür ein eigenes validiertes Payload-Schema. Unbekannte Nachrichtentypen werden ignoriert oder sicher protokolliert.
4. Fähigkeiten klein halten
Ein iframe, das nur seine Höhe melden muss, braucht keine universelle execute-Nachricht. Kleine, spezifische Operationen sind leichter zu prüfen und schwieriger zu missbrauchen.
5. Antworten korrelieren
Bei Request/Response-Mustern helfen zufällige Request-IDs, damit eine Antwort eindeutig einer offenen Anfrage zugeordnet werden kann. Das ersetzt keine Origin-Prüfung, verhindert aber, dass beliebige alte Nachrichten als Antwort auf einen neuen Vorgang interpretiert werden.
6. Listener nur so lange wie nötig aktiv halten
Ein globaler message-Listener, der nach dem Schließen eines Popups für den Rest der Session aktiv bleibt, vergrößert unnötig die Angriffsfläche. Registrieren und entfernen Sie Listener entlang des tatsächlichen Lifecycles.
Iframes brauchen zusätzlich eine Embedding-Strategie
postMessage und iframe-Sicherheit sind eng verbunden, aber nicht dasselbe.
Ein sandbox-Attribut kann eingebetteten Dokumenten Fähigkeiten entziehen. Content Security Policy kann mit frame-src beziehungsweise verwandten Direktiven steuern, welche Frames geladen werden dürfen, und frame-ancestors kontrolliert, wer Ihre eigene Seite einbetten darf. Diese Kontrollen ersetzen keine Nachrichtenvalidierung – sie verkleinern aber die Zahl der Kontexte, mit denen überhaupt kommuniziert werden kann.
Bei sandboxed oder opaque origins gibt es Sonderfälle, in denen ein exakter Ziel-Origin nicht möglich ist. MDN nennt beispielsweise data:-URLs als opaque Origins, für die zum Senden * erforderlich sein kann. Solche Ausnahmen sollten jedoch nicht das gesamte Protokoll verwässern. Wenn targetOrigin nicht einschränken kann, werden Datenminimierung, event.source, strikte Nachrichtenschemata und ein bewusst begrenzter Funktionsumfang umso wichtiger.
Ein praktischer Audit in 30 Minuten
Für eine erste Bestandsaufnahme suchen Sie im Frontend nach:
postMessage(;addEventListener("message";onmessage;contentWindow;window.open;- eingebetteten Drittanbieter-Widgets und iframes.
Erstellen Sie danach eine kleine Tabelle: Sender, Empfänger, erlaubter Origin, Nachrichtentypen, sensible Daten, ausgelöste Aktionen, Origin-Prüfung, Source-Prüfung, Schema-Validierung.
Testen Sie anschließend die wichtigsten Kanäle im Browser:
- Was passiert bei einer Nachricht vom falschen Origin?
- Was passiert bei einem erlaubten Origin mit falschem Payload?
- Werden unbekannte Typen verworfen?
- Kann ein anderer gleich-originiger Frame dieselbe Aktion auslösen?
- Werden sensible Daten mit
*gesendet? - Kann das Ziel vor dem Senden navigieren?
- Löst eine Message serverseitige Änderungen aus, ohne dass dort erneut Berechtigungen geprüft werden?
- Werden Message-Daten als HTML oder Code interpretiert?
- Bleiben Listener länger aktiv als nötig?
- Sind erlaubte Origins für Produktion und Preview sauber getrennt?
Schon dieser kurze Test findet häufig mehr als ein pauschaler „Security Header Score“, weil er tatsächliche Anwendungslogik betrachtet.
Was Website-Pflichtencheck prüfen würde
Bei einem technischen Check von eingebetteten Diensten und Cross-Window-Kommunikation können unter anderem geprüft werden:
- alle
postMessage-Sender und Message-Listener; - konkrete
targetOrigin-Werte und Wildcards; - exakte
event.origin-Prüfungen; - sinnvolle Nutzung von
event.source; - Daten- und Schema-Validierung;
- DOM-XSS-Risiken durch Message-Payloads;
- privilegierte Aktionen und serverseitige Autorisierung;
- iframe-
sandbox, CSP und erlaubte Embedding-Quellen; - Popup- und Redirect-Lifecycles;
- Unterschiede zwischen Produktion, Staging und Preview;
- Drittanbieter-Widgets, deren Origin oder Integrationsmodell sich geändert hat;
- Lifecycle und Cleanup globaler Listener;
- Tests für falsche Origins, fehlerhafte Payloads und unerwartete Fenster.
Das Ziel ist nicht, postMessage zu vermeiden. Die API existiert gerade deshalb, weil moderne Websites sicher über Origin-Grenzen hinweg kommunizieren müssen.
Aber ein Message-Listener ist keine interne Funktion nur deshalb, weil er im Frontend läuft. Sobald fremde Fenster ihn erreichen können, verdient er dieselbe Sorgfalt wie jeder andere API-Eingang.