Écrire un petit script d'audit : les en-têtes HTTP
Quand votre navigateur charge une page, le serveur répond avec le contenu, mais aussi avec des « en-têtes » : de petites consignes de sécurité adressées au navigateur. Leur absence est l'une des observations les plus fréquentes dans un audit web, et l'une des plus faciles à corriger. Cette leçon boucle le chapitre avec un script d'audit complet, volontairement limité à un site que vous possédez ou que vous avez le droit de tester.
Règle d'or : votre site, ou une autorisation écrite
Même une requête qui paraît anodine, envoyée vers un système qui n'est pas le vôtre, est un acte à encadrer. Pour un audit professionnel, exigez une autorisation écrite et un périmètre défini (articles 323-1 à 323-3-1 du Code pénal). Pour vous entraîner, utilisez OWASP Juice Shop ou DVWA lancés localement dans votre laboratoire, ou le site personnel que vous hébergez. Le script de cette leçon exige d'ailleurs un paramètre explicite pour rappeler cette règle à l'utilisateur.
À quoi servent les en-têtes de sécurité ?
Un en-tête HTTP de sécurité est une instruction envoyée par le serveur que le navigateur applique. C'est une défense « en profondeur » : même si une faille existe dans la page, l'en-tête réduit ce qu'elle permet. Voici ceux que nous contrôlerons :
| En-tête | Rôle |
|---|---|
Strict-Transport-Security | Indique au navigateur de n'utiliser que HTTPS pour ce site pendant une durée donnée |
Content-Security-Policy | Liste les sources de contenu autorisées (scripts, images…), ce qui limite l'impact de l'injection de contenu |
X-Content-Type-Options | Empêche le navigateur de « deviner » le type d'un fichier |
Referrer-Policy | Contrôle les informations de provenance envoyées à d'autres sites |
Permissions-Policy | Restreint l'accès à certaines fonctions du navigateur (caméra, localisation…) |
À l'inverse, certains en-têtes en disent trop : Server et X-Powered-By indiquent parfois le logiciel et sa version, ce qui aide un attaquant à cibler ses recherches. Les masquer ne corrige pas une vulnérabilité, mais ne donne pas d'indice gratuit. Les pages de référence sur les en-têtes se trouvent sur developer.mozilla.org, et le projet « Secure Headers » de l'OWASP (owasp.org) propose des recommandations à jour.
Concevoir le script
Avant d'écrire, formulez le besoin en quatre phrases : « Le script reçoit une adresse. Il envoie une requête HEAD (qui demande uniquement les en-têtes, pas la page). Il compare les en-têtes reçus à une liste attendue. Il affiche les écarts. » Une requête HEAD est légère et non intrusive : c'est le minimum nécessaire pour l'objectif. Respecter le principe de proportionnalité (ne faire que ce qui est nécessaire) est une qualité professionnelle.
Nous utilisons uniquement la bibliothèque standard de Python, ce qui évite toute installation et permet de relire entièrement le code.
Le script complet
import sys
import urllib.error
import urllib.request
EN_TETES_ATTENDUS = {
"strict-transport-security": "Impose HTTPS aux visites suivantes",
"content-security-policy": "Limite les sources de contenu autorisées",
"x-content-type-options": "Empêche le navigateur de deviner le type de contenu",
"referrer-policy": "Contrôle les informations transmises dans Referer",
"permissions-policy": "Restreint les fonctions du navigateur (caméra, position...)",
}
EN_TETES_BAVARDS = ("server", "x-powered-by")
def recuperer_en_tetes(url):
requete = urllib.request.Request(url, method="HEAD")
try:
with urllib.request.urlopen(requete, timeout=10) as reponse:
return {k.lower(): v for k, v in reponse.headers.items()}
except urllib.error.HTTPError as erreur:
return {k.lower(): v for k, v in erreur.headers.items()}
def auditer(en_tetes):
constats = []
for nom, role in EN_TETES_ATTENDUS.items():
if nom not in en_tetes:
constats.append(f"ABSENT {nom} : {role}")
for nom in EN_TETES_BAVARDS:
if nom in en_tetes:
constats.append(f"REVELE {nom} = {en_tetes[nom]}")
return constats
def main():
if len(sys.argv) != 3 or sys.argv[2] != "--site-autorise":
print("Usage : python audit_entetes.py URL --site-autorise")
print("N'auditez que vos propres sites ou ceux d'un laboratoire.")
sys.exit(1)
url = sys.argv[1]
try:
en_tetes = recuperer_en_tetes(url)
except (urllib.error.URLError, TimeoutError, ValueError) as erreur:
print(f"Impossible de joindre {url} : {erreur}")
sys.exit(2)
constats = auditer(en_tetes)
print(f"Audit des en-têtes de {url}")
if constats:
for ligne in constats:
print(" ", ligne)
else:
print(" Aucun écart parmi les contrôles prévus.")
if __name__ == "__main__":
main()
Lire le script pas à pas
- Dictionnaire et tuple de départ : la liste des contrôles est séparée de la logique. Pour ajouter un contrôle, on modifie une ligne de données, pas le programme.
recuperer_en_tetes: elle envoie la requête HEAD avec un délai maximal de 10 secondes (un script ne doit jamais attendre indéfiniment). Les noms d'en-têtes sont passés en minuscules, car HTTP ne distingue pas les majuscules. Si le serveur répond par une erreur (par exemple « 404 »), les en-têtes de cette réponse sont tout de même exploitables : on les récupère.auditer: fonction « pure », sans accès au réseau, qui compare des données. Elle est donc très facile à tester avec de faux en-têtes.main: elle impose le paramètre--site-autorise, gère proprement les erreurs de connexion et utilise des codes de retour différents (1 pour un mauvais usage, 2 pour un échec réseau), pratiques pour enchaîner le script avec d'autres.
Exécuter dans votre laboratoire
Lancez OWASP Juice Shop localement dans votre machine virtuelle de laboratoire (il écoute généralement sur le port 3000 de la machine locale), puis, dans un terminal :
python3 audit_entetes.py http://127.0.0.1:3000 --site-autorise
Vous obtiendrez la liste des en-têtes absents. L'application d'entraînement est volontairement imparfaite : c'est normal, c'est même son but. Il serait trompeur d'en tirer des conclusions sur une application réelle ; en revanche, vous disposez d'un outil à ajouter à votre portfolio.
Pour tester sans application, vous pouvez aussi lancer dans un dossier de laboratoire le serveur web minimal de Python (python3 -m http.server 8000) et pointer le script vers http://127.0.0.1:8000. Vous verrez alors cinq en-têtes absents et un en-tête server qui révèle la version du logiciel.
Du constat au rapport
Un script produit des observations, pas des conclusions. Dans un rapport, chaque ligne devient un constat contextualisé :
Constat : l'en-tête
Content-Security-Policyest absent des réponses du site. Risque : en cas de défaut d'échappement du contenu, le navigateur n'a aucune consigne pour limiter les scripts exécutés. Recommandation : définir une politique adaptée aux ressources réellement utilisées, la tester en mode « rapport » avant de l'appliquer. Priorité : moyenne.
Notez la prudence du vocabulaire : l'absence d'en-tête est une défense manquante, pas une faille exploitable en soi. Évitez les formulations alarmistes : un rapport crédible distingue ce qui est démontré de ce qui est potentiel.
À vous de jouer
Étendez le script pour qu'il contrôle aussi la protection contre l'affichage de la page dans un cadre d'un autre site (le « clickjacking »). Deux mécanismes existent : l'en-tête X-Frame-Options, ou la directive frame-ancestors dans Content-Security-Policy. Le contrôle est satisfait si l'un des deux est présent.
Voir une solution
Ajoutez dans auditer, avant le return :
politique = en_tetes.get("content-security-policy", "")
if "x-frame-options" not in en_tetes and "frame-ancestors" not in politique:
constats.append("ABSENT protection contre l'encadrement de la page (X-Frame-Options ou frame-ancestors)")
On utilise .get avec une valeur par défaut pour éviter une erreur quand l'en-tête manque. Testez avec de faux dictionnaires d'en-têtes pour vérifier les trois cas : aucun mécanisme, X-Frame-Options seul, frame-ancestors seul.
Pour votre portfolio
Ajoutez au dépôt un fichier README qui explique l'objectif du script, son usage, ses limites (il ne teste que des en-têtes) et son avertissement légal. Un employeur lit ce détail avec attention : il montre que vous comprenez le cadre dans lequel un outil s'utilise.
Vous avez maintenant une première boîte à outils : un nettoyeur de listes, un analyseur de journaux et un audit d'en-têtes. Dans le chapitre suivant, nous quittons le code pour comprendre comment les équipes s'organisent : l'attaque simulée (red team) et la défense (blue team).
À retenir
- Les en-têtes de sécurité sont des consignes données au navigateur ; leur absence est un constat fréquent, simple à corriger.
- Un bon script est proportionné (requête HEAD), borné (délai maximal), explicite sur son usage autorisé et facile à relire.
- Séparez les données (contrôles attendus) de la logique, et la logique pure (comparaison) des accès réseau.
- Un script livre des observations ; le rapport les transforme en constats nuancés avec risque et recommandation.
- N'auditez que vos sites, votre laboratoire ou un périmètre autorisé par écrit.