Entdecken Sie sichere ursprungsübergreifende Kommunikation mit der PostMessage-API. Erfahren Sie mehr über ihre Funktionen, Sicherheitsrisiken und Best Practices zur Minderung von Schwachstellen in Webanwendungen.
Ursprungsübergreifende Kommunikation: Sicherheitsmuster mit der PostMessage-API
Im modernen Web müssen Anwendungen häufig mit Ressourcen von unterschiedlichen Ursprüngen interagieren. Die Same-Origin-Policy (SOP) ist ein entscheidender Sicherheitsmechanismus, der Skripte daran hindert, auf Ressourcen von einem anderen Ursprung zuzugreifen. Es gibt jedoch legitime Szenarien, in denen eine ursprungsübergreifende Kommunikation notwendig ist. Die postMessage-API bietet einen kontrollierten Mechanismus, um dies zu erreichen, aber es ist unerlässlich, ihre potenziellen Sicherheitsrisiken zu verstehen und geeignete Sicherheitsmuster zu implementieren.
Verständnis der Same-Origin-Policy (SOP)
Die Same-Origin-Policy ist ein grundlegendes Sicherheitskonzept in Webbrowsern. Sie hindert Webseiten daran, Anfragen an eine andere Domain zu stellen als die, von der die Webseite ausgeliefert wurde. Ein Ursprung (Origin) wird durch das Schema (Protokoll), den Host (Domain) und den Port definiert. Wenn sich einer dieser Werte unterscheidet, gelten die Ursprünge als unterschiedlich. Zum Beispiel:
https://example.comhttps://www.example.comhttp://example.comhttps://example.com:8080
Dies sind alles unterschiedliche Ursprünge, und die SOP beschränkt den direkten Skriptzugriff zwischen ihnen.
Einführung in die PostMessage-API
Die postMessage-API bietet einen sicheren und kontrollierten Mechanismus für die ursprungsübergreifende Kommunikation. Sie ermöglicht es Skripten, Nachrichten an andere Fenster (z. B. iframes, neue Fenster oder Tabs) zu senden, unabhängig von deren Ursprung. Das empfangende Fenster kann dann auf diese Nachrichten lauschen und sie entsprechend verarbeiten.
Die grundlegende Syntax zum Senden einer Nachricht lautet:
otherWindow.postMessage(message, targetOrigin);
otherWindow: Eine Referenz auf das Zielfenster (z. B.window.parent,iframe.contentWindowoder ein Fensterobjekt, das vonwindow.openerhalten wurde).message: Die Daten, die Sie senden möchten. Dies kann jedes JavaScript-Objekt sein, das serialisiert werden kann (z. B. Zeichenketten, Zahlen, Objekte, Arrays).targetOrigin: Gibt den Ursprung an, an den Sie die Nachricht senden möchten. Dies ist ein entscheidender Sicherheitsparameter.
Auf der empfangenden Seite müssen Sie auf das message-Ereignis lauschen:
window.addEventListener('message', function(event) {
// ...
});
Das event-Objekt enthält die folgenden Eigenschaften:
event.data: Die vom anderen Fenster gesendete Nachricht.event.origin: Der Ursprung des Fensters, das die Nachricht gesendet hat.event.source: Eine Referenz auf das Fenster, das die Nachricht gesendet hat.
Sicherheitsrisiken und Schwachstellen
Obwohl postMessage eine Möglichkeit bietet, die SOP-Einschränkungen zu umgehen, birgt es bei unachtsamer Implementierung auch potenzielle Sicherheitsrisiken. Hier sind einige häufige Schwachstellen:
1. Nicht übereinstimmender Ziel-Ursprung
Das Versäumnis, die Eigenschaft event.origin zu validieren, ist eine kritische Schwachstelle. Wenn der Empfänger der Nachricht blind vertraut, kann jede beliebige Website bösartige Daten senden. Überprüfen Sie immer, ob event.origin mit dem erwarteten Ursprung übereinstimmt, bevor Sie die Nachricht verarbeiten.
Beispiel (verwundbarer Code):
window.addEventListener('message', function(event) {
// MACHEN SIE DAS NICHT!
processMessage(event.data);
});
Beispiel (sicherer Code):
window.addEventListener('message', function(event) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Nachricht von nicht vertrauenswürdigem Ursprung erhalten:', event.origin);
return;
}
processMessage(event.data);
});
2. Dateninjektion
Die Behandlung der empfangenen Daten (event.data) als ausführbarer Code oder deren direkte Injektion in das DOM kann zu Cross-Site-Scripting-Schwachstellen (XSS) führen. Bereinigen und validieren Sie die empfangenen Daten immer, bevor Sie sie verwenden.
Beispiel (verwundbarer Code):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
document.body.innerHTML = event.data; // MACHEN SIE DAS NICHT!
}
});
Beispiel (sicherer Code):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
const sanitizedData = sanitize(event.data); // Implementieren Sie eine ordnungsgemäße Bereinigungsfunktion
document.getElementById('message-container').textContent = sanitizedData;
}
});
function sanitize(data) {
// Implementieren Sie hier eine robuste Bereinigungslogik.
// Verwenden Sie zum Beispiel DOMPurify oder eine ähnliche Bibliothek
return DOMPurify.sanitize(data);
}
3. Man-in-the-Middle-Angriffe (MITM)
Wenn die Kommunikation über einen unsicheren Kanal (HTTP) erfolgt, kann ein MITM-Angreifer die Nachrichten abfangen und modifizieren. Verwenden Sie immer HTTPS für eine sichere Kommunikation.
4. Cross-Site Request Forgery (CSRF)
Wenn der Empfänger Aktionen auf der Grundlage der empfangenen Nachricht ohne ordnungsgemäße Validierung durchführt, könnte ein Angreifer potenziell Nachrichten fälschen, um den Empfänger zur Ausführung unbeabsichtigter Aktionen zu verleiten. Implementieren Sie CSRF-Schutzmechanismen, wie z. B. das Einfügen eines geheimen Tokens in die Nachricht und dessen Überprüfung auf der Empfängerseite.
5. Verwendung von Wildcards in targetOrigin
Das Setzen von targetOrigin auf * erlaubt es jedem Ursprung, die Nachricht zu empfangen. Dies sollte vermieden werden, es sei denn, es ist absolut notwendig, da es den Zweck der ursprungsbasierten Sicherheit zunichtemacht. Wenn Sie * verwenden müssen, stellen Sie sicher, dass Sie andere starke Sicherheitsmaßnahmen implementieren, wie z. B. Nachrichten-Authentifizierungscodes (MACs).
Beispiel (Vermeiden Sie dies):
otherWindow.postMessage(message, '*'); // Vermeiden Sie die Verwendung von '*', es sei denn, es ist absolut notwendig
Sicherheitsmuster und Best Practices
Um die mit postMessage verbundenen Risiken zu mindern, befolgen Sie diese Sicherheitsmuster und Best Practices:
1. Strikte Ursprungsvalidierung
Validieren Sie immer die Eigenschaft event.origin auf der Empfängerseite. Vergleichen Sie sie mit einer vordefinierten Liste vertrauenswürdiger Ursprünge. Verwenden Sie für den Vergleich die strikte Gleichheit (===).
2. Datenbereinigung und -validierung
Bereinigen und validieren Sie alle über postMessage empfangenen Daten, bevor Sie sie verwenden. Wenden Sie je nach Verwendungszweck der Daten geeignete Bereinigungstechniken an (z. B. HTML-Escaping, URL-Kodierung, Eingabevalidierung). Verwenden Sie Bibliotheken wie DOMPurify zur Bereinigung von HTML.
3. Nachrichten-Authentifizierungscodes (MACs)
Fügen Sie einen Nachrichten-Authentifizierungscode (MAC) in die Nachricht ein, um deren Integrität und Authentizität zu gewährleisten. Der Absender berechnet den MAC mit einem gemeinsamen geheimen Schlüssel und fügt ihn in die Nachricht ein. Der Empfänger berechnet den MAC mit demselben gemeinsamen geheimen Schlüssel neu und vergleicht ihn mit dem empfangenen MAC. Wenn sie übereinstimmen, gilt die Nachricht als authentisch und unverfälscht.
Beispiel (Verwendung von HMAC-SHA256):
// Absender
async function sendMessage(message, targetOrigin, sharedSecret) {
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(message));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
const securedMessage = {
data: message,
signature: signatureHex
};
otherWindow.postMessage(securedMessage, targetOrigin);
}
// Empfänger
async function receiveMessage(event, sharedSecret) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Nachricht von nicht vertrauenswürdigem Ursprung erhalten:', event.origin);
return;
}
const securedMessage = event.data;
const message = securedMessage.data;
const receivedSignature = securedMessage.signature;
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(message));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
if (signatureHex === receivedSignature) {
console.log('Nachricht ist authentisch!');
processMessage(message); // Fahren Sie mit der Verarbeitung der Nachricht fort
} else {
console.error('Überprüfung der Nachrichtensignatur fehlgeschlagen!');
}
}
Wichtig: Der gemeinsame geheime Schlüssel muss sicher generiert und gespeichert werden. Vermeiden Sie es, den Schlüssel fest im Code zu verankern.
4. Verwendung von Nonce und Zeitstempeln
Um Replay-Angriffe zu verhindern, fügen Sie eine eindeutige Nonce (einmalig verwendete Nummer) und einen Zeitstempel in die Nachricht ein. Der Empfänger kann dann überprüfen, ob die Nonce noch nicht verwendet wurde und ob der Zeitstempel innerhalb eines akzeptablen Zeitrahmens liegt. Dies mindert das Risiko, dass ein Angreifer zuvor abgefangene Nachrichten wiederholt abspielt.
5. Prinzip der geringsten Rechte (Principle of Least Privilege)
Gewähren Sie dem anderen Fenster nur die minimal notwendigen Berechtigungen. Wenn das andere Fenster beispielsweise nur Daten lesen muss, erlauben Sie ihm nicht, Daten zu schreiben. Gestalten Sie Ihr Kommunikationsprotokoll unter Berücksichtigung des Prinzips der geringsten Rechte.
6. Content Security Policy (CSP)
Verwenden Sie eine Content Security Policy (CSP), um die Quellen einzuschränken, aus denen Skripte geladen werden können, und die Aktionen, die Skripte ausführen dürfen. Dies kann dazu beitragen, die Auswirkungen von XSS-Schwachstellen zu mindern, die durch unsachgemäßen Umgang mit postMessage-Daten entstehen könnten.
7. Eingabevalidierung
Validieren Sie immer die Struktur und das Format der empfangenen Daten. Definieren Sie ein klares Nachrichtenformat und stellen Sie sicher, dass die empfangenen Daten diesem Format entsprechen. Dies hilft, unerwartetes Verhalten und Schwachstellen zu vermeiden.
8. Sichere Datenserialisierung
Verwenden Sie ein sicheres Datenserialisierungsformat wie JSON, um Nachrichten zu serialisieren und zu deserialisieren. Vermeiden Sie Formate, die die Ausführung von Code ermöglichen, wie z. B. eval() oder Function().
9. Begrenzung der Nachrichtengröße
Begrenzen Sie die Größe der über postMessage gesendeten Nachrichten. Große Nachrichten können übermäßige Ressourcen verbrauchen und potenziell zu Denial-of-Service-Angriffen führen.
10. Regelmäßige Sicherheitsaudits
Führen Sie regelmäßige Sicherheitsaudits Ihres Codes durch, um potenzielle Schwachstellen zu identifizieren und zu beheben. Achten Sie besonders auf die Implementierung von postMessage und stellen Sie sicher, dass alle bewährten Sicherheitspraktiken befolgt werden.
Beispielszenario: Sichere Kommunikation zwischen einem Iframe und seiner übergeordneten Seite
Stellen Sie sich ein Szenario vor, in dem ein auf https://iframe.example.com gehosteter Iframe mit seiner übergeordneten Seite kommunizieren muss, die auf https://parent.example.com gehostet wird. Der Iframe muss Benutzerdaten zur Verarbeitung an die übergeordnete Seite senden.
Iframe (https://iframe.example.com):
// Generieren Sie einen gemeinsamen geheimen Schlüssel (ersetzen Sie dies durch eine sichere Methode zur Schlüsselgenerierung)
const sharedSecret = 'YOUR_SECURE_SHARED_SECRET';
// Benutzerdaten abrufen
const userData = {
name: 'John Doe',
email: 'john.doe@example.com'
};
// Senden Sie die Benutzerdaten an die übergeordnete Seite
async function sendUserData(userData) {
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(userData));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
const securedMessage = {
data: userData,
signature: signatureHex
};
parent.postMessage(securedMessage, 'https://parent.example.com');
}
sendUserData(userData);
Übergeordnete Seite (https://parent.example.com):
// Gemeinsamer geheimer Schlüssel (muss mit dem Schlüssel des Iframes übereinstimmen)
const sharedSecret = 'YOUR_SECURE_SHARED_SECRET';
window.addEventListener('message', async function(event) {
if (event.origin !== 'https://iframe.example.com') {
console.warn('Nachricht von nicht vertrauenswürdigem Ursprung erhalten:', event.origin);
return;
}
const securedMessage = event.data;
const userData = securedMessage.data;
const receivedSignature = securedMessage.signature;
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(userData));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
if (signatureHex === receivedSignature) {
console.log('Nachricht ist authentisch!');
// Verarbeiten Sie die Benutzerdaten
console.log('Benutzerdaten:', userData);
} else {
console.error('Überprüfung der Nachrichtensignatur fehlgeschlagen!');
}
});
Wichtige Hinweise:
- Ersetzen Sie
YOUR_SECURE_SHARED_SECRETdurch einen sicher generierten gemeinsamen geheimen Schlüssel. - Der gemeinsame geheime Schlüssel muss sowohl im Iframe als auch auf der übergeordneten Seite identisch sein.
- Dieses Beispiel verwendet HMAC-SHA256 zur Nachrichtenauthentifizierung.
Fazit
Die postMessage-API ist ein leistungsstarkes Werkzeug, um ursprungsübergreifende Kommunikation in Webanwendungen zu ermöglichen. Es ist jedoch entscheidend, die potenziellen Sicherheitsrisiken zu verstehen und geeignete Sicherheitsmuster zu implementieren, um diese Risiken zu mindern. Indem Sie die in diesem Leitfaden beschriebenen Sicherheitsmuster und Best Practices befolgen, können Sie postMessage sicher verwenden, um robuste und sichere Webanwendungen zu erstellen.
Denken Sie daran, der Sicherheit immer Priorität einzuräumen und sich über die neuesten bewährten Sicherheitspraktiken für die Webentwicklung auf dem Laufenden zu halten. Überprüfen Sie regelmäßig Ihren Code und Ihre Sicherheitskonfigurationen, um sicherzustellen, dass Ihre Anwendungen vor potenziellen Schwachstellen geschützt sind.