iframe: „odrzucił połączenie” – naprawa X-Frame-Options

Kluczowy wniosek

„Serwer odrzucił połączenie” w iframe to nie błąd sieci. Osadzana witryna wysłała nagłówek X-Frame-Options lub CSP frame-ancestors, który każe przeglądarce nie wyświetlać jej wewnątrz innej strony. Poprawka leży po stronie serwera osadzanej witryny, nigdy w Twoim znaczniku iframe: zostaw SAMEORIGIN dla całej witryny i otwórz jedną ścieżkę embed z frame-ancestors *.

Sprawdź teraz

X-Frame-Options w praktyce: iframe z komunikatem „odrzucił połączenie” i rozwiązanie — SAMEORIGIN dla całej witryny, CSP frame-ancestors * tylko dla ścieżki embed
X-Frame-Options w praktyce: iframe z komunikatem „odrzucił połączenie” i rozwiązanie — SAMEORIGIN dla całej witryny, CSP frame-ancestors * tylko dla ścieżki embed
Zacznij czytać
  1. Co naprawdę oznacza „odrzucił połączenie” w iframe
  2. Jak działa X-Frame-Options
  3. DENY czy SAMEORIGIN: który wysłać?
  4. ALLOW-FROM nie żyje — nie używaj go
  5. frame-ancestors: dyrektywa CSP, która go zastępuje
  6. Co się dzieje, gdy ustawione są oba nagłówki
  7. Krok 1: znajdź nagłówek, który cię blokuje
  8. Krok 2: zdecyduj, kto może osadzać stronę
  9. Właściwy wzorzec: zablokuj witrynę, otwórz jedną ścieżkę embed
  10. Nginx (i pułapka add_header)
  11. Apache
  12. Next.js i Express
  13. Bezpieczne osadzanie widżetu zewnętrznego dostawcy
  14. sandbox: co zezwolić dla widżetu kalkulatora
  15. Dlaczego po stronie osadzającego nie ma rozwiązania
  16. Sprawdzaj nagłówki po każdej zmianie
  17. Objaw → przyczyna → rozwiązanie
  18. Najczęściej zadawane pytania

Co naprawdę oznacza „odrzucił połączenie” w iframe

Dodajesz <iframe> wskazujący na inną witrynę, odświeżasz i zamiast strony widzisz szare pole z ikoną smutnej buźki; po najechaniu kursorem pojawia się napis Serwer example.com odrzucił połączenie. Pierwsza myśl to problem z siecią, bo dokładnie to samo Chrome pokazuje przy martwym serwerze. Ale otwórz DevTools, a konsola opowie inną historię:

text
Refused to display 'https://emicalcs.com/' in a frame because it set 'X-Frame-Options' to 'sameorigin'.

W sieci nic nie zawiodło. DNS się rozwiązał, połączenie TCP zostało otwarte, TLS się zakończył, a serwer zwrócił zupełnie poprawną odpowiedź 200. Przeglądarka pobrała stronę, odczytała jeden nagłówek odpowiedzi i celowo odmówiła wyświetlenia jej w Twojej stronie. Tym nagłówkiem jest X-Frame-Options, a jego zadaniem jest ochrona przed clickjackingiem — atakiem, w którym złośliwa strona ładuje formularz logowania lub płatności w niewidocznym iframe i nakłania odwiedzających do kliknięcia przycisków, których nie widzą.

To ważne dla diagnozy. Prawdziwa awaria połączenia — ERR_CONNECTION_REFUSED po otwarciu adresu bezpośrednio w karcie — to problem serwera lub zapory, opisany w artykule ERR_CONNECTION_REFUSED. Wersja z iframe pojawia się tylko wtedy, gdy strona jest osadzana, a ten sam adres w osobnej karcie działa bez zarzutu. Jeśli witryna ładuje się bezpośrednio, ale nie w Twojej ramce, masz do czynienia z polityką osadzania, a nie z awarią.

Sprawdź nagłówki X-Frame-Options i CSP strony

Jak działa X-Frame-Options

X-Frame-Options to nagłówek odpowiedzi HTTP, opisany po raz pierwszy w RFC 7034 — informacyjnym RFC, które obecnie zastępuje standard HTML — i obsługiwany przez każdą przeglądarkę od Internet Explorera 8. Serwer dołącza go do strony; przeglądarka odczytuje go przed renderowaniem i decyduje, czy strona może pojawić się wewnątrz <frame>, <iframe>, <embed> lub <object> na innej stronie.

Ma dwie wartości, które wciąż mają znaczenie, i jedną, która już nie ma:

WartośćKto może osadzić stronęTypowe zastosowanie
DENYNikt — nawet ta sama witrynaStrony logowania, panele administracyjne, wszystko z sesją
SAMEORIGINTylko strony z dokładnie tego samego originu (schemat + host + port)Rozsądne ustawienie domyślne dla większości witryn; pozwala osadzać własne strony
ALLOW-FROM https://a.comPrzestarzałe — ignorowane przez każdą nowoczesną przeglądarkęNie używaj; zobacz niżej

Nagłówek działa dla każdej odpowiedzi osobno, a nie dla całej witryny. To szczegół, który większość poradników pomija, a który umożliwia rozwiązanie z tego artykułu: serwer może wysyłać SAMEORIGIN na 99% stron i inną politykę na jednej ścieżce przeznaczonej do osadzania.

DENY czy SAMEORIGIN: który wysłać?

Wysyłaj SAMEORIGIN, chyba że masz powód, by tego nie robić. Blokuje każdą ramkę z zewnątrz, a Twoja witryna nadal może osadzać własne strony — widżety pomocy, podglądy, panele ładujące inne wewnętrzne strony.

Wysyłaj DENY na stronach, na których osadzanie nie ma uzasadnienia, a skutki nadużycia są poważne: formularze logowania, strony płatności, akcje jednym kliknięciem, takie jak „usuń konto”. DENY obejmuje też subtelny przypadek, gdy atakujący zdoła wstrzyknąć ramkę na jedną z Twoich stron — przy SAMEORIGIN taka wstrzyknięta ramka nadal byłaby dozwolona.

Cokolwiek wybierzesz, ustaw to raz na poziomie serwera WWW lub CDN, aby obejmowało każdą odpowiedź, w tym strony błędów i zasoby statyczne. Nagłówki ustawiane w kodzie aplikacji często omijają stronę 404 i stronę prac serwisowych, więc ustawiaj je tam, gdzie przechodzi każda odpowiedź.

ALLOW-FROM nie żyje — nie używaj go

Starsze artykuły wciąż polecają X-Frame-Options: ALLOW-FROM https://partner.com, aby zezwolić na jedną osadzającą witrynę. Nigdy nie działało w Chrome ani Safari, a Firefox porzucił je w wersji 70 (październik 2019). Co gorsza, nowoczesna przeglądarka widząca ALLOW-FROM uznaje cały nagłówek za nieprawidłowy i ignoruje go całkowicie, przez co stronę może osadzić każdy — dokładnie odwrotnie, niż zamierzałeś.

Jeśli chcesz zezwolić konkretnym witrynom, odpowiedzią jest CSP frame-ancestors, omówione dalej. X-Frame-Options potrafi powiedzieć tylko „nikt” albo „tylko ja”.

frame-ancestors: dyrektywa CSP, która go zastępuje

Content Security Policy Level 2 wprowadziło frame-ancestors — dyrektywę, która robi wszystko, co X-Frame-Options, i dodaje to, czego tamten nagłówek nie potrafi: prawdziwą listę dozwolonych źródeł. Działa w standardowym nagłówku Content-Security-Policy i przyjmuje jedno lub więcej źródeł:

http
# Nobody may frame this (same as X-Frame-Options: DENY)
Content-Security-Policy: frame-ancestors 'none'

# Only my own origin (same as SAMEORIGIN)
Content-Security-Policy: frame-ancestors 'self'

# My origin plus two named partners, one with a subdomain wildcard
Content-Security-Policy: frame-ancestors 'self' https://partner.example https://*.trusted.example

# Anyone may frame this — for a public embed endpoint only
Content-Security-Policy: frame-ancestors *

Zanim przełączysz się na nią, warto znać trzy szczegóły:

  • Sprawdza każdego przodka, nie tylko rodzica. Jeśli strona A osadza stronę B, a B osadza Twoją stronę, dozwolone muszą być zarówno A, jak i B (CSP). Standard HTML stosuje teraz X-Frame-Options również do każdego przodka (WHATWG), ale starsze przeglądarki sprawdzały tylko rodzica lub stronę najwyższego poziomu.
  • Działa wyłącznie jako nagłówek HTTP. frame-ancestors jest jawnie ignorowane, gdy CSP jest dostarczane przez znacznik <meta http-equiv> — przeglądarka musi znać politykę, zanim w ogóle zdecyduje, czy renderować dokument. To samo dotyczy X-Frame-Options. Żadnego z nich nie da się ustawić z poziomu HTML.
  • Symbole wieloznaczne działają per host, nie globalnie. https://*.example.com pasuje do dowolnej subdomeny jednej witryny; sama gwiazdka * pasuje do każdego originu w internecie. W sam raz dla widżetu kalkulatora, źle dla wszystkiego, co przenosi sesję.

Co się dzieje, gdy ustawione są oba nagłówki

Większość witryn podczas migracji wysyła oba, a pierwszeństwo jest zdefiniowane: jeśli obecne jest frame-ancestors, przeglądarka je egzekwuje i ignoruje X-Frame-Options. Mówi o tym wprost specyfikacja CSP Level 2, a Chrome, Firefox, Safari i Edge stosują się do niej.

Dzięki temu bezpieczna ścieżka migracji jest prosta. Zostaw X-Frame-Options: SAMEORIGIN dla garstki starych klientów sprzed CSP 2, dodaj obok Content-Security-Policy: frame-ancestors 'self', a każda nowoczesna przeglądarka użyje reguły CSP. Kłopoty zaczynają się tylko wtedy, gdy każesz im mówić różne rzeczy — na przykład X-Frame-Options: DENY obok frame-ancestors * — wtedy nowoczesne przeglądarki zezwalają na osadzanie, a blokują je tylko bardzo stare. Zachowaj spójność, a nie będzie czego debugować.

Krok 1: znajdź nagłówek, który cię blokuje

Zanim cokolwiek zmienisz, ustal, która polityka obowiązuje. Konsola przeglądarki już podała nazwę nagłówka; curl pokaże dokładną wartość wysyłaną przez serwer. Ten przykład używa witryny z omówionego dalej przykładu praktycznego — jej główne strony są celowo zablokowane:

bash
curl -sI https://emicalcs.com/ | grep -i -E 'x-frame-options|content-security-policy'

# Output:
# x-frame-options: SAMEORIGIN

Wracają trzy możliwości:

  • x-frame-options: SAMEORIGIN lub DENY — klasyczna polityka osadzania. Zdjąć ją może tylko zmiana na tamtym serwerze.
  • content-security-policy: … frame-ancestors … — polityka CSP. Przeczytaj listę źródeł; jeśli Twojego originu na niej nie ma, jesteś zablokowany. Dyrektywa bywa ukryta w długim nagłówku CSP obok script-src i podobnych, więc przeczytaj całą wartość.
  • Żaden z nagłówków: osadzana witryna zezwala na ramkę, a „odrzucił połączenie” ma inną przyczynę: przekierowanie na stronę, która jest zablokowana (często strona logowania; podążaj za nim przez curl -sIL), albo nagłówek Cross-Origin-Embedder-Policy: require-corp na Twojej własnej stronie, który blokuje każdą ramkę niewyrażającą zgody i pokazuje to samo szare pole.

Krok 2: zdecyduj, kto może osadzać stronę

Wszystko dalej zależy od jednego pytania: czy kontrolujesz osadzaną witrynę?

Jeśli nie, zatrzymaj się tutaj. Nie ma obejścia po stronie klienta — ani atrybutu sandbox, ani innego src, ani flagi przeglądarki, którą mieliby Twoi odwiedzający. Właściciel witryny zdecydował, że jego strona nie ma być osadzana, a przeglądarka egzekwuje tę decyzję w jego imieniu. Możesz zamiast tego umieścić link do strony, zapytać właściciela, czy udostępnia endpoint do osadzania, albo skorzystać z oficjalnego widżetu, jeśli istnieje. Wiele usług blokujących główną witrynę takie rozwiązanie oferuje; przykład w następnej sekcji pokazuje, jak to wygląda.

Jeśli ją kontrolujesz, oprzyj się pokusie usunięcia nagłówka z całej witryny. To na nowo otwiera clickjacking na każdej stronie — także tych z sesjami i formularzami — by naprawić osadzanie na jednej. Zamiast tego zdecyduj, które konkretnie ścieżki mają być osadzane i przez kogo:

SytuacjaNagłówek do wysłania na tej ścieżce
Wewnętrzne narzędzie osadzane przez Twój własny panelframe-ancestors 'self' (plus X-Frame-Options: SAMEORIGIN)
Widżet osadzany przez kilku znanych partnerówframe-ancestors 'self' https://partner-a.com https://partner-b.com
Publiczny widżet do osadzania (kalkulator, mapa, odtwarzacz)frame-ancestors * tylko na ścieżce embed
Cała resztaframe-ancestors 'self' plus X-Frame-Options: SAMEORIGIN

Właściwy wzorzec: zablokuj witrynę, otwórz jedną ścieżkę embed

Najczystszy przykład tego wzorca z prawdziwego świata, jaki znam, to EMICalcs — darmowy zbiór 45 indyjskich kalkulatorów finansowych (rata kredytu EMI, SIP, podatek dochodowy, GST i inne), które inne witryny mogą osadzać. Główna witryna jest zablokowana; dedykowana ścieżka /embed/ jest otwarta. Porównaj nagłówki obu:

bash
# The calculator page a visitor normally uses — locked
curl -sI https://emicalcs.com/income-tax-calculator/ | grep -i -E 'x-frame|frame-ancestors'
# x-frame-options: SAMEORIGIN

# The same calculator on its embed path — open to any site
curl -sI https://emicalcs.com/embed/income-tax-calculator/ | grep -i -E 'x-frame|frame-ancestors'
# content-security-policy: frame-ancestors *

Zwróć uwagę, czego odpowiedź embed nie zawiera: na /embed/ nie ma w ogóle nagłówka X-Frame-Options. To celowe. Ponieważ dyrektywa CSP ma pierwszeństwo, pozostawienie SAMEORIGIN byłoby w nowoczesnych przeglądarkach nieszkodliwe — ale zmyliłoby kolejnego inżyniera czytającego nagłówki i nadal blokowałoby nieliczne stare klienty rozumiejące wyłącznie X-Frame-Options. Ścieżka embed wysyła jedną politykę i mówi dokładnie jedną rzecz.

Cała reszta witryny zachowuje SAMEORIGIN, więc strona główna, artykuły i pełne strony kalkulatorów pozostają chronione przed clickjackingiem. Wersje do osadzania to odchudzone dokumenty bez nawigacji i bez niczego, co warto przechwytywać, dlatego otwarcie ich na * jest rozsądnym kompromisem.

Ich przewodnik po osadzaniu warto przeczytać jako wzór, jeśli publikujesz własny widżet: jeden stały adres URL na kalkulator, stała wysokość pasująca do większości z nich, loading="lazy" we fragmencie kodu i obietnica, że wycofane adresy embed będą przekierowywać zamiast zwracać 404 — bo każda witryna, która Cię osadza, ufa, że Twój adres URL będzie dalej działał.

Nginx (i pułapka add_header)

Oto ten sam wzorzec w trzech miejscach, w których większość witryn ustawia nagłówki. W każdym przypadku reguła dla całej witryny blokuje wszystko, a węższa reguła nadpisuje ją dla ścieżki embed. Najpierw Nginx, bo ma pułapkę:

nginx
# Site-wide default (server block)
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "frame-ancestors 'self'" always;
add_header X-Content-Type-Options "nosniff" always;

location /embed/ {
    # WARNING: any add_header here stops ALL add_header inheritance from the
    # server block — re-declare every header you still want on this path.
    add_header Content-Security-Policy "frame-ancestors *" always;
    add_header X-Content-Type-Options "nosniff" always;
}

Ten komentarz nie jest do pominięcia. add_header nie jest scalane między poziomami: gdy tylko blok location zadeklaruje jakikolwiek add_header, przestaje dziedziczyć wszystkie add_header z bloku server. Zespoły regularnie tracą w ten sposób nagłówki HSTS czy nosniff na ścieżce embed i dowiadują się o tym dopiero miesiące później ze skanu bezpieczeństwa. Zadeklaruj ponownie to, czego potrzebujesz, i pomiń X-Frame-Options w bloku embed, aby ścieżka wysyłała jedną jasną politykę. Od nginx 1.29.3 add_header_inherit merge może włączyć dziedziczenie z powrotem (nginx), ale nie używaj go w bloku embed: dodałoby ogólnowitrynowe X-Frame-Options i frame-ancestors 'self' z powrotem do tej ścieżki, a przy dwóch nagłówkach CSP przeglądarka egzekwuje oba, więc ramka pozostaje zablokowana.

Apache

Dyrektywa Header w Apache scala się między zakresami, więc możesz ustawić wartość domyślną raz i zmienić tylko dwa nagłówki na ścieżce embed:

apache
# Site-wide default (needs mod_headers)
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Content-Security-Policy "frame-ancestors 'self'"

<LocationMatch "^/embed/">
    Header always unset X-Frame-Options
    Header always set Content-Security-Policy "frame-ancestors *"
</LocationMatch>

Next.js i Express

W Next.js nagłówki deklaruje się w next.config.js. Gdy dwie reguły pasują do tej samej ścieżki i ustawiają ten sam nagłówek, wygrywa późniejsza — ale nagłówek ustawiony tylko przez wcześniejszą regułę nadal obowiązuje. Aby X-Frame-Options w ogóle nie trafiał na ścieżkę embed, wyłącz go z reguły dla całej witryny za pomocą negatywnego wyprzedzenia (negative lookahead):

javascript
// next.config.js
module.exports = {
  async headers() {
    return [
      {
        // Every path EXCEPT /embed/... gets the locked defaults
        source: '/((?!embed/).*)',
        headers: [
          { key: 'X-Frame-Options', value: 'SAMEORIGIN' },
          { key: 'Content-Security-Policy', value: "frame-ancestors 'self'" },
        ],
      },
      {
        source: '/embed/:path*',
        headers: [
          { key: 'Content-Security-Policy', value: 'frame-ancestors *' },
        ],
      },
    ]
  },
}

W Express popularny middleware helmet domyślnie wysyła już X-Frame-Options: SAMEORIGIN oraz CSP zawierające frame-ancestors 'self'. Zamontuj go globalnie, a następnie w routerze embed wywołaj res.removeHeader('X-Frame-Options') i ustaw Content-Security-Policy na frame-ancestors * przed renderowaniem. Kolejność ma znaczenie: Twoje nadpisanie musi działać po helmet, inaczej helmet je nadpisze.

Bezpieczne osadzanie widżetu zewnętrznego dostawcy

Teraz druga strona barykady: to Ty osadzasz, dostawca ma otwartą ścieżkę embed, a Ty chcesz, by ramka zachowywała się poprawnie. Dobrze zrobiony fragment widżetu wygląda tak; jest to ten, który EMICalcs publikuje na każdej stronie kalkulatora:

html
<iframe src="https://emicalcs.com/embed/income-tax-calculator/"
        title="Income Tax Calculator FY 2026-27 — EMICalcs"
        width="100%" height="760"
        style="max-width:680px;border:0;border-radius:16px;overflow:hidden"
        loading="lazy"></iframe>
<p>Free <a href="https://emicalcs.com/income-tax-calculator/" target="_blank" rel="noopener">Income Tax Calculator FY 2026-27</a> by EMICalcs</p>

Każdy atrybut ma swoje zadanie:

  • title — nadaje ramce nazwę dla czytników ekranu. Bez niego technologia asystująca ogłasza tylko „ramka”, a audyty dostępności oblewają stronę.
  • width="100%" z max-width w style — wypełnia wąskie kolumny na telefonach, nie wylewając się poza szerokie na komputerach.
  • Stała height — strona osadzająca nie może odczytać wysokości zawartości ramki z innego originu, więc wpisana na stałe wartość to jedyny sposób, by uniknąć przesunięcia układu podczas ładowania. Ustaw ją raz dla każdego widżetu.
  • loading="lazy" — odkłada żądanie do chwili, gdy ramka zbliży się do widocznego obszaru, więc widżet na dole długiego artykułu nic nie kosztuje przy pierwszym ładowaniu.
  • border:0; border-radius; overflow:hidden — usuwa domyślną wciętą ramkę i przycina rogi, aby widżet wyglądał naturalnie na Twojej stronie.

Linia z podaniem źródła znajduje się celowo poza iframe. Wszystko wewnątrz ramki należy do osadzonego dokumentu — jego linki to linki dostawcy, pod adresem dostawcy — więc link wewnątrz ramki nie jest częścią Twojej strony w oczach Twoich czytelników ani robota wyszukiwarki. Jeśli chcesz wskazać źródło albo dostawca o to prosi, umieść zwykły akapit obok ramki we własnym HTML, jak wyżej.

sandbox: co zezwolić dla widżetu kalkulatora

Atrybut sandbox to własna ochrona osadzającego. Dodaj go bez wartości, a osadzona strona nie dostaje niczego: żadnych skryptów, formularzy, wyskakujących okien, nawigowania Twoim oknem, a do tego dostaje nieprzejrzysty origin, który wyłącza jej własne ciasteczka i pamięć. Potem dodaj z powrotem tylko to, czego widżet potrzebuje:

html
<iframe src="https://emicalcs.com/embed/loan-emi-calculator/"
        sandbox="allow-scripts allow-forms allow-same-origin allow-popups"
        title="Loan EMI Calculator — EMICalcs"
        width="100%" height="760" loading="lazy"></iframe>

allow-scripts i allow-forms to minimum dla wszystkiego, co interaktywne. allow-same-origin pozwala osadzonej stronie zachować własny origin, dzięki czemu działają jej pamięć i ciasteczka — przy osadzeniu z innego originu jest to bezpieczne, bo „jej origin” to nadal origin dostawcy, a nie Twój. allow-popups jest potrzebne tylko wtedy, gdy widżet otwiera linki w nowej karcie, jak robi to link z podaniem źródła wewnątrz osadzenia EMICalcs.

Czego nie należy robić, to łączyć allow-scripts z allow-same-origin w ramce wskazującej na Twój własny origin. Osadzony skrypt może wtedy sięgnąć wyżej i usunąć własny atrybut sandbox, a Ty nie zabezpieczyłeś niczego.

Dlaczego po stronie osadzającego nie ma rozwiązania

Wyszukanie „iframe odrzucił połączenie” pokazuje trzy popularne obejścia. Wszystkie są złe i warto dokładnie wiedzieć dlaczego.

  • Rozszerzenia przeglądarki usuwające X-Frame-Options — działają tylko w Twojej przeglądarce. Każdy odwiedzający Twoją witrynę nadal widzi szare pole, a Ty tymczasem wyłączasz ochronę przed clickjackingiem na każdej odwiedzanej witrynie.
  • Przepuszczanie strony przez własny serwer — pobierasz HTML zewnętrznej witryny po stronie serwera i serwujesz go ze swojego originu, więc nagłówek nigdy nie dociera do przeglądarki. To psuje linki względne, ciasteczka, logowania i wszystko oparte na skryptach; bierzesz na siebie odpowiedzialność za ich treść; i zwykle łamie to regulamin dostawcy. Witryny, które to wykryją, blokują adres IP Twojego serwera.
  • Usługi zrzutów ekranu lub „web-to-image” — dostajesz statyczny obrazek, a nie działającą stronę. Dobre na miniaturę podglądu, bezużyteczne dla kalkulatora.

Nagłówek to oświadczenie właściciela witryny, egzekwowane przez przeglądarkę, o tym, co wolno robić z jego stroną. Jedyne uczciwe drogi to dwie już omówione: właściciel otwiera ścieżkę embed albo Ty linkujesz na zewnątrz. Dostawcy, którzy chcą być osadzani, ułatwiają to — dobrzy publikują fragment kodu, utrzymują stały adres URL i otwierają tylko ścieżkę, którą bezpiecznie otworzyć.

Sprawdzaj nagłówki po każdej zmianie

Nagłówki osadzania ustawia się w pliku konfiguracyjnym i zapomina o nich, a CDN, reverse proxy lub wtyczka bezpieczeństwa z dobrymi intencjami mogą je w każdej chwili dodać, usunąć lub zduplikować. Po wdrożeniu sprawdź je spoza własnej sieci:

bash
# The locked pages should show SAMEORIGIN and/or frame-ancestors 'self'
curl -sI https://example.com/ | grep -i -E 'x-frame-options|content-security-policy'

# The embed path should show ONLY frame-ancestors, with the sources you intended
curl -sI https://example.com/embed/widget/ | grep -i -E 'x-frame-options|content-security-policy'

# Error pages should carry the header too
curl -sI https://example.com/this-does-not-exist | grep -i -E 'x-frame-options|content-security-policy'

Potem przetestuj to tak, jak robi to przeglądarka: umieść adres embed w <iframe> na stronie serwowanej z innego originu — lokalna strona file:// się nie nadaje, bo przeglądarki traktują ją specjalnie — i otwórz konsolę. Czyste załadowanie oznacza brak linii Refused to display lub Refused to frame.

Jeśli witryna stoi za Cloudflare lub innym CDN, kopia z pamięci podręcznej albo reguła na brzegu może mieć inne nagłówki niż Twój serwer źródłowy. Checker nagłówków HTTP pobiera adres z naszego serwera, spoza Twojej przeglądarki i sieci, i pokazuje X-Frame-Options oraz Content-Security-Policy wraz z werdyktem dotyczącym osadzania (SAMEORIGIN lub DENY, przez CSP frame-ancestors, lub niewysłane): to szybki sposób na wychwycenie reguły CDN, która po cichu przywróciła SAMEORIGIN na Twojej ścieżce embed.

Objaw → przyczyna → rozwiązanie

Użyj tego, by przejść od razu do rozwiązania zamiast czytać cały poradnik od nowa:

Co widziszCo to oznaczaRozwiązanie
Szare pole „odrzucił połączenie”, ale adres ładuje się w karcieX-Frame-Options lub frame-ancestors zablokowało ramkęZmienić to może tylko właściciel witryny — użyj jego ścieżki embed lub linkuj na zewnątrz
Konsola: Refused to display … 'X-Frame-Options' to 'sameorigin'Klasyczny nagłówek; dozwolone tylko ramki z tego samego originuNa osadzanej witrynie otwórz ścieżkę embed z frame-ancestors * lub listą partnerów
Konsola: Refused to frame … violates … frame-ancestorsLista dozwolonych CSP nie zawiera Twojego originuDodaj swój origin do źródeł frame-ancestors osadzanej witryny
Działało wczoraj, dziś nie działa, bez zmian w kodzieCDN lub wtyczka bezpieczeństwa zaczęły wstrzykiwać SAMEORIGINSprawdź przez curl; wyłącz ścieżkę embed w regule CDN lub wtyczki
Twój własny panel nie może osadzić Twojej własnej stronyNiezgodność subdomeny lub schematu (www a domena bez www, http a https)Użyj frame-ancestors 'self' https://example.com https://*.example.com (symbol wieloznaczny nie obejmuje samej domeny)
Embed działa, ale HSTS lub nosniff zniknęły na tej ścieżceDziedziczenie add_header w Nginx je odrzuciłoZadeklaruj ponownie każdy nagłówek wewnątrz bloku location
ERR_CONNECTION_REFUSED po bezpośrednim otwarciu adresuPrawdziwa awaria sieci, a nie polityka osadzaniaZobacz ERR_CONNECTION_REFUSED i port checker

Jeśli adres zawodzi także w osobnej karcie, ramka jest fałszywym tropem. Przejdź warstwy sieci po kolei — DNS, port, TLS, nagłówki — według poradnika testowania publicznego endpointu, zanim ruszysz jakiekolwiek nagłówki osadzania.

Zobacz wszystkie nagłówki bezpieczeństwa wysyłane przez adres URL

Sprawdź X-Frame-Options, Content-Security-Policy, HSTS i resztę dla dowolnej strony, pobrane z naszych serwerów, aby rozszerzenie przeglądarki lub proxy w Twojej sieci nie zmieniło tego, co widzisz. Za darmo, bez rejestracji.

Źródła

  1. RFC 7034: HTTP Header Field X-Frame-Options, IETF
  2. CSP Level 3: frame-ancestors, W3C
  3. HTML Standard: the X-Frame-Options header, WHATWG
  4. ngx_http_headers_module (add_header, add_header_inherit), nginx

Najczęściej zadawane pytania

Czy X-Frame-Options jest przestarzały?

Funkcjonalnie zastąpiła go dyrektywa CSP frame-ancestors, którą przeglądarki traktują priorytetowo, gdy obecne są oba nagłówki. Nie jest jednak bezużyteczny: każda aktualna przeglądarka nadal honoruje X-Frame-Options, gdy frame-ancestors nie ma, a nagłówek chroni niewielką liczbę starych klientów sprzed CSP Level 2. Praktyczna rada: wysyłaj oba i zachowaj spójność — SAMEORIGIN obok frame-ancestors 'self'.

Co oznacza X-Frame-Options: DENY?

DENY mówi przeglądarce, że strony nigdy nie wolno renderować wewnątrz elementu frame, iframe, embed ani object na żadnej stronie — także na stronach tej samej witryny. To najsurowsze ustawienie i właściwe dla formularzy logowania, stron płatności i paneli administracyjnych, gdzie nie ma żadnego uzasadnionego powodu do osadzania, a clickjacking byłby poważny.

Jaka jest różnica między DENY a SAMEORIGIN?

DENY blokuje każde osadzanie. SAMEORIGIN pozwala osadzać tylko stronom z dokładnie tego samego originu — ten sam schemat, host i port — i blokuje wszystkich pozostałych. SAMEORIGIN to rozsądne ustawienie domyślne dla całej witryny, bo powstrzymuje clickjacking z zewnątrz, a Twoja witryna może nadal osadzać własne strony; DENY jest dla garstki stron, które nigdy nie powinny być osadzane.

Jak obejść X-Frame-Options, aby osadzić witrynę?

Ze strony osadzającego się nie da. Nagłówek ustawia witryna, którą próbujesz osadzić, a egzekwują go przeglądarki Twoich odwiedzających; żaden atrybut iframe, flaga sandbox ani sztuczka po stronie klienta tego nie zmieni. Rozszerzenia usuwające nagłówek działają tylko w Twojej przeglądarce, a przepuszczanie strony przez Twój serwer psuje ją i zwykle łamie regulamin dostawcy. Uczciwe drogi to oficjalny endpoint do osadzania, jeśli dostawca go oferuje, albo link do strony.

Jak ustawić X-Frame-Options na DENY?

Dodaj go jako nagłówek odpowiedzi HTTP na poziomie serwera WWW, aby obejmował każdą stronę, w tym strony błędów. W Nginx: add_header X-Frame-Options "DENY" always; w Apache: Header always set X-Frame-Options "DENY". Połącz go z Content-Security-Policy: frame-ancestors 'none' dla nowoczesnych przeglądarek. Nie da się go ustawić znacznikiem meta — działa tylko nagłówek HTTP.

Dlaczego mój iframe pokazuje „odrzucił połączenie”, skoro witryna działa w nowej karcie?

Bo z połączeniem wszystko jest w porządku. Witryna zwróciła stronę normalnie, ale z nagłówkiem X-Frame-Options lub frame-ancestors zabraniającym renderowania jej wewnątrz innej strony. Chrome pokazuje dla tego to samo szare pole „odrzucił połączenie” co przy prawdziwej awarii sieci, dlatego jest to mylące; wskazówką jest komunikat w konsoli „Refused to display … in a frame”.

Czy mogę ustawić X-Frame-Options lub frame-ancestors w znaczniku meta?

Nie. Oba muszą być dostarczone jako nagłówki odpowiedzi HTTP. Przeglądarka musi znać politykę osadzania, zanim w ogóle zdecyduje, czy renderować dokument, a znacznik meta przychodzi za późno. frame-ancestors jest jawnie ignorowane w CSP przez meta http-equiv, a X-Frame-Options w znaczniku meta jest ignorowany przez każdą przeglądarkę.

Czy frame-ancestors * sprawia, że witryna jest niebezpieczna?

Tylko dla odpowiedzi, które go wysyłają. Nagłówek jest oceniany per dokument, więc wysyłanie frame-ancestors * na dedykowanej ścieżce /embed/ pozostawia resztę witryny w pełni chronioną przez SAMEORIGIN lub frame-ancestors 'self'. Ryzyko ogranicza się do tego, co atakujący mógłby zyskać, osadzając samą stronę embed — a dla bezstanowego widżetu bez ciasteczka sesji i bez wrażliwych akcji jest to nic.

Dlaczego X-Frame-Options: ALLOW-FROM nie działa?

ALLOW-FROM wdrożyły tylko Internet Explorer, oryginalny Edge (wersje od 12 do 18) i Firefox, który usunął je w wersji 70. Chrome i Safari nigdy tego nie obsługiwały. Nowoczesne przeglądarki uznają nagłówek zawierający ALLOW-FROM za nieprawidłowy i ignorują go całkowicie, przez co stronę może osadzić każdy. Użyj dyrektywy CSP frame-ancestors, która obsługuje prawdziwą listę dozwolonych originów.

Który nagłówek wygrywa, jeśli ustawione są X-Frame-Options i frame-ancestors?

frame-ancestors. Standard HTML każe przeglądarkom ignorować X-Frame-Options, gdy egzekwowane CSP zawiera frame-ancestors (CSP Level 2 już to zalecało), i robią tak wszystkie aktualne przeglądarki; polityka Report-Only się nie liczy. Tylko klienty zbyt stare, by rozumieć CSP, wracają do X-Frame-Options, dlatego nadal warto go wysyłać — o ile oba nagłówki są zgodne.

O autorze

Shaik Vahid

Założyciel DNS Robot. Vahid przez lata pracował we wsparciu hostingu, naprawiając klientom domeny, rekordy DNS, certyfikaty SSL i dostarczanie poczty. Tworzy narzędzia DNS Robot i pisze te poradniki na podstawie błędów, które ludzie naprawdę widzą.

Sprawdzanie Nagłówków HTTP

Analizuj nagłówki odpowiedzi HTTP i ocenę nagłówków bezpieczeństwa