← Retour au blog

Durcir son site - security.txt et en-têtes de sécurité

6 min de lecture

Il y a une ironie à parler de sécurité applicative sur un site dont la configuration serait négligée. C'est le cordonnier mal chaussé : la crédibilité d'un propos technique tient aussi à l'exemple qu'on donne. Alors voici comment j'ai durci ce site, expliqué pièce par pièce — tu peux vérifier chaque point de tes propres yeux avec curl ou sur securityheaders.com.

Deux chantiers : un petit fichier qui dit comment me signaler une faille, et une poignée d'en-têtes HTTP qui demandent au navigateur de se méfier.

security.txt : la porte d'entrée des chercheurs

Imagine que tu trouves une faille sur un site. Où écris-tu ? Au support ? Sur Twitter ? La RFC 9116 répond à cette question avec une convention simple : un fichier texte à un emplacement fixe, /.well-known/security.txt, qui donne le canal de signalement.

Le mien ressemble à ça :

Contact: mailto:sgbrt42@proton.me
Expires: 2027-10-15T00:00:00.000Z
Preferred-Languages: fr, en
Canonical: https://sigabort42.me/.well-known/security.txt
  • Contact (obligatoire) : où envoyer un rapport.
  • Expires (obligatoire) : au-delà, le fichier est considéré périmé. Sur mon site, je le recalcule à un an à chaque requête, pour qu'il ne se démode jamais.
  • Canonical : l'URL officielle du fichier, pour éviter qu'une copie égarée fasse autorité.

Les chercheurs — et les outils automatisés de divulgation — cherchent ce fichier en premier. L'avoir, c'est connaître la norme et offrir une entrée propre. Techniquement, c'est une route qui renvoie du texte brut ; dix minutes de travail.

Les en-têtes de sécurité

Un en-tête HTTP de sécurité, c'est une instruction que ton serveur attache à chaque réponse et que le navigateur fait respecter. Tu ne protèges pas le serveur : tu demandes au navigateur du visiteur de se comporter plus prudemment. En voici cinq, du plus simple au plus subtil.

Strict-Transport-Security (HSTS)

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

Dit au navigateur : « pendant deux ans, ne me contacte qu'en HTTPS, même si l'utilisateur tape l'adresse en clair ou clique sur un vieux lien ». Ça ferme la fenêtre pendant laquelle une première requête en clair pourrait être interceptée et détournée.

includeSubDomains étend la règle à tous les sous-domaines — à n'activer que si tous servent bien du HTTPS. J'ai volontairement laissé de côté l'option preload, qui inscrit le domaine dans une liste codée en dur dans les navigateurs : c'est efficace, mais très difficile à défaire ensuite.

X-Content-Type-Options

X-Content-Type-Options: nosniff

Par défaut, un navigateur essaie parfois de deviner le type d'un fichier d'après son contenu, pas d'après ce que le serveur annonce. Un fichier déclaré « image » mais contenant du JavaScript pourrait ainsi être exécuté. nosniff coupe court : le type annoncé fait foi, point.

X-Frame-Options

X-Frame-Options: DENY

Empêche qu'on affiche ton site dans une iframe sur un autre domaine. Sans ça, un attaquant peut superposer ton site invisible au-dessus du sien et piéger les clics de la victime — c'est le clickjacking. DENY interdit tout encadrement. (La version moderne équivalente passe par la directive frame-ancestors d'une CSP, dont on parle plus bas.)

Referrer-Policy

Referrer-Policy: strict-origin-when-cross-origin

Quand on quitte ton site par un lien, le navigateur envoie au site de destination l'adresse d'où l'on vient (le referer). Cette valeur n'envoie que l'origine (le domaine nu, sans le chemin complet) vers l'extérieur, et rien du tout si l'on passe de HTTPS à HTTP. On évite ainsi de fuiter des URL parfois sensibles.

Permissions-Policy

Permissions-Policy: camera=(), microphone=(), geolocation=()

Désactive explicitement des fonctions du navigateur dont le site n'a pas besoin. Un portfolio n'a aucune raison de demander la caméra ou la géolocalisation : en les coupant, un éventuel script injecté ne pourra pas les réclamer non plus. C'est de la réduction de surface d'attaque.

Où poser ces en-têtes

Deux endroits possibles : dans l'application, ou dans le reverse proxy en amont. J'ai choisi le proxy (Traefik), parce que les en-têtes y sont centralisés une fois pour toutes, quels que soient les services derrière. Concrètement, une « middleware » déclarative :

http:
  middlewares:
    sec-headers:
      headers:
        stsSeconds: 63072000
        stsIncludeSubdomains: true
        contentTypeNosniff: true
        frameDeny: true
        referrerPolicy: strict-origin-when-cross-origin
        permissionsPolicy: "camera=(), microphone=(), geolocation=()"

… qu'on applique ensuite au routeur du site. L'avantage : la politique de sécurité vit à côté de l'infrastructure, versionnée, et ne dépend pas du framework applicatif.

La CSP : la plus puissante, et la plus piégeuse

Il manque la pièce maîtresse : la Content-Security-Policy. C'est la principale défense contre le XSS (l'injection de script). Elle déclare une liste blanche : d'où le navigateur a le droit de charger des scripts, des styles, des images, des polices. Tout ce qui n'y figure pas est bloqué. Un script injecté par un attaquant, s'il vient d'une source non autorisée, ne s'exécute tout simplement pas.

Sa force est aussi son danger : une CSP trop stricte casse ton propre site. Ce site charge un script d'analyse depuis un sous-domaine et des polices depuis un service tiers ; une politique naïve les bloquerait au même titre qu'un script hostile. Il faut donc lister précisément ses propres sources — un travail d'inventaire qui n'a rien d'automatique.

La bonne méthode est progressive. On commence par l'en-tête en mode observation :

Content-Security-Policy-Report-Only: default-src 'self'; …

En Report-Only, le navigateur ne bloque rien : il signale seulement ce qu'il aurait bloqué. On collecte ces signalements, on complète la liste blanche jusqu'à ce qu'il ne reste que le trafic légitime, et seulement alors on bascule l'en-tête en mode bloquant. C'est pour ça que je l'ai mise de côté pour l'instant : les cinq autres en-têtes sont sans risque et immédiats ; la CSP mérite son propre chantier, fait proprement.

Mesurer

Rien de tout cela n'est visible à l'œil. Deux façons de vérifier :

curl -sI https://sigabort42.me | grep -iE 'strict-transport|x-frame|x-content|referrer|permissions'
curl -s  https://sigabort42.me/.well-known/security.txt

Et pour une note globale, securityheaders.com analyse ton domaine et attribue une lettre. Sans CSP, on plafonne autour du B ; c'est elle qui ouvre le A. Un objectif concret pour le prochain chantier.

Le vrai intérêt

Au fond, ces en-têtes ne sont pas qu'une liste de cases à cocher. Chacun répond à une classe d'attaque précise — interception, clickjacking, confusion de type, fuite de référent, XSS. Les mettre en place, c'est se forcer à nommer la menace qu'ils écartent. Et c'est la meilleure façon de comprendre, en les configurant sur son propre site, ce qu'on cherchera ensuite à exploiter sur celui des autres.