Tous les articles

HSTS contre upgrade-insecure-requests, faut-il les deux

CentralCSP Team ·

HSTS force chaque navigation vers votre hôte à passer en HTTPS. La directive CSP upgrade-insecure-requests réécrit les URL de sous-ressources non sécurisées à l'intérieur de la page, tiers compris. Ils sont complémentaires, pas des alternatives, et un site qui ne veut aucune requête non sécurisée envoie les deux.

On traite souvent ces deux mécanismes comme deux façons de faire la même chose, forcer HTTPS. Ce n'est pas le cas. HTTP Strict Transport Security (HSTS) contrôle comment le navigateur joint votre hôte. Il fait passer chaque navigation vers votre domaine en HTTPS, y compris un clic depuis un lien externe, une fois que le navigateur connaît l'hôte. La directive CSP upgrade-insecure-requests contrôle les URL que vos pages chargent une fois ouvertes. Elle réécrit les requêtes de sous-ressources non sécurisées à l'intérieur de votre document, y compris vers des hôtes tiers que HSTS ne peut pas atteindre. Ils ne se recouvrent presque nulle part, et c'est pourquoi vous envoyez les deux.

HSTS vs upgrade-insecure-requests, les deux moitiés du HTTPS forcé

Pensez à deux questions distinctes. Premièrement, quand un navigateur va sur votre site, se connecte-t-il en HTTPS ou commence-t-il par un saut en clair qu'un attaquant peut retirer ? C'est une question de transport, sur le fait de joindre votre hôte. Deuxièmement, une fois votre page chargée, les ressources qu'elle tire, les vôtres et celles des tiers, se chargent-elles toutes en HTTPS, ou une image http:// égarée déclenche-t-elle du mixed content ? C'est une question de contenu, sur ce qui s'exécute à l'intérieur de la page.

HSTS possède la première question. upgrade-insecure-requests possède la seconde. Ni l'un ni l'autre ne répond à celle de l'autre.

HSTS sécurise comment le navigateur joint votre hôte

HSTS est un header de réponse, défini par le RFC 6797, qu'un site envoie en HTTPS pour dire au navigateur « pendant les N prochaines secondes, ne parle à cet hôte qu'en HTTPS ». Le navigateur met votre domaine en cache comme hôte HSTS connu, réécrit toute URL http:// le concernant en https:// avant que la requête ne quitte la machine, et échoue durement sur les erreurs de certificat sans possibilité de passer outre.

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

max-age est la durée de vie requise en secondes, et chaque réponse HTTPS qui porte le header remet le compteur à zéro, donc les visiteurs réguliers ne sortent jamais de la politique. Envoyer max-age=0 en HTTPS supprime l'entrée en cache, c'est la porte de sortie si vous avez posé le header par erreur. Le navigateur ignore complètement le header quand il arrive en HTTP simple ; sinon, un attaquant assis sur le saut non sécurisé pourrait poser ou effacer votre politique à votre place. HSTS ne fonctionne aussi que pour les noms de domaine, une adresse IP ne peut pas être un hôte HSTS connu.

includeSubDomains étend la politique à chaque sous-domaine, vers le bas depuis l'hôte qui l'a envoyée. La justification de l'OWASP mérite d'être gardée en tête : un cookie de session scopé au domaine parent peut être manipulé depuis un sous-domaine non sécurisé, donc couvrir tout l'arbre est ce qui protège réellement la session.

Ce que cela stoppe, c'est le protocol downgrade, aussi appelé SSL stripping : un attaquant sur le réseau qui transforme votre connexion HTTPS en une connexion en clair, ou usurpe un certificat. Comme le navigateur passe en HTTPS avant que la requête ne parte et refuse de laisser l'utilisateur ignorer une erreur TLS, il n'y a aucun saut non sécurisé à retirer. La seule faille est la toute première requête vers un hôte que le navigateur n'a jamais vu, le RFC 6797 l'appelle la vulnérabilité MITM de bootstrap, et le preload la referme.

La limite de portée clé : HSTS ne couvre que les hôtes que vous contrôlez et qui envoient le header. Il n'a rien à dire sur un domaine tiers depuis lequel votre page charge un script.

Le preload, et pourquoi on y monte par étapes

preload est le signal d'opt-in pour la liste de preload des navigateurs sur hstspreload.org, qui embarque la politique dans le navigateur lui-même pour qu'une toute première visite soit elle aussi protégée. Il ne fait pas partie du RFC 6797 ; c'est un mécanisme de facto géré via la liste de Chrome que les autres navigateurs consomment. La soumission exige un certificat valide, une redirection HTTP vers HTTPS sur le même hôte, chaque sous-domaine servi en HTTPS, un max-age d'au moins un an, à la fois includeSubDomains et preload dans le header, et le header présent sur la redirection HTTPS elle-même.

Ces exigences sont aussi le piège. Preload plus includeSubDomains force HTTPS sur chaque sous-domaine, y compris les internes ou les oubliés, pour chaque utilisateur de chaque navigateur qui embarque la liste. Revenir en arrière est lent : vous retirez la directive preload, demandez le retrait, puis attendez, car le retrait met 6 à 12 semaines à atteindre la plupart des utilisateurs de Chrome et plus longtemps pour les autres navigateurs, parce que la liste est codée en dur dans les versions des navigateurs. Montez donc par étapes, comme hstspreload.org le prescrit lui-même : max-age=300, puis 604800 (une semaine), puis 2592000 (un mois), en laissant s'écouler chaque max-age avant de l'augmenter, et seulement ensuite engagez-vous sur la valeur d'un an ou plus avec preload.

upgrade-insecure-requests corrige le mixed content dans vos pages

upgrade-insecure-requests est une directive de politique de sécurité du contenu (CSP). Quand une page la porte, le navigateur réécrit chaque URL de sous-ressource http:// non sécurisée de ce document en https:// avant que la requête ne soit faite, first-party comme tiers : images, scripts, styles, frames, fetch. Les navigations sont traitées plus étroitement. Le navigateur passe en HTTPS les navigations vers votre propre hôte, les soumissions de formulaires et les frames imbriquées, mais laisse tranquilles les liens vers des sites tiers ; les auteurs de la spec ont jugé que réécrire l'URL de navigation de quelqu'un d'autre porte un risque de casse trop élevé, donc un simple lien vers http://other.example/ est demandé tel quel.

Content-Security-Policy: upgrade-insecure-requests

Pour les sous-ressources, la portée est l'opposé de HSTS. Elle s'applique à chaque URL que votre page charge, y compris des hôtes tiers que vous ne possédez pas et pour lesquels vous ne pourriez jamais envoyer HSTS. Si votre page référence http://widgets.example.net/w.js, HSTS sur votre propre domaine n'y fait rien, mais upgrade-insecure-requests la réécrit en https:// avant que la requête ne soit faite. C'est ainsi que vous éliminez les avertissements de mixed content sur une page qui tire de nombreuses origines sans éditer chaque URL à la main.

La réécriture est aveugle, et il n'y a pas de repli. Si une ressource n'est pas réellement disponible en HTTPS, la requête passée en HTTPS échoue et le navigateur ne réessaie jamais en HTTP. Le prérequis de déploiement est que tout ce que vos pages chargent soit servi à la même URL sur les deux schémas.

Il est aussi utile de connaître la base de départ que vous améliorez. Les navigateurs modernes passent déjà automatiquement en HTTPS ce que la spec Mixed Content appelle le contenu upgradable : les images, l'audio et la vidéo chargés via src, plus les images CSS. Tout le reste du contenu non sécurisé, scripts, feuilles de style, iframes, appels fetch et XHR, polices, est bloqué net plutôt que chargé. Donc sans la directive, un script http:// n'est pas un trou silencieux, c'est une page cassée. Ce que upgrade-insecure-requests ajoute, c'est que ce contenu blocable est passé en HTTPS au lieu d'être bloqué, et la console arrête de se remplir d'avertissements de mixed content. Un cas limite survit dans les deux situations : le mixed content adressé par une adresse IP est bloqué, pas passé en HTTPS. La directive sœur dépréciée, block-all-mixed-content, prenait l'approche inverse et bloquait ce contenu au lieu de le passer en HTTPS.

Pourquoi aucun ne remplace l'autre

Ils résolvent des problèmes différents, donc l'un ne peut pas se substituer à l'autre, et les specs le disent elles-mêmes. MDN énonce que upgrade-insecure-requests « ne remplace pas le header Strict-Transport-Security (HSTS) », et la spec du W3C est encore plus directe : la directive « ne déprécie pas, ne remplace pas et ne réduit en aucune façon la valeur de » HSTS. Passer en HTTPS les URL à l'intérieur d'une page ne fait rien pour la navigation de premier niveau qui a chargé la page en premier lieu. Un utilisateur qui clique un lien http:// vers votre site depuis ailleurs a besoin de HSTS pour être passé en HTTPS avant que la connexion ne soit faite ; la directive CSP ne s'exécute jamais, parce que la page n'est pas encore chargée.

Prenez-le dans l'autre sens et la faille est tout aussi nette. HSTS passe en HTTPS les requêtes vers votre propre hôte, donc une référence http:// égarée vers votre propre domaine est corrigée, mais une sous-ressource tierce http:// est entièrement hors de sa portée. Seul upgrade-insecure-requests l'atteint.

Ce que chacun couvre

Strict-Transport-Security (HSTS)upgrade-insecure-requests
CoucheTransport, comment le navigateur joint votre hôteContenu, URL chargées dans votre page
PortéeVotre hôte et les sous-domaines que vous couvrezChaque sous-ressource, hôtes tiers inclus
Navigation de premier niveau depuis un lien externePassée en HTTPS, une fois connu ou preloadéNon couverte
Mixed content tiersNon couvertPassé en HTTPS
MenaceSSL stripping, protocol downgradeMixed content sur la page
En cas d'échecErreurs de certificat fatales, impossible de passer outreLa requête passée en HTTPS échoue, pas de repli HTTP
Où il est poséHeader de réponse Strict-Transport-SecurityDirective CSP
SupportTous les moteurs modernes (Blink, Gecko, WebKit)Tous les moteurs modernes (Blink, Gecko, WebKit)

Recommandation

Envoyez les deux. Posez HSTS avec un long max-age et includeSubDomains, montez le max-age par étapes, et n'ajoutez preload qu'une fois chaque sous-domaine confirmé en HTTPS, pour que les navigations vers votre hôte soient verrouillées. Ajoutez upgrade-insecure-requests à votre politique de sécurité du contenu pour que chaque sous-ressource, y compris les tiers que HSTS ne peut pas atteindre, se charge en HTTPS, après avoir vérifié que ces ressources existent réellement en HTTPS, car un passage en HTTPS qui échoue ne retombe pas en HTTP. Ensemble, ils couvrent tout le trajet : HSTS amène le navigateur à votre hôte en sécurité, et la directive CSP garde en HTTPS tout ce que la page charge ensuite.

Pour confirmer que les deux sont présents et bien formés sur tout votre site, lancez le scanner de headers de sécurité gratuit de CentralCSP. Il signale un header HSTS manquant ou faible et montre si votre politique porte upgrade-insecure-requests.

La partie upgrade mérite aussi d'être surveillée après le scan. Une requête upgradée qui n'a pas de version HTTPS échoue purement et simplement, et rien dans la page ne vous dit quel tiers a cassé. Comme l'échec est une erreur réseau, un endpoint CentralCSP qui reçoit les reports NEL fait remonter ces hosts depuis le trafic réel, ce qui vous permet de trouver la ressource qui n'existe qu'en HTTP avant vos visiteurs.

FAQ

upgrade-insecure-requests remplace-t-il HSTS ?

Non. La directive ne réécrit que les URL à l'intérieur d'une page déjà chargée, donc elle ne peut pas passer en HTTPS la navigation qui amène un utilisateur sur votre site depuis un lien http:// ailleurs. MDN comme la spec du W3C disent de garder HSTS posé à côté ; la formulation de la spec est que la directive « ne déprécie pas, ne remplace pas et ne réduit en aucune façon la valeur de » HSTS.

Faut-il à la fois HSTS et upgrade-insecure-requests ?

Oui, ils couvrent deux moitiés différentes. HSTS fige la façon dont les navigateurs joignent votre hôte d'une visite à l'autre, et dès la première visite une fois preloadé, ce que la directive CSP ne pourra jamais faire. upgrade-insecure-requests passe en HTTPS les sous-ressources dans la page, y compris les hôtes tiers que votre header HSTS ne peut pas couvrir.

Comment upgrade-insecure-requests corrige-t-il le mixed content ?

Il dit au navigateur de réécrire chaque URL de sous-ressource http:// non sécurisée de la page, first-party comme tierce, en https:// avant que la requête ne soit faite, pour que les images, scripts, styles et frames se chargent de façon sécurisée au lieu d'être bloqués ou signalés comme mixed content. Il n'y a pas de repli : une ressource non disponible en HTTPS échoue, tout simplement.

Le preload HSTS est-il sans risque ?

Il est sans risque une fois chaque sous-domaine confirmé en HTTPS, mais il est presque irréversible à échelle humaine. Le preload exige un max-age minimum d'un an avec includeSubDomains, donc chaque sous-domaine, y compris les internes que vous avez oubliés, est forcé en HTTPS pour tous les utilisateurs. Le retrait de la liste met 6 à 12 semaines à atteindre la plupart des utilisateurs de Chrome, plus longtemps pour les autres navigateurs. Montez le max-age par étapes (300, puis 604800, puis 2592000) avant de vous engager.

Sources

Articles liés