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. Les deux ne se recouvrent presque pas, et c'est bien pour ça qu'il faut envoyer les deux.

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

Posez-vous deux questions distinctes. D'abord, 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 : comment le navigateur joint votre hôte. Ensuite, une fois votre page chargée, les ressources qu'elle charge, les vôtres comme celles des tiers, arrivent-elles toutes en HTTPS, ou une image http:// égarée déclenche-t-elle du mixed content ? C'est une question de contenu : ce qui s'exécute à l'intérieur de la page.

HSTS répond à la première question, upgrade-insecure-requests à la seconde. Aucun des deux 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 placé sur le saut non sécurisé pourrait poser ou effacer votre politique à votre place. HSTS ne vaut par ailleurs 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. L'argument de l'OWASP mérite d'être retenu : un cookie de session scopé au domaine parent peut être manipulé depuis un sous-domaine non sécurisé ; couvrir tout l'arbre, c'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.

Le point clé sur la portée : HSTS ne couvre que les hôtes que vous contrôlez et qui envoient le header. Il n'a rien à dire d'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 reprennent. 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 en https:// chaque URL de sous-ressource http:// non sécurisée de ce document, avant l'envoi de la requête, first-party comme tiers : images, scripts, styles, frames, fetch. Les navigations, elles, sont traitées plus strictement. Le navigateur passe en HTTPS les navigations vers votre propre hôte, les soumissions de formulaires et les frames imbriquées, mais ne touche pas aux liens vers des sites tiers : les auteurs de la spec ont jugé que réécrire l'URL de navigation de quelqu'un d'autre présentait un risque de casse trop élevé, et un simple lien vers http://other.example/ part donc tel quel.

Content-Security-Policy: upgrade-insecure-requests

Côté sous-ressources, la portée est l'inverse de celle de HSTS. Elle s'applique à chaque URL que votre page charge, y compris des hôtes tiers qui ne vous appartiennent 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 change rien, alors que upgrade-insecure-requests la réécrit en https:// avant l'envoi de la requête. C'est comme ça qu'on élimine les avertissements de mixed content sur une page qui charge de nombreuses origines, sans reprendre 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 savoir de quelle base vous partez. 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 passe en HTTPS au lieu d'être bloqué, et que la console cesse de se remplir d'avertissements de mixed content. Un cas limite subsiste 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, l'un ne peut donc pas se substituer à l'autre, et les specs le disent elles-mêmes. MDN précise 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. Un utilisateur qui clique sur un lien http:// vers votre site depuis ailleurs a besoin de HSTS pour basculer en HTTPS avant l'établissement de la connexion ; la directive CSP, elle, ne s'exécute jamais, puisque la page n'est pas encore chargée.

Dans l'autre sens, 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 est en cause. 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 recommandent de garder HSTS à 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 chacun une moitié du problème. 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 fera jamais. 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 demande au navigateur de réécrire en https:// chaque URL de sous-ressource http:// non sécurisée de la page, first-party comme tierce, avant l'envoi de la requête, 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