magentix

Développeur backend indépendant Web, e-commerce, Open-Source, low-tech, indie-web, slow-web, SSG, accessibility, PHP, Python

Billets : Utiliser une stratégie de sécurité du contenu (Content Security Policy)


Utiliser une stratégie de sécurité du contenu (Content Security Policy)

Par Matthieu le 28/01/2020

Le CSP (Content Security Policy) est un excellent moyen de diminuer les attaques XSS et les injections de contenu. Vous décidez des resources internes et externes que le navigateur a le droit de charger. Configurer une stratégie CSP nécessite simplement d'utiliser un en-tête HTTP Content-Security-Policy.


La mise en place d'une stratégie de sécurité du contenu avec CSP est extrêmement efficace. CSP vous permet d'éliminer les moyens de réaliser des attaques XSS en permettant de spécifier les domaines autorisés à fournir des ressources (scripts, images, CSS...) pour la page visitée.

Configuration

Plusieurs possibilités pour inclure l'en-tête Content-Security-Policy.

Configuration Apache

# Content-Security-Policy
<IfModule mod_headers.c>
    Header set Content-Security-Policy "default-src 'self'"
</IfModule>
# /Content-Security-Policy

Configuration Nginx

add_header Content-Security-Policy "default-src 'self';"

Fichier .htaccess

# Content-Security-Policy
<IfModule mod_headers.c>
    Header set Content-Security-Policy "default-src 'self'"
</IfModule>
# /Content-Security-Policy

Header (PHP)

header("Content-Security-Policy: default-src 'self'");

Meta Tag

<meta http-equiv="Content-Security-Policy" content="default-src 'self'" />

Page d'exemple

Pour notre article, nous utilisons la page suivante :

<!doctype html>
<html lang="fr">
    <head>
        <title>Content Security Policy</title>

        <!-- Content Security Policy -->
        <meta http-equiv="Content-Security-Policy" content="default-src 'self'" />

        <!-- Local resources -->
        <link rel="stylesheet" href="css/style.css" type="text/css" />
        <script type="text/javascript" src="js/script.js"></script>

        <!-- External resources -->
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.10/styles/default.min.css" type="text/css" />
        <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.10/highlight.min.js"></script>

        <!-- Inline CSS -->
        <style>
            h1 {
                color: #CC0000;
            }
        </style>
    </head>
    <body>
        <h1>CSP Page</h1>

        <!-- Inline CSS -->
        <p style="font-weight: bold">Content Security Policy example page</p>

        <!-- External image -->
        <img src="https://fr.wikipedia.org/static/images/project-logos/frwiki.png" alt="Wikipedia" />

        <!-- Inline Script -->
        <script type="text/javascript">
            console.log('ok');
        </script>
    </body>
</html>

Application

Si vous copiez la page d'exemple dans un fichier html, la majorité des ressources sont bloquées. Vous obtenez dans la console du navigateur :

Blocage des ressources par le navigateur (CSP)

Chrome

Refused to [apply|load|execute] the [stylesheet|script|image|object|media|font] [resource] because it violates the following Content Security Policy directive: (...)

Firefox

Content Security Policy: The page's settings blocked the loading of a resource at [resource] (...)

Content Security Policy: Les paramètres de la page ont empêché le chargement d'une ressource à [ressource] (...)

Ressources locales

Considérons la directive CSP suivante :

default-src 'self'

Nous indiquons ici au navigateur de ne charger que les ressources locales, c'est à dire les fichiers Javascript et CSS herbergés sur notre serveur. Dans notre page d'exemple cela correspond à :

<!-- Local resources -->
<link rel="stylesheet" href="css/style.css" type="text/css" />
<script type="text/javascript" src="js/script.js"></script>

Cette stratégie est la plus restrictive et sécurisée qui soit (après la valeur "none" mais c'est un peu excessif). Par défaut nous n'autorisons que les ressources locales, les scripts et CSS inline sont ignorés.

Style inline

Notre page contient des styles en ligne dans le header et dans la page. Avec la directive précedente ils sont ignorés. Nous pouvons autoriser le style inline grâce à la valeur unsafe-inline de la directive style-src :

default-src 'self'; style-src 'self' 'unsafe-inline'

Avec cette stratégie, nos styles en ligne sont maintenant interprétés :

<!-- Inline CSS -->
<style>
    h1 {
        color: #CC0000;
    }
</style>
<!-- Inline CSS -->
<p style="font-weight: bold">Content Security Policy example page</p>

Script inline

Notre page contient un script en ligne ignoré avec notre politique par défaut. Il est cependant possible d'accepter les scripts inline par le biais de la valeur unsafe-inline de la directive script-src :

default-src 'self'; script-src 'self' 'unsafe-inline'

Nous autorisons ainsi la portion de code suivante :

<!-- Inline Script -->
<script type="text/javascript">
    console.log('ok');
</script>

Dans la pratique il est souvent complexe de ne pas autoriser les scripts en ligne, mais pour une stratégie de sécurité du contenu optimale il faut tendre à les éviter.

CSS externe

Nous voulons charger une ressource CSS herbergée sur un serveur distant : cdnjs.cloudflare.com. Cette ressource est automatiquement bloquée. Il est nécessaire de l'autoriser. Il faut pour cela indiquer le domaine concerné pour style-src :

default-src 'self'; style-src 'self' cdnjs.cloudflare.com

Cette stratégie nous permet de charger le CSS externe spécifié dans le header :

<!-- External resources -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/.../styles/default.min.css" type="text/css" />

Script externe

Nous souhaitons charger un script herbergé sur un serveur distant : cdnjs.cloudflare.com. Cette ressource est bloquée par défaut. Il nous faut l'autoriser :

default-src 'self'; script-src 'self' cdnjs.cloudflare.com

Cette stratégie nous permet de charger le Script spécifié dans le header :

<!-- External resources -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax.../highlight.min.js"></script>

Image externe

Pour finir, une image externe doit apparaître dans la page. Elle est également bloquée par défaut. Nous l'autorisons via img-src :

default-src 'self'; img-src 'self' fr.wikipedia.org

Nous acceptons ainsi l'affichage des images issues du domaine fr.wikipedia.org :

<img src="https://fr.wikipedia.org/static/images/project-logos/frwiki.png" alt="Wikipedia" />

Stratégie de sécurité

Pour afficher complétement notre page d'exemple, nous arrivons finalement à la stratégie de sécurité suivante :

default-src 'self'; style-src 'self' 'unsafe-inline' cdnjs.cloudflare.com; script-src 'self' 'unsafe-inline' cdnjs.cloudflare.com; img-src 'self' fr.wikipedia.org

La mise en place de CSP sur un site existant peut être extrêmement complexe, une politique moins restrictive (unsafe-inline) sera certainement nécessaire. L'avantage est qu'une fois établie vous maîtrisez les ressources externes et le respect des bonnes pratiques pour les intégrations futures.

Dans tous les cas n'oubliez jamais de vérifier la console du navigateur pour adapter si besoin votre stratégie de sécurité du contenu lors de sa mise en place.

Autres directives

Dans notre page d'exemple nous avons abordés les directives CSP (version 1) suivantes :

CSP présente d'autres directives à utiliser selon vos besoins :

Toutes les directive acceptent les valeurs none, self et une liste de domaine.

Les directives style-src et script-src acceptent également la valeur unsafe-inline. Il est possible d'indiquer unsafe-eval avec script-src pour autoriser l'utilisation de la méthode eval().

La directive img-src accepte data: pour les images en base64 (script-src 'self' data:)

D'autres directives existent en CSP version 2 et 3 (form-action, child-src...). Retrouvez l'ensemble des directives sur MDN Web Docs.

Google Analytics

Si comme une grande majorité de sites vous implémentez Google Analytics, vous pouvez appliquer la stratégie de base suivante :

default-src 'self'; script-src 'self' 'unsafe-inline' www.googletagmanager.com www.google-analytics.com; img-src 'self' www.google-analytics.com

Une question ? Rejoignez-moi sur le Fédivers :
@magentix@magentix.space