# Comment les attaquants contournent-ils une Content Security Policy ?

> CDN autorisés qui hébergent n'importe quel code, endpoints JSONP, redirections ouvertes, base-uri absent et unsafe-eval : comment une politique d'apparence stricte se fait contourner, et pourquoi un contournement réussi n'apparaît jamais dans vos rapports de violation.

- Canonical: https://info.centralcsp.com/fr/articles/csp-bypass-techniques/
- Published: 2026-09-21
- Language: fr
- Publisher: CentralCSP (https://centralcsp.com)

Une Content Security Policy qui a l'air stricte dans un fichier de configuration et une Content Security Policy qui arrête une XSS sont deux choses différentes. L'écart est large. Sur les 761 345 domaines de notre rapport State of the Web 2026, 22,0 % publient une CSP, et 0,97 % en publient une qui passe vraiment. L'essentiel de la différence ne tient pas à des directives absentes. Il tient à des directives présentes et contournées.

Tous les contournements décrits ici ont la même forme : l'attaquant dispose déjà d'une injection HTML, et la politique est la seule chose entre cette injection et l'exécution de code. Le contournement trouve quelque chose que la politique autorise déjà et s'en sert comme chargeur. Rien n'est exploité dans le navigateur. La politique est simplement respectée.

Ce dernier point compte davantage que les techniques, autant le dire tout de suite : un contournement réussi ne produit aucun rapport de violation. Le navigateur a laissé passer le script, parce que la politique le lui disait.

## Les hôtes autorisés qui exécutent n'importe quoi

C'est le gros morceau, et la raison de la mauvaise réputation des listes blanches.

```http
Content-Security-Policy: script-src 'self' https://cdn.example-libraries.net
```

Cela paraît rigoureux. Une origine, un CDN réputé. Le problème, c'est qu'un CDN public de bibliothèques héberge des milliers de fichiers, dont certains exécuteront une chaîne que vous contrôlez. Un `<script src="https://cdn.example-libraries.net/some/old/framework.js">` injecté est parfaitement conforme à la politique. Si ce vieux framework fait du templating côté client, l'attaquant dispose d'un évaluateur d'expressions tournant sur votre page avec les privilèges de votre origine.

La recherche est directe sur l'ampleur du phénomène. Une équipe de Google ayant étudié des politiques réelles, publication à ACM CCS en 2016, a trouvé la grande majorité des politiques à liste blanche contournables sans difficulté, le plus souvent via une seule entrée de la liste. Dix ans après, le constat tient, parce que la cause n'a pas bougé : vous ne maîtrisez pas ce qu'une origine tierce sert, et la politique autorise l'origine, pas le fichier.

Même catégorie, versions plus banales :

- Un joker du type `https://*.example.com` où un sous-domaine sert des téléversements utilisateur, une application de recette ou un vieux site marketing que plus personne ne maintient.
- Votre propre origine `'self'`, si elle expose un endpoint de téléversement capable de servir un fichier avec un type MIME JavaScript, ou un endpoint qui reflète une entrée dans une réponse `.js`.
- Un compte CDN où n'importe quel client peut téléverser, sur un nom d'hôte partagé.

Le correctif n'est pas une meilleure liste. C'est de ne pas avoir de liste.

## Les endpoints JSONP, de l'injection de callback par conception

Un endpoint JSONP prend un nom de callback et emballe sa réponse JSON dedans :

```
https://api.allowed-vendor.example/data?callback=myHandler
```

Cela renvoie `myHandler({...})`, donc un script. Ce que vous placez dans `callback` se retrouve en code exécutable en tête de réponse. Si `api.allowed-vendor.example` figure dans votre `script-src`, l'attaquant injecte une balise script pointant dessus avec le callback de son choix, et la politique laisse passer.

Beaucoup de prestataires exposent encore ce genre d'endpoints sur des origines qui finissent dans les listes blanches d'analytics et de publicité. Avant d'accorder une origine à `script-src`, la question n'est pas « est-ce que je fais confiance à cette société » mais « y a-t-il sur ce nom d'hôte quoi que ce soit qui transforme un paramètre d'URL en code ». Vous ne pouvez en général pas y répondre, ce qui ramène encore aux nonces.

## Les redirections ouvertes, et l'inutilité des listes blanches par chemin

On essaie souvent de resserrer une liste blanche en ajoutant un chemin :

```http
Content-Security-Policy: script-src https://vendor.example/safe/scripts/
```

Puis on découvre une redirection ouverte sur cet hôte. La CSP abandonne délibérément la composante de chemin lorsqu'elle suit une redirection, afin que les politiques ne divulguent pas les cibles de redirection via les messages d'erreur. Résultat : une redirection atteignant n'importe quel point d'une origine autorisée vide entièrement la restriction de chemin de sa substance.

Restreindre par chemin rapporte moins qu'il n'y paraît. Considérez l'origine comme l'unité de confiance, puisque c'est ce que fait le navigateur en pratique.

## Les deux directives qu'on oublie

`base-uri` est la discrète. Si un attaquant parvient à injecter une balise `<base href="https://attacker.example">`, toute URL de script relative de votre page se résout chez lui et non chez vous. Votre propre `<script src="/app/main.js">`, parfaitement légitime, se charge désormais depuis son serveur. Un nonce ne vous sauve pas ici, parce que la balise chargée est votre balise, porteuse de votre nonce.

On la pose et on n'y pense plus :

```http
base-uri 'none'
```

`object-src` est l'autre. Les plugins sont morts, mais `<object>` et `<embed>` peuvent encore mener à de l'exécution de script sur certains moteurs, et `default-src` ne couvre pas le cas aussi sûrement qu'on le croit. `object-src 'none'` ne coûte rien.

Le Builder de CentralCSP fixe les deux à `'none'` dans toutes les politiques qu'il génère, exactement pour cette raison. Ce sont des gains gratuits, et ils ressortent en findings sur des politiques par ailleurs respectables plus souvent que tout le reste.

## unsafe-eval, et les bibliothèques qui l'exigent

`'unsafe-eval'` réactive `eval()`, `new Function()` et les arguments en chaîne de `setTimeout`. S'il est dans votre politique, une charge injectée qui atteint l'un de ces points obtient une exécution complète, quoi que la politique dise par ailleurs sur les sources.

Il est en général là parce qu'une bibliothèque l'a réclamé : un moteur de templates, une vieille bibliothèque de graphiques, une configuration de tag manager qui construit des fonctions depuis des chaînes. Cette bibliothèque est la politique. Qui peut influencer les chaînes qu'elle compile peut exécuter du code.

À défaut de le retirer tout de suite, sachez au moins quelle dépendance l'exige et inscrivez la date de retrait à côté. D'expérience, la réponse est souvent que la bibliothèque a été mise à jour il y a deux ans et n'en a plus besoin, et que personne n'est retourné vérifier.

## Ce que strict-dynamic apporte réellement

Une politique à nonce avec `'strict-dynamic'` supprime toute la première moitié de cet article :

```http
Content-Security-Policy: script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none'
```

Dès que `'strict-dynamic'` est présent, l'autorisation par hôte est désactivée. Vos entrées de CDN cessent de compter, puisqu'elles cessent d'être consultées. Un script porteur du bon nonce s'exécute, les scripts qu'il charge par programme héritent de cette confiance, et une balise injectée sans le nonce ne s'exécute pas, quelle que soit l'origine visée. C'est la forme à viser.

Ce n'est pas étanche, et les failles restantes méritent d'être nommées. Un nonce réutilisé d'une réponse à l'autre (hébergement statique, cache CDN de page entière) est un mot de passe écrit sur la porte, ce qui explique que [nonces et hashes ne soient pas interchangeables](/fr/articles/csp-nonces-vs-hashes/). Un nonce renvoyé dans la page par une réflexion est un nonce divulgué. Et un script légitime porteur du nonce qui passe une entrée attaquante à `innerHTML` ou à un compilateur de templates reste un trou, parce que `'strict-dynamic'` a fait confiance à votre script et que votre script a fait confiance à l'attaquant.

## Le point gênant : rien de tout cela n'apparaît dans les rapports

Revenons au début. Un rapport de violation est la trace de ce que le navigateur a refusé. Un contournement est ce que le navigateur a autorisé. Les deux ensembles ne se recoupent pas.

Une politique peut donc se faire contourner quotidiennement pendant que le tableau de bord des violations reste propre. Plus propre, même, que celui d'un site à politique stricte où de la casse légitime fait du bruit.

Ce qui l'attrape, c'est une trace de ce qui s'est exécuté. Le rapport de hash CSP (`'report-sha256'` dans la politique) fait remonter par le navigateur chaque script exécuté avec son SHA-256, ce qui produit un inventaire issu de sessions réelles plutôt que d'un crawl : chaque script, par page, première et tierce partie, avec historique de hash, première et dernière apparition. Un script servi depuis un CDN autorisé que personne dans l'équipe ne reconnaît y apparaît comme un nouveau hash sur une page où il n'a rien à faire. C'est le signal qu'un flux de violations ne peut pas donner, et c'est [la façon de construire un inventaire de scripts sans agent](/fr/articles/script-inventory-without-an-agent/).

L'appariement est tout l'enjeu. Les violations disent ce que votre politique bloque et où elle est trop serrée. Les hashes de scripts disent ce que votre politique autorise et où elle est trop lâche. N'en exploiter qu'un des deux laisse à moitié aveugle, ce qui est à peu près la situation de la plupart des déploiements CSP.

## Une courte liste de contrôle

1. Passez votre politique actuelle dans l'[évaluateur CSP gratuit](https://centralcsp.com/fr/tools/csp-evaluator/). Il signale les origines propices au JSONP, les directives dangereuses et l'absence de `base-uri` ou d'`object-src`, noté sur 100 avec un correctif par finding. Une minute, sans compte.
2. Posez `object-src 'none'` et `base-uri 'none'` aujourd'hui. Aucune migration, aucun risque.
3. Trouvez quelle dépendance réclame `'unsafe-eval'`. Vérifiez si c'est encore le cas.
4. Planifiez le passage de la liste blanche au nonce avec `'strict-dynamic'`. Construisez-la sur du trafic réel plutôt qu'au jugé, ce que couvre [transformer les rapports CSP en politique](/fr/articles/turn-csp-reports-into-policy/).
5. Activez le rapport de hash de script, pour que les scripts autorisés par votre politique soient inventoriés et non supposés.

L'étape quatre prend des semaines. Les étapes deux et cinq prennent un après-midi à elles deux et couvrent les modes de panne qu'une politique d'apparence stricte dissimule.

## Questions fréquentes

### Un contournement de CSP apparaît-il dans mes rapports de violation ?

Non, et c'est ce qui surprend. Un contournement fonctionne précisément parce que le script injecté satisfait la politique : le navigateur l'autorise et ne génère aucune violation. Les rapports de violation montrent ce qui a été bloqué. Pour voir ce qu'un contournement a fait, il faut une trace de ce qui s'est exécuté, et c'est ce que donne le rapport de hash de script : chaque script exécuté, avec son SHA-256.

### Une CSP à liste blanche vaut-elle quand même le déploiement ?

Elle vaut mieux que rien et bien moins qu'une politique à nonce. Une recherche Google présentée à ACM CCS en 2016 a montré que la grande majorité des politiques à liste blanche étudiées étaient contournables sans effort, en général via un seul hôte de la liste servant du JSONP ou du contenu utilisateur. Si vous exploitez déjà une liste blanche, gardez-la et planifiez le passage aux nonces avec strict-dynamic plutôt que d'affiner la liste.

### strict-dynamic rend-il une politique inviolable ?

Il supprime toute la catégorie des contournements par liste blanche, puisque les sources par hôte sont ignorées dès qu'il est présent. Il ne vous protège ni d'un nonce qui fuite dans la page, ni d'unsafe-eval, ni d'un script légitime porteur du nonce qui charge une entrée contrôlée par l'attaquant. C'est le meilleur ajout unique possible, pas une réponse complète.

### Pourquoi object-src compte-t-il si personne n'utilise de plugins ?

Parce que default-src ne le couvre pas toujours comme on l'imagine, et qu'un élément object ou embed autorisé peut encore mener à de l'exécution de script dans certains moteurs. Mettre object-src à none ne coûte rien, aucun site moderne n'en a besoin, et l'oublier reste l'un des findings les plus fréquents sur des politiques par ailleurs correctes.
