CentralCSP
Headers de sécurité

X-Content-Type-Options

Le header nosniff empêche les navigateurs de deviner le Content-Type et ferme les attaques par MIME sniffing. Rôle et mise en place.

Dernière mise à jour:

Les navigateurs inspectaient autrefois les premiers octets d'une réponse pour deviner son vrai type dès que le Content-Type déclaré manquait ou semblait faux, un comportement appelé MIME sniffing. X-Content-Type-Options: nosniff coupe la devinette : le navigateur fait confiance au type que votre serveur déclare et traite la réponse comme ce type uniquement.

La devinette était la surface d'attaque : un fichier uploadé comme image inoffensive mais qui contient en réalité du HTML et du JavaScript pouvait être sniffé en page et exécuté dans l'origine de votre site, un XSS stocké. Ce header ferme ce chemin.

C'est tout le header. Il ne fait son travail que si le Content-Type de chaque réponse est correct :

X-Content-Type-Options: nosniff

Valeurs et ce que fait chacune

ValeurStatutDescription
nosniff✅ BonLa seule valeur. Le navigateur fait confiance au Content-Type déclaré et bloque les scripts et feuilles de style dont le type ne correspond pas.

nosniff, défini dans le standard Fetch, a deux effets.

D'abord, un blocage strict pour les scripts et les feuilles de style. Tout chargement de type script (un <script>, un worker, un shared worker, un service worker, un worklet audio ou paint) doit arriver avec un type MIME JavaScript, et une feuille de style doit arriver avec exactement text/css. En cas de non-correspondance, le navigateur bloque la réponse au niveau réseau et elle ne s'exécute jamais ; Chromium journalise une erreur console indiquant que le script a été refusé parce que « strict MIME type checking is enabled ». Sans le header, Chromium exécute encore les scripts servis en text/plain, text/html ou application/octet-stream (les types image, audio, vidéo et CSV sont bloqués comme scripts dans les deux cas).

Ensuite, plus aucune devinette d'octets ailleurs. Quand une réponse porte un Content-Type, le type déclaré est définitif. Quand elle n'en porte aucun, le navigateur peut encore classer le corps comme texte ou binaire, mais il ne le sniffera jamais vers du HTML, du XML ou du PDF.

Chrome embarque aussi Opaque Response Blocking (ORB), qui bloque pour tous les sites les lectures no-cors cross-origin des réponses qui ressemblent à du HTML, du JSON ou du XML, quels que soient les headers envoyés. nosniff rend ce blocage déterministe : un type de la blocklist, un text/plain ou un type absent échoue fermé au lieu de dépendre d'un sniffing de confirmation. ORB ne fait rien pour les chargements same-origin, donc le header reste la seule chose qui fait respecter la correction MIME pour vos propres scripts et feuilles de style.

Le volet Content-Type

Le header fait respecter l'étiquette, donc l'étiquette doit être juste sur chaque ressource : les scripts servis avec un type MIME JavaScript (text/javascript), les feuilles de style en text/css, le HTML en text/html. Sur les réponses HTML, déclarez aussi l'encodage des caractères :

Content-Type: text/html; charset=UTF-8

La cheat sheet OWASP sur les headers HTTP le dit sans détour : « the charset attribute is necessary to prevent XSS in HTML pages » (l'attribut charset est nécessaire pour prévenir le XSS dans les pages HTML). La version classique de cette attaque, faire passer du script via UTF-7, est morte parce que les navigateurs ont abandonné cet encodage, mais omettre le charset reste exploitable. Une recherche publiée par Sonar en 2024 a montré que lorsqu'une page omet son charset, Chrome et Firefox auto-détectent ISO-2022-JP, et un attaquant qui contrôle une partie de la page peut utiliser les séquences d'échappement de cet encodage pour sortir de l'échappement en plein document, un XSS fonctionnel en 2024. Déclarez le charset sur chaque réponse HTML.

Contre quoi cela protège

  • Des uploads mal étiquetés exécutés comme HTML ou script. Un avatar.png qui est en réalité un fichier HTML, servi avec un type absent ou inconnu, pouvait être sniffé en page et s'exécuter dans votre origine comme XSS stocké. Avec nosniff, le navigateur prend le fichier pour son type déclaré.
  • Une infrastructure mal configurée ou qui retire le type. Un serveur qui omet Content-Type, ou un proxy qui le retire, laisse le navigateur deviner. Le header supprime la devinette, donc une erreur de configuration reste une ressource cassée au lieu de devenir du contenu exécutable.
  • Des scripts de worker mal étiquetés. Le blocage strict couvre les workers, shared workers, service workers et worklets, donc une ressource chargée comme worker doit elle aussi porter un type MIME JavaScript.
  • Les fuites de données cross-origin. Il renforce le blocage des lectures cross-origin du navigateur (ORB) contre les attaques de classe Spectre en rendant le blocage déterministe plutôt que fondé sur le sniffing.

Le header fait respecter l'étiquette, rien de plus. Une réponse correctement étiquetée text/html se rend toujours, du JavaScript correctement étiqueté s'exécute toujours, donc il ne remplace ni la politique de sécurité du contenu (CSP) ni la gestion des uploads (validation, une origine séparée pour le contenu utilisateur).

Risques sans le header

Sans le header, chaque réponse a besoin d'un Content-Type parfait, parce que toute réponse qui n'en a pas est ouverte à la réinterprétation. Une seule route de contenu utilisateur qui sert des fichiers avec un type absent ou inconnu, un seul proxy qui retire le header, et un moteur de sniffing peut promouvoir un fichier d'apparence inoffensive en HTML dans votre origine. Les moteurs modernes ont réduit le sniffing (ils ne sniffent vers HTML que lorsque le type est absent ou inconnu), donc l'exposition pratique se concentre sur les surfaces mal configurées et les moteurs anciens, ce qui est exactement le rôle de la défense en profondeur.

Risques et pièges à l'usage

  • Les scripts mal typés cessent de charger. Un script légitime servi en text/plain ou application/octet-stream (un serveur de fichiers brut, un objet Amazon S3 uploadé sans type) est bloqué dès que nosniff est déployé. Corrigez les mappings MIME avant d'ajouter le header, pas après.
  • Les feuilles de style doivent être exactement text/css. Tout le reste est bloqué, de la même façon que les scripts exigent un type MIME JavaScript.
  • Forme header uniquement. Il n'existe pas d'équivalent <meta> ; il doit être envoyé comme header de réponse.
  • Aucun rapport de violation. Les chargements bloqués n'apparaissent que comme erreurs console. Contrairement à CSP, le header n'a aucune intégration avec la Reporting API, donc rien ne vous signale qu'une ressource a cassé en production ; testez avant et après le déploiement.

Comment le mettre en place

  1. Auditez vos mappings Content-Type : chaque script servi avec un type MIME JavaScript, chaque feuille de style en text/css, chaque réponse HTML en text/html; charset=UTF-8. Prêtez attention aux serveurs de fichiers et au stockage objet, où le type est celui défini à l'upload.
  2. Ajoutez X-Content-Type-Options: nosniff à chaque réponse, réponses d'API comprises. Le définir une fois au niveau du serveur ou du CDN est plus simple que route par route, et il n'y a aucune valeur à régler.
  3. Vérifiez le header déployé, et le reste de vos headers de réponse, avec le scanner de headers de sécurité, et surveillez la console du navigateur pour des scripts ou feuilles de style refusés après le déploiement.

Recommandation

Envoyez X-Content-Type-Options: nosniff sur chaque réponse :

X-Content-Type-Options: nosniff

La cheat sheet OWASP sur les headers HTTP recommande de l'envoyer sur toutes les réponses que les navigateurs peuvent consommer, et le cheat sheet OWASP sur la sécurité REST étend cela aux réponses d'API. Associez-le à un Content-Type correct sur chaque ressource et à un charset sur le HTML (text/html; charset=UTF-8) : le header fait respecter l'étiquette, donc l'étiquette doit être juste.

Prise en charge par les navigateurs

Pris en charge par tous les moteurs modernes. Microsoft a introduit le header dans Internet Explorer en 2008 ; leur démo était un fichier HTML servi en text/plain qui se rendait comme du HTML dans les anciennes versions d'Internet Explorer mais comme du texte brut dans la version qui a livré nosniff. Chrome, Firefox et Safari l'appliquent tous intégralement dans leurs versions actuelles (certaines premières versions de Chrome n'appliquaient pas la vérification des feuilles de style). Firefox applique aussi nosniff aux chargements de page de premier niveau (voir l'annonce de Mozilla) : une navigation dont le type déclaré ne correspond pas affiche une page d'erreur, et un type absent se rend en texte brut ou se télécharge.

FAQ

Faut-il nosniff sur les réponses d'API ?

Oui. La cheat sheet OWASP sur la sécurité REST recommande X-Content-Type-Options: nosniff sur chaque réponse, réponses d'API comprises. Il ne coûte rien à envoyer sur tout le site, empêche une réponse JSON d'être sniffée vers du HTML, et il n'y a aucune valeur à régler, donc définissez-le une fois au niveau du serveur ou du CDN plutôt que route par route.

nosniff casse-t-il quelque chose ?

Seulement les ressources envoyées avec le mauvais Content-Type. Un script servi en text/plain, ou une feuille de style qui n'est pas exactement text/css, est bloqué dès que le header est déployé. Corrigez d'abord vos mappings MIME, puis ajoutez nosniff, de sorte que le contenu correctement étiqueté continue de charger et que seules les ressources jusque-là mal étiquetées apparaissent en erreur.

nosniff arrête-t-il le XSS ?

Non. Il ferme un chemin précis : un upload mal étiqueté sniffé et exécuté comme HTML ou script dans votre origine. Du HTML correctement étiqueté se rend toujours et du JavaScript correctement étiqueté s'exécute toujours, donc vous avez encore besoin d'une politique de sécurité du contenu (CSP) et d'une bonne gestion des uploads. Traitez nosniff comme de la défense en profondeur, pas comme un correctif XSS.

Voir aussi

Sources

On this page