Concilier un cache performant avec une politique de sécurité stricte de CSP avec nonce
Retour d’expérience sur la mise en place par Bearstech d’une architecture permettant l’utilisation de nonce (token unique par client) avec la mise en cache des pages par Vinyl (anciennement varnish cache) et HAProxy pour les en-têtes en évitant d’utiliser script-src ‘unsafe-inline’.
En développement web, un nonce est un jeton ou un nombre aléatoire unique généré pour une requête ou une session spécifique, les développements de nos clients utilisent la plupart du temps des caches, dans ce cas, une fois la mise en cache générée, les visiteurs suivants reçoivent le même nonce
L’enjeu est donc de mettre en place une solution permettant de conserver un élément dynamique pour chaque visiteur, tout en gardant l’utilité du cache.
Dans le contexte spécifique du projet sur lequel nous avons travaillé, l’application web utilise Google Tag Manager, obligeant à inclure du javascript inline dynamique dans les pages html.
L’utilisation de ‘unsafe-inline’ dans une directive script-src de la Content Security Policy (CSP) est fortement déconseillée car elle désactive la protection principale de la CSP contre les failles XSS (Cross-Site Scripting). Elle autorise l’exécution de n’importe quel script écrit directement dans le code HTML.
La CSP (Content Security Policy, ou Politique de Sécurité du Contenu en français) est une couche de sécurité essentielle qui prend la forme d’un entête HTTP envoyé par le serveur au navigateur. Son objectif principal est de détecter et de bloquer les attaques par injection de code, en premier lieu les failles XSS (Cross-Site Scripting) et les injections de données.
Pour éviter d’utiliser script-src ‘unsafe-inline’ dans l’entête CSP (content-security-policy), il est possible d’utiliser un ‘nonce’, c’est à dire un token unique de 32 caractères hexadécimaux (128 bits) pour chaque réponse https.
Ce nonce étant unique pour chaque utilisateur, il ne peut pas être mise en cache par Vinyl sous peine d’être identique pour chaque utilisateur.
Normalement Vinyl ne permet pas de gérer cela (seule la version non libre le permet via l’utilisation de 2 vmods) , nous partageons avec vous comment contourner cela sans utilisation de vmod libre non packagés par Debian.
Haproxy s’occupera de mettre à jour l’entete CSP avec un nonce, de transmettre ce nonce unique à Vinyl, et Vinyl s’occupera d’injecter le même nonce dans le body (via un ESI) permettant ainsi que la page soit quand même mise en cache.
(Un ESI : Edge Side Includes est un langage de balisage (similaire au HTML) utilisé pour optimiser la mise en cache des pages web dynamiques)
Pour éviter d’utiliser script-src 'unsafe-inline' dans l’entête CSP (content-security-policy), il est possible d’utiliser un ‘nonce’, c’est à dire un token unique de 32 caractères hexadecimaux (128 bits) pour chaque réponse https.
Exemple d’entete CSP avec un script inline avec un nonce :
content-security-policy: script-src 'strict-dynamic' 'nonce-ae713b66e9a46c2e1353b44430e99818'
Script dans la page html correspondante :
<sript type="text/javascript" nonce="ae713b66e9a46c2e1353b44430e99818">
window.alert("Hello world!");
</script>
Les navigateurs (s’ils sont compatibles avec csp) n’exécuteront le code inline que si les 2 tokens nonce sont identiques.
Référence : https://developer.mozilla.org/fr/docs/Web/HTML/Reference/Global_attributes/nonce
x-nonce avec un token unique à chaque requête entrante (avec un filtre pour ne le faire que sur les pages html)Par exemple en php :
<script type="text/javascript" nonce="<?php echo getenv('x-nonce');?>">
window.alert("Hello world!");
</script>
Si vous utilisez le framework php symfony, il faut récuperer le token avec :
$request->headers->get('x-nonce');
header("Content-Security-Policy: script-src 'strict-dynamic' 'nonce_placeholder'");
x-nonce avec un token unique à chaque requete entrante (avec un filtre pour ne le faire que sur les pages html)Surrogate-Capability: "abc=ESI/1.0" uniquement pour les pages contenant un script inline, pour indiquer à varnish qu’il doit exécuter les ESI . Example en php :header("Surrogate-Capability: 'abc=ESI/1.0'");
<script> par un ESI qui sera remplacé automatiquement par varnish (la page peut donc être en cache), L’ESI sera mis à jour dynamiquement à chaque requete. Ceci est à faire uniquement pour les pages contenant un script inline.. Exemple :<esi:include src='/_esi-x-nonce'/>
window.alert("Hello world!");
</script>
que varnish remplacera automatiquement en :
<script type="text/javascript" nonce="ae713b66e9a46c2e1353b44430e99818">
window.alert("Hello world!");
</script>
header("Content-Security-Policy: script-src 'strict-dynamic' 'nonce_placeholder'");
Par Pierre Arlais et Laurent Haond
Mieux comprendre le monde du DevOps et de l'administration système.
Abonnez-vous à notre newsletterNotre équipe possède une vaste expertise technologique.