Monday, November 28, 2011

CSRF with JSON – leveraging XHR and CORS

Same Origin Policy (SOP) dictates cross domain calls and allows establishment of cross domain connections. SOP bypasses allow CSRF attack vector, an attacker can inject a payload on cross domain page that initiate a request without consent or knowledge of the target user. HTML 5 is having one more policy in place called CORS (Cross Origin Resource Sharing). CORS is a “response blind” technique and controlled by extra added HTTP header “orgin” and their variants but it allows request to hit the target in one way direction. Hence, it is possible to do one-way CSRF. It is possible to initiate CSRF vector using XHR-Level 2 on HTML 5 pages and can prove really lethal attack vector. XHR establishes a stealth connection and remains much hidden, XHR connection can be set using “withCredentials” as true along with POST method. It allows cookie to replay and helps in crafting successful CSRF scenario or session riding. Interestingly HTML 5 along with CORS allows performing file upload CSRF as well. It is possible to craft a JavaScript using XHR and inject JSON payload as cross domain. If server side code on JSON library is not validating the “Content-Type” then it will process the request and allows successful CSRF.
For example,

Here is a script which will do CSRF on cross domain.



Here, we have “Content-Type” as “text-plain” and no new extra header added so CORS will not initiate OPTIONS to check rules on the server side and directly make POST request. At  the same time we have kept credential to “true” so cookie will replay.

On the wire we can see following request.

















As you can see cookie is replayed and JSON POST has been initiated. We get following response back from application.
















Application processed the request and sent JSON back. It is clear case of CSRF. This can be applied to other streams as well.

12 comments:

Gynvael Coldwind said...

Hey,

Cool write-up, I've got worried that this opens a new SOP bypass hole at first read.
But I've done some digging and quite quickly realized that this isn't anything that wasn't already possible to do with standard form+.submit() XSRF exploitation method (with JSON in input's name in this case).
So I guess the good-old XSRF token still saves the day, even in this case.

Cheers :)

shreeraj said...

Yes. It is possible to do with form but you need to hide that "=" at the trailing end or somewhere in between the JSON. It may spoil JSON structure and may no get processed by server.

Gursev Kalra said...

Hi Shreeraj,

Does this look like a one more way to achieve successful CSRF with JSON?
http://gursevkalra.blogspot.com/2011/12/json-csrf-with-parameter-padding.html

Regards

Gynvael Coldwind said...

@Gursev Kalra
"one more way"? Nope, it actually looks like the exact thing I've written about in my previous comment, so that would make it "the old way" :)

Gursev Kalra said...

Gynvael,

So your comment does not talk about how to send a well formed JSON. Sending parameter in the name does leave a trailing '=' sign as Shreeraj indicated.

Gynvael Coldwind said...

@Gursev Kalra
Agreed that it does not say, but it's pretty obvious how to fix the formatting :)
Hence, it's not a "one more way".

Gursev Kalra said...

Gyanvael,

No worries. You win :)

Gynvael Coldwind said...

@Gursev Kalra
That being said... it's cool that you've actually written it down :)

kp said...

Hi,

I need help with writing code for CSRF for the appln using GWT framework.Its returning the content in JSON.So does the similar code given above by shreeraj works?or do v need any other new code.PLEASE help.

Gil Bar-Tur said...
This comment has been removed by the author.
Gil Bar-Tur said...

Tried it now and can seem to get it to work.
this is my code-

function callServer() {
var xmlhttp = new XMLHttpRequest();

xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
document.getElementById('retrievedData').innerHTML = xmlhttp.responseText;
}
}


xmlhttp.open('GET', 'http://localhost:1077/DoSomething.ashx?p=' + Math.random(), true);
xmlhttp.setRequestHeader('Content-Type', 'text/plain').value);
xmlhttp.withCredentials = "true";
xmlhttp.send('hello');
}

can you tell why my csrf attack doesnt work?

Satish Kumar said...

It works fine when we set Content-Type to "text/plain".
But I need set 'Content-Type' to 'application/json'
I just replaced 'text/plain' with 'application/json' and the HTTP method was set to 'OPTIONS' and content-type was not set.

Is there any way to content-type to 'application/json'.

Thanks