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; preloadmax-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-requestsPour 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 | |
|---|---|---|
| Couche | Transport, comment le navigateur joint votre hôte | Contenu, URL chargées dans votre page |
| Portée | Votre hôte et les sous-domaines que vous couvrez | Chaque sous-ressource, hôtes tiers inclus |
| Navigation de premier niveau depuis un lien externe | Passée en HTTPS, une fois connu ou preloadé | Non couverte |
| Mixed content tiers | Non couvert | Passé en HTTPS |
| Menace | SSL stripping, protocol downgrade | Mixed content sur la page |
| En cas d'échec | Erreurs de certificat fatales, impossible de passer outre | La requête passée en HTTPS échoue, pas de repli HTTP |
| Où il est posé | Header de réponse Strict-Transport-Security | Directive CSP |
| Support | Tous 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
- RFC 6797, HTTP Strict Transport Security
- W3C, Upgrade Insecure Requests
- MDN, Strict-Transport-Security
- MDN, upgrade-insecure-requests
- MDN, Mixed content
- hstspreload.org, exigences de soumission et retrait
- OWASP, HTTP Strict Transport Security cheat sheet