Vulnérabilités des headers HTTP
Ce TP utilise 2 machines : un serveur sous Linux Debian, qui héberge le site avec Apache, et un client sous Windows, qui s'y connecte avec son navigateur.
- Comprendre comment l'absence de certains headers HTTP laisse une application exposée à des attaques (Clickjacking, XSS, interception) alors même que le serveur et le code fonctionnent normalement : un cas particulier de Security Misconfiguration, n°2 du Top 10 de l'OWASP (2025).
- Apprendre à ajouter ces headers avec Apache, grâce au module
mod_headers.
Un header de sécurité ne corrige jamais une faille présente dans le code : il ajoute une protection supplémentaire, au niveau du navigateur, qui limite les dégâts si une faille existe quand même. C'est une protection en profondeur, pas un substitut à du code bien écrit.
1. Création du site vulnérable
Sur le serveur Debian, créez un dossier /var/www/html/club-randonnee/ contenant la page de
recherche d'adhérents du club de randonnée.
index.php :
<?php
$nom = $_GET['nom'] ?? '';
?>
<!DOCTYPE html>
<html lang="fr">
<head><meta charset="UTF-8"><title>Club de randonnée - Recherche d'adhérents</title></head>
<body>
<h1>Club de randonnée</h1>
<form method="get" action="">
<label>Rechercher un adhérent par nom : <input type="text" name="nom"></label>
<button type="submit">Rechercher</button>
</form>
<?php if ($nom !== ''): ?>
<p>Résultat de la recherche : <?= $nom ?></p>
<?php endif; ?>
</body>
</html>
<?= $nom ?> affiche directement la valeur du paramètre nom, sans aucun échappement : c'est
volontaire, pour provoquer une faille XSS à l'étape 4, exactement comme le ferait un formulaire de
recherche mal écrit sur un vrai site.
2. Démarrer Apache
Sur le serveur Debian, installez Apache et PHP, puis placez le site dans le répertoire par défaut du serveur :
sudo apt update
sudo apt install apache2 libapache2-mod-php php -y
sudo systemctl start apache2
sudo systemctl enable apache2
sudo systemctl start apache2 peut aussi s'écrire sudo service apache2 start : les deux
commandes démarrent Apache, service étant l'ancienne syntaxe (toujours disponible sur Debian),
systemctl la syntaxe actuelle (systemd).
Relevez l'adresse IP du serveur Debian (ip addr show) : nous l'appellerons <IP_DU_SERVEUR>
dans la suite du TP.
Depuis le client Windows, vérifiez qu'Apache fonctionne en ouvrant
http://<IP_DU_SERVEUR>/club-randonnee/ dans votre navigateur : le formulaire de recherche doit
s'afficher.
3. Vérifier l'absence de headers de sécurité
Depuis un terminal du client Windows (PowerShell, qui inclut curl par défaut), interrogez
les headers HTTP renvoyés par le serveur :
curl -I http://<IP_DU_SERVEUR>/club-randonnee/
Question : Parmi les headers affichés, lequel protège contre le Clickjacking, l'exécution de script injecté, ou l'interception en clair de la connexion ?
📌 Une réponse
4. Constater la vulnérabilité au Clickjacking
Sur le client Windows, créez un fichier clickjacking.html :
<!DOCTYPE html>
<html>
<head><title>Page piégée</title></head>
<body>
<h2>Jeu concours : cliquez ici pour gagner !</h2>
<iframe src="http://<IP_DU_SERVEUR>/club-randonnee/" width="800" height="600"></iframe>
</body>
</html>
Ouvrez ce fichier dans le navigateur du client Windows.
Résultat attendu : le site du club de randonnée s'affiche normalement dans l'iframe.
Question : En quoi est-ce dangereux, alors que le contenu affiché est celui du vrai site ?
📌 Une réponse
5. Constater la vulnérabilité XSS liée à l'absence de CSP
Dans le navigateur du client Windows, ouvrez :
http://<IP_DU_SERVEUR>/club-randonnee/index.php?nom=<script>alert('XSS')</script>
Résultat attendu : une alerte JavaScript s'affiche.
Question : Cette faille vient-elle d'un header manquant, ou d'autre chose ?
📌 Une réponse
Jusqu'ici, aucune configuration n'a été modifiée : c'est la configuration par défaut d'Apache et de PHP qui laisse ces deux attaques possibles.
6. Ajouter les headers de sécurité avec Apache
Sur le serveur Debian, activez le module mod_headers, indispensable pour ajouter des
headers personnalisés avec Apache :
sudo a2enmod headers
Ouvrez la configuration du site :
sudo nano /etc/apache2/sites-available/000-default.conf
Ajoutez ces lignes dans le bloc <VirtualHost *:80> :
<VirtualHost *:80>
# ... lignes existantes ...
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Frame-Options "DENY"
Header always set X-Content-Type-Options "nosniff"
Header always set Content-Security-Policy "default-src 'self'"
Header always set Referrer-Policy "no-referrer"
</VirtualHost>
Redémarrez Apache pour appliquer les changements :
sudo systemctl restart apache2
always indique à Apache d'ajouter le header à toutes les réponses, y compris les pages
d'erreur (404, 500...), qui seraient sinon oubliées.
7. Vérifier que les headers sont bien appliqués
Depuis le client Windows, rejouez la commande de l'étape 3 :
curl -I http://<IP_DU_SERVEUR>/club-randonnee/
Résultat attendu : les cinq headers ajoutés apparaissent désormais dans la réponse.
📌 Une réponse
8. Revérifier le Clickjacking
Sur le client Windows, rechargez clickjacking.html dans votre navigateur.
Résultat attendu : l'iframe reste vide, et la console du navigateur affiche un refus du type « Refused to display ... in a frame because it set 'X-Frame-Options' to 'deny' ».
9. Revérifier le XSS
Sur le client Windows, rechargez :
http://<IP_DU_SERVEUR>/club-randonnee/index.php?nom=<script>alert('XSS')</script>
Résultat attendu : l'alerte ne s'affiche plus. La console du navigateur signale que
l'exécution du script a été bloquée par la politique Content-Security-Policy.
Le paramètre nom reste affiché sans échappement dans le code source de la page : seule la CSP
empêche le script de s'exécuter. Une vraie correction consiste à échapper l'affichage
(htmlspecialchars($nom)), en plus de garder la CSP comme protection supplémentaire.
Notions théoriques
Les headers HTTP sont des métadonnées envoyées avec chaque réponse d'un serveur Web. Certains d'entre eux, dits headers de sécurité, indiquent au navigateur des règles supplémentaires à appliquer pour limiter les conséquences de failles déjà présentes dans une application.
Les principaux headers de sécurité
| Header | Protège contre | Effet |
|---|---|---|
| Strict-Transport-Security (HSTS) | Interception en clair, Man-in-the-Middle | Oblige le navigateur à utiliser uniquement HTTPS pour ce site |
| X-Frame-Options | Clickjacking | Empêche l'affichage du site dans une iframe |
| Content-Security-Policy (CSP) | XSS | Limite les sources de script autorisées à s'exécuter |
| X-Content-Type-Options | MIME sniffing | Empêche le navigateur d'interpréter un fichier autrement que son type déclaré |
| Referrer-Policy | Fuite d'informations | Limite ce qui est transmis dans l'URL d'origine lors d'une navigation |
Pourquoi ces headers sont-ils si souvent absents ?
Aucun de ces headers n'est ajouté par défaut par Apache : il faut explicitement activer
mod_headers et les déclarer, comme vu dans le TP. Une application peut donc fonctionner
parfaitement, sans aucune erreur visible, tout en restant dépourvue de cette protection : c'est
pour cela que l'absence de headers de sécurité est une forme de
mauvaise configuration,
plutôt qu'une faille de code.
Contrairement au XSS, qui porte sur une faille du code, un header de sécurité se règle au niveau du serveur Web, indépendamment du code de l'application.
Comment vérifier les headers d'un site ?
- En ligne de commandes, avec
curl -I https://exemple.fr. - Dans un navigateur, via les outils de développement (
F12), onglet « Réseau », en cliquant sur la requête principale.
Un site protégé par des headers de sécurité n'a pas pour autant corrigé les failles de son
code : comme vu à l'étape 9, la CSP empêche le script de s'exécuter, mais le paramètre nom
reste affiché sans échappement. Les headers sont une protection en plus, jamais un
remplacement du code correctement écrit.
Test de mémorisation/compréhension
Bonnes pratiques
- Activer
mod_headerset déclarer les headers de sécurité sur chaque site Apache, car aucun n'est ajouté par défaut. - Utiliser
Header always setplutôt queHeader set, pour que la protection s'applique aussi aux pages d'erreur. - Vérifier les headers après chaque modification (
curl -I, outils de développement du navigateur), pas seulement une fois au moment de la mise en production. - Ne jamais considérer un header de sécurité comme un correctif du code : la CSP bloque un script injecté, elle ne remplace pas l'échappement des données affichées.
- Combiner plusieurs headers (HSTS, X-Frame-Options, CSP, X-Content-Type-Options, Referrer-Policy) : chacun protège contre un type d'attaque différent.
Ce que fait Symfony
Symfony, par défaut, n'ajoute pas davantage de headers de sécurité qu'Apache seul : c'est à vous de les régler, par exemple via un EventListener sur la réponse, ou en installant le bundle NelmioSecurityBundle, qui fournit des réglages prêts à l'emploi pour la plupart de ces headers.
# config/packages/nelmio_security.yaml
nelmio_security:
clickjacking:
paths:
'^/.*': DENY
content_type:
nosniff: true
csp:
enforce:
default-src: ["'self'"]
Comme avec Apache, activer ces headers ne corrige jamais une faille déjà présente dans vos
contrôleurs ou vos templates Twig : Twig échappe automatiquement les variables affichées
({{ nom }}), mais seulement si vous ne désactivez pas ce comportement avec le filtre |raw.