Das Symptom
Auf der eigenen Seite soll ein fremder Inhalt erscheinen — der Fahrplan des
Verkehrsverbunds, das Buchungsformular des Anbieters, das Kundenportal einer
Bank. Der Rahmen ist eingebaut, die Adresse stimmt, im eigenen Tab lädt die Seite
tadellos. Im Rahmen bleibt sie weiß. Manchmal blitzt kurz etwas auf und
verschwindet wieder. In der Konsole steht eine Zeile, die mit „Refused to
display“ beginnt.
Die „Layer 8“-Wahrheit
Hier ist nichts kaputt. Hier funktioniert eine Absperrung, und zwar exakt so, wie
sie gedacht ist. Der Denkfehler steckt in der Annahme, das Einbetten sei die
Entscheidung dessen, der einbettet. Sie ist es nicht. Sie gehört dem, dessen
Inhalt eingebettet werden soll, und durchgesetzt wird sie vom Browser des
Besuchers — also an zwei Stellen, an denen du nichts zu bestimmen hast.
Der Grund für diese Absperrung heißt Clickjacking und ist unangenehm einfach: Ein
Angreifer lädt die echte Seite eines Anbieters in einen unsichtbaren Rahmen, legt
seine eigene harmlose Oberfläche darüber und positioniert sie so, dass ein Klick
auf „Weiter“ in Wahrheit den Schalter im fremden Rahmen trifft. Der Besucher ist
dort angemeldet, also führt die fremde Seite die Aktion bereitwillig aus. Sie
sieht ja einen ganz normalen Klick eines angemeldeten Nutzers. Genau dagegen
wurde 2013 die Kopfzeile X-Frame-Options standardisiert; die
CSP-Spezifikation nennt dieselbe Gefahr heute unter dem Oberbegriff UI Redressing.
Zwei Mechanismen existieren nebeneinander, was regelmäßig für Verwirrung sorgt.
Der ältere ist grob: DENY verbietet jedes Einbetten, SAMEORIGIN erlaubt es
nur der eigenen Herkunft — Zwischenstufen gibt es nicht. Der Wert ALLOW-FROM
war einmal als Freigabe für einzelne Partner gedacht, wurde aber nie überall
umgesetzt und ist praktisch tot. Der neuere Mechanismus ist die CSP-Regel
frame-ancestors, die eine echte Liste erlaubter Domains kennt, und wo beide
gesendet werden, gewinnt sie: Die Spezifikation legt fest, dass sie die alte
Kopfzeile übersteuert.
Für alle, die etwas einbetten wollen, folgt daraus eine kurze Anweisung. Sieh mit
curl nach, was der fremde Server sendet, statt im Rahmen weiter herumzuraten.
Steht dort ein Verbot, ist das Thema für dich beendet — frag den Betreiber, oder
setz einen Link. Und für den umgekehrten Fall, in dem die Seite dir gehört: Die
Regel gehört in eine echte HTTP-Kopfzeile. Im meta-Element des Dokuments wird
sie laut Spezifikation ignoriert, und zwar aus einem sehr handfesten Grund — bis
der Browser dort ankommt, hat er die Seite längst in den fremden Rahmen geladen.
Die echte, verifizierte Lösung
1000 % faktenbasiert. Jeder Befehl ist real, jeder Pfad existiert. Schritt für Schritt:
1.Die Meldung in der Konsole lesen
Mit F12 die Entwicklerwerkzeuge öffnen und in die Konsole schauen. Dort steht, welcher der beiden Mechanismen zugeschlagen hat: eine Meldung mit `X-Frame-Options` oder eine, die eine Richtlinie mit `frame-ancestors` nennt. Beide sagen dasselbe aus — der eingebettete Server hat das Anzeigen verweigert —, aber die Reparatur setzt an unterschiedlichen Stellen an.
2.Nachsehen, was der fremde Server tatsächlich sendet
Die Konsole zeigt die Wirkung, dieser Befehl die Ursache. Er holt nur die Kopfzeilen der Antwort und filtert die beiden Zeilen heraus, um die es geht. Kommt keine Ausgabe, liegt es nicht am Einbettungsschutz und du suchst an der falschen Stelle weiter.
curl -sSI https://example.com/ | grep -i -E "^x-frame-options:|^content-security-policy:"
3.Den gefundenen Wert richtig deuten
`DENY` heißt: in keinem Rahmen, von niemandem, niemals. `SAMEORIGIN` heißt: nur auf der eigenen Herkunft — für dich als Fremde also ebenfalls nein. Bei `frame-ancestors` steht dahinter eine Liste erlaubter Quellen; `'none'` entspricht ungefähr `DENY`, `'self'` ungefähr `SAMEORIGIN`. Steht deine Domain nicht in der Liste, ist die Antwort nein. Ein wichtiger Hinweis aus RFC 7034 zu `SAMEORIGIN`: Kann ein Browser nicht zuverlässig feststellen, ob Inhalt und Rahmen dieselbe Herkunft haben, muss er das wie `DENY` behandeln.
4.Wenn dir die Seite gehört: die Regel gezielt setzen
Die moderne und feiner steuerbare Variante ist die CSP-Regel. Sie erlaubt eine Liste konkreter Partner statt der Alles-oder-nichts-Entscheidung der alten Kopfzeile. Wichtig: als echte HTTP-Kopfzeile ausliefern, nicht im `meta`-Element, und daran denken, dass diese Regel nicht auf `default-src` zurückfällt. Beispiel für nginx:
add_header Content-Security-Policy "frame-ancestors 'self' https://partner.example" always;
5.Die alte Kopfzeile als Auffangnetz danebenstellen
Sehr alte Browser kennen `frame-ancestors` nicht. Weil die CSP-Regel die alte Kopfzeile ohnehin übersteuert, wo beide verstanden werden, kostet das Mitsenden nichts und deckt den Rest ab. `SAMEORIGIN` erlaubt dabei die eigene Herkunft; eine Freigabe für einzelne Partner kann diese Kopfzeile nicht — dafür ist die CSP-Regel zuständig. Für Apache:
Header always set X-Frame-Options "SAMEORIGIN"
6.Die Änderung überprüfen, statt sie zu glauben
Nach dem Neuladen der Serverkonfiguration noch einmal die Kopfzeilen abrufen und danach die einbettende Seite mit geleertem Zwischenspeicher testen. Ein häufiger Stolperstein: Ein vorgelagerter Zwischenspeicher oder ein Auslieferungsnetz liefert die alte Antwort weiter, und du misst die Vergangenheit.
curl -sSI https://example.com/ | grep -i -E "^x-frame-options:|^content-security-policy:"
7.Wenn die Seite dir nicht gehört: den Betreiber fragen oder umbauen
Bleibt es beim Nein, gibt es zwei ehrliche Wege. Erstens: den Betreiber bitten, deine Domain in seine Liste erlaubter Vorfahren aufzunehmen — bei Geschäftspartnern ist das ein normaler Vorgang. Zweitens: statt des Rahmens einen normalen Link setzen, der die Seite in einem neuen Tab öffnet. Das ist nicht die elegante Lösung, aber die einzige, die dauerhaft funktioniert.