Tous les articles

X-Frame-Options vs frame-ancestors, quel contrôle anti-clickjacking utiliser

CentralCSP Team ·

Les deux répondent à la même question : qui a le droit de placer votre page dans une frame, ce qui est le contrôle qui stoppe le clickjacking. La directive frame-ancestors de la politique de sécurité du contenu (CSP) est la version moderne et plus expressive ; X-Frame-Options est le header plus ancien, conservé pour les clients qui n'ont jamais implémenté la CSP. Envoyez les deux. Ils ne peuvent pas entrer en conflit : tout navigateur qui applique frame-ancestors doit, selon la spec CSP, ignorer X-Frame-Options, donc la directive CSP décide sur les navigateurs modernes et le header plus ancien couvre le reste.

La menace du clickjacking

Le clickjacking est une attaque où l'attaquant charge votre page dans une frame sur un site qu'il contrôle, généralement invisible ou transparente, et dépose son propre appât par-dessus. La victime croit cliquer sur un bouton de la page de l'attaquant, mais le clic atterrit sur votre page framée : confirmer un paiement, changer un réglage, approuver une permission. La défense est d'indiquer au navigateur quels sites, s'il y en a, peuvent framer votre page. Les deux contrôles ci-dessous font exactement cela.

X-Frame-Options, le header d'origine

X-Frame-Options est le header anti-clickjacking d'origine, documenté dans la RFC 7034. Il a deux valeurs qui fonctionnent encore.

  • DENY : aucun site ne peut framer la page. MDN précise que la page "cannot be loaded in any frame, regardless of origin", donc même votre propre site ne peut pas la framer.
  • SAMEORIGIN : seules les pages de la même origine peuvent la framer.
X-Frame-Options: DENY

Une nuance sur SAMEORIGIN. Les navigateurs modernes vérifient chaque ancêtre de la chaîne de frames, donc une frame same-origin imbriquée dans une page hostile reste bloquée. La RFC 7034 documentait que les premières implémentations ne vérifiaient que le contexte de navigation de premier niveau, ce qui laissait cette astuce d'imbrication ouverte ; les moteurs actuels la ferment.

Malgré son âge, le header n'est pas déprécié. En janvier 2025, les éditeurs de la spec HTML ont délibérément retiré les mots "legacy" et "obsoleted by" de sa description du header, et la spec CSP dit désormais que frame-ancestors "overrides" (l'emporte sur) X-Frame-Options plutôt que "obsoletes" (le rend obsolète). Tous les navigateurs l'appliquent encore, et rien ne prévoit de le retirer.

ALLOW-FROM est obsolète, et son échec laisse tout ouvert

Le header a eu autrefois une troisième valeur, ALLOW-FROM uri, censée autoriser une seule origine nommée. Elle a commencé comme une extension d'Internet Explorer, n'a jamais été implémentée uniformément (les mots mêmes de la RFC 7034), ne pouvait nommer qu'une seule origine sans wildcards, et Firefox, qui l'avait aussi livrée, l'a retirée en 2019 en pointant vers frame-ancestors comme remplacement.

Le point dangereux est sa façon d'échouer. Un navigateur qui ne reconnaît pas ALLOW-FROM traite la valeur comme invalide et ignore le header entier. Le message de console de Chromium le dit explicitement : "'ALLOW-FROM' is not a recognized directive. The header will be ignored." Un site qui envoie aujourd'hui uniquement X-Frame-Options: ALLOW-FROM https://partner.example.com n'a donc aucune restriction de framing dans les navigateurs modernes, ce qui revient à échouer en laissant tout ouvert (fail open) ; OWASP avertit qu'un site qui dépend de ALLOW-FROM n'a aucune défense anti-clickjacking dans les navigateurs qui ne le prennent pas en charge. Si vous devez autoriser des framers précis, c'est le travail de frame-ancestors.

Des valeurs contradictoires retombent sur deny

Un piège associé pour les infrastructures en couches. Si deux couches posent des valeurs différentes, disons que l'application envoie SAMEORIGIN et qu'un CDN ajoute DENY, le navigateur reçoit des valeurs X-Frame-Options contradictoires. Chromium traite cela comme une erreur et retombe sur deny, refusant d'afficher la page dans toute frame. Quand un framing same-origin qui fonctionnait casse après un changement d'infrastructure, les headers dupliqués sont la première chose à vérifier.

frame-ancestors, le remplaçant CSP

frame-ancestors est arrivée avec CSP Level 2 et c'est la directive qui reprend le travail. Au lieu de mots-clés fixes, elle prend une liste de sources : 'none', 'self', et n'importe quel nombre de sources d'hôte ou de schéma, y compris des sous-domaines wildcard comme *.example.com. C'est ce qui la rend plus expressive. Vous pouvez autoriser plusieurs partenaires de confiance à la fois, un arbre de sous-domaines entier, ou un schéma précis, ce que X-Frame-Options ne peut exprimer dans aucun de ces cas. Les nonces et les hashes ne s'y appliquent pas, puisqu'elle liste des parents de framing, pas des scripts. Le navigateur vérifie chaque ancêtre de la chaîne de frames, et si un seul d'entre eux échoue face à la liste, le chargement est annulé.

Content-Security-Policy: frame-ancestors 'self' https://partner.example.com

Deux règles de déploiement comptent plus que tout le reste.

D'abord, elle n'a pas de fallback : default-src ne la couvre pas. MDN est explicite : "a policy that declares default-src 'none' still allows the resource to be embedded by anyone" (une politique qui déclare default-src 'none' laisse quand même n'importe qui embarquer la ressource). Une CSP verrouillée sans ligne frame-ancestors explicite ne dit rien sur le framing, donc posez-la explicitement.

Ensuite, elle ne fonctionne qu'en header. La spec CSP exclut frame-ancestors (avec report-uri et sandbox) de la livraison via <meta http-equiv="Content-Security-Policy">, donc les navigateurs l'ignorent dans une balise meta. Posez-la au niveau du serveur ou du CDN. Si vous voulez la protection anti-clickjacking sans adopter une politique complète, voir comment poser frame-ancestors sans un header CSP complet.

Le support n'est plus une vraie contrainte : tous les moteurs modernes (Blink, Gecko, WebKit) appliquent frame-ancestors. La seule lacune notable est Internet Explorer, qui ne l'a jamais implémentée et ne comprend que X-Frame-Options.

La précédence, la règle exacte

La spec CSP tranche ce qui se passe quand les deux headers arrivent sur la même réponse : "If a resource is delivered with a policy that includes a directive named frame-ancestors and whose disposition is 'enforce', then the X-Frame-Options header MUST be ignored." Chromium l'implémente littéralement ; son ancestor throttle laisse frame-ancestors décider à la place de X-Frame-Options dès que la réponse porte une directive frame-ancestors appliquée.

Notez le mot "enforce". Seule une frame-ancestors appliquée désactive X-Frame-Options. Si vous testez la directive dans un header Content-Security-Policy-Report-Only, elle rapporte les violations mais ne supprime pas X-Frame-Options, donc le header plus ancien continue d'appliquer pendant que vous observez les reports. C'est exactement le comportement que vous voulez pendant un déploiement.

Cette règle de précédence est la raison pour laquelle envoyer les deux est sûr. Il n'existe aucun état où les deux se combattent : les navigateurs qui comprennent frame-ancestors l'utilisent et laissent tomber X-Frame-Options, et les clients plus anciens qui précèdent CSP Level 2 lisent X-Frame-Options seul. Les recommandations anti-clickjacking d'OWASP conseillent d'empiler des défenses indépendantes pour la même raison.

X-Frame-Options vs frame-ancestors côte à côte

CapacitéX-Frame-Optionsframe-ancestors
N'autoriser aucun framingDENY'none'
N'autoriser que le framing same-originSAMEORIGIN'self'
Autoriser des origines externes précisesImpossible (ALLOW-FROM obsolète, échec ouvert)'self' https://partner.example.com
Sous-domaines wildcardImpossible*.example.com
Ancêtres vérifiésToute la chaîne dans les navigateurs modernes (premier niveau seulement dans les premières implémentations)Toute la chaîne, tout ancêtre en échec annule le chargement
Définie via une balise metaSans effet (header seulement)Sans effet (header seulement)
Couverte par default-srcNon applicableNon, à poser explicitement
Quand les deux sont envoyésIgnoré là où une frame-ancestors appliquée est présenteL'emporte quand elle est appliquée
SupportTous les moteurs, y compris le vieil IETous les moteurs modernes (Blink, Gecko, WebKit) ; jamais IE

Recommandation

Déployez les deux, avec le réglage le plus strict que votre site permet. Utilisez 'none' et DENY si rien ne devrait jamais framer votre page ; utilisez 'self' et SAMEORIGIN si votre propre application se frame elle-même.

Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY

N'utilisez jamais ALLOW-FROM, et retirez-le partout où vous le trouvez, puisqu'il peut laisser une page sans aucune protection. Si vous devez autoriser des partenaires nommés, exprimez-le uniquement dans frame-ancestors et gardez X-Frame-Options à SAMEORIGIN ou DENY pour les vieux clients qui ne peuvent de toute façon pas lire l'allowlist. Vous pouvez confirmer que les deux headers sont présents et bien formés avec le scanner de headers de sécurité gratuit de CentralCSP.

Les deux headers diffèrent aussi après le déploiement, pas seulement en syntaxe : les violations de frame-ancestors sont signalées via la Reporting API, et les refus de X-Frame-Options ne le sont pas du tout. Pointer la CSP vers un endpoint CentralCSP signifie qu'un site partenaire qui ne peut soudain plus vous intégrer apparaît comme une csp-violation nommant l'origine qui encadre, plutôt que comme un ticket de support.

FAQ

X-Frame-Options est-il déprécié ?

Non. Ni MDN ni les données de compatibilité des navigateurs ne le marquent comme déprécié, et tous les navigateurs l'appliquent encore. En janvier 2025, les éditeurs de la spec HTML ont retiré les mots "legacy" et "obsoleted by" de sa description, et la spec CSP dit désormais que frame-ancestors "overrides" (l'emporte sur) le header plutôt que "obsoletes" (le rend obsolète). Seule la valeur ALLOW-FROM est obsolète. Continuez d'envoyer le header à côté de frame-ancestors.

Faut-il utiliser à la fois X-Frame-Options et frame-ancestors ?

Oui. Ils ne peuvent pas entrer en conflit, parce que la spec CSP exige que tout navigateur appliquant frame-ancestors ignore X-Frame-Options. La directive CSP décide donc sur tous les navigateurs modernes, et X-Frame-Options couvre les vieux clients comme Internet Explorer qui n'ont jamais implémenté la CSP. OWASP recommande d'empiler des défenses anti-clickjacking indépendantes, et envoyer les deux headers coûte une ligne chacun.

Pourquoi X-Frame-Options ALLOW-FROM ne fonctionne-t-il pas ?

Parce qu'il est obsolète. Il a commencé comme une extension d'Internet Explorer, n'a jamais été implémenté uniformément ailleurs, et Firefox l'a retiré en 2019. Pire, un navigateur qui ne reconnaît pas ALLOW-FROM ignore le header entier, donc la page échoue en laissant tout ouvert, sans aucune protection de framing. Remplacez-le par Content-Security-Policy: frame-ancestors 'self' https://partner.example.com.

Puis-je poser frame-ancestors dans une balise meta ?

Non. Les navigateurs ignorent frame-ancestors dans une balise meta, et une balise meta ne fonctionne pas non plus pour X-Frame-Options, donc posez les deux au niveau du serveur ou du CDN. Pour les détails et les options de déploiement, voir comment poser frame-ancestors sans un header CSP complet.

frame-ancestors en mode Report-Only désactive-t-il X-Frame-Options ?

Non. La règle de précédence ne s'applique qu'à une politique "whose disposition is 'enforce'" (dont la disposition est "enforce"), donc une directive frame-ancestors dans un header Content-Security-Policy-Report-Only rapporte les violations pendant que X-Frame-Options continue d'appliquer. Cela fait de Report-Only une façon sûre de tester une allowlist avant de l'activer.

Lectures liées

Sources