Wednesday, March 20, 2013

Pwning Your Privacy in All Browsers

I found new vectors and techniques for the detection attack from my previous post. There is a cross browser way to detect does certain URL redirect to another URL and is destination URL equal to TRY_URL. Interestingly in webkit you can check a few millions of TRY_URLs per minute (brute force).

There were similar attacks: CSS-visited-links leaking, cross domain search timing but the vector I am going to describe looks more universal and dangerous.

For sure this is not a critical vulnerability - 9 thousand years to brute force, say, /callback?code=j4JIos94nh (64**10 => 1152921504606846976 checking 25mln/min). Stealing random tokens does not seem feasible.

On the other hand the trick is WontFix. Every website owner can detect:
  1. if you are user of any other website - github, google, or, maybe, ?
  2. if you have access to some private resources/URLs (do you follow some protected twitter account)
  3. applications you authorized on any OAuth1/2 provider
  4. some private bits URL can contain: your Facebook nickname or Vkontakte ID.

I wrote a simple JS function and playground - feel free to use! (Sorry playground can be buggy sometimes, timeouts for Firefox should be longer and direct_history=false)

check_redirect(base, callback, [assigner])

base, String - starting URL. Can redirect to other URL automatically or only with some conditions (user is not logged in).

callback, Function - receives one Boolean argument, true when at least one of assigned URLs changed history instantly (URL was guessed properly) and false when history object wasn't updated (no URL guessed and all assignment were "reloading").

assigner, Function - optional callback, can be used for arbitary URL attempts and iterations (bruteforce). For example bruteforcing user id between 0-99:
  for(var i=0;i < 100;i++){

By default exploit assigns location=base+'#', making sure no redirect happened at all. Checking if user is logged in on any website is super easy:

  alert(result ? 'yes' : 'no');
(/private_path path is supposed to redirect user, in case he is not logged in.

Under the hood.

When you execute cross_origin_window.location=SAME_URL# and SAME_URL is equal to current location, browsers do not reload that frame. But, at the same time, they update 'history' object instantly.
This opens a timing attack on history object:
  1. Prepare 2 slots in history POINT 1 and POINT 2 that belong to your origin.
  2. Navigate cross origin window to BASE_URL
  3. Try to assign location=TRY_URL# and instantly navigate 2 history slots back.
  4. If the frame changed location to POINT 2 - your TRY_URL was equal to  REAL_URL and history object was updated instantly. If it is POINT 1 - TRY_URL was different and it made browser to start page reloading, history object was not changed immediately.
Wait, only Chrome supports cross origin access to "history" property - how can you make it work on other browsers?

Thankfully I found a universal bypass - simply assign a new location POINT 3 which will contain <script>history.go(-3)</script> doing the same job.

Bruteforce in webkit:

Vkontakte (russian social network) redirects to We can use map-reduce-technique from my previous Chrome Auditor script extraction attack.
Iterate 1-1kk, 1kk-2kk, 2kk-3kk and see which one returned to POINT 2. Repeat: 2000000-2100000, 2100000-2200000 etc.

It will take a few minutes to brute force certain user id between 1 and 1 000 000. Vkontakte has more than 100 millions of users - around 10 minutes should be enough.

Maybe performance tricks can make bruteforce loop faster (e.g. web workers). So far for(){} loop is only bottle neck here :)


Website owners can track customers' hobbies, facebook friends (predefined) visiting their blogs etc.

I have a strong feeling we dont want such information to be disclosed. It should be fixed just like CSS :visited vulnerability was fixed a few years ago.

Please, browsers, change your mind :)

Bonus - 414 and XFO

During the research I found unrelated but interesting attack vector, based on X-Frame-Options detection (Jeremiah wrote about it) and 414 error (URI is too long).

Server error pages almost never serve X-Frame-Options header and this trick can be applied for many popular websites using X-Frame-Options.

The picture below makes it clear (LONG_PAYLOAD depends on the server configuration, usually 5000-50000 symbols.):

Friday, March 15, 2013

The Achilles Heel of OAuth or Why Facebook Adds #_=_

This is a short addition to the previous rants on OAuth problems.

We've got Nir Goldshlager working on our side (he simply loves bounties and facebook does pay 'em). We both discovered some vulnerabilities in Facebook and we joined our forces to demonstrate how many potential problems are hidden in OAuth.

TL;DR: all oauth exploits are based on tampering with the redirect_uri parameter.
1 2 3 4
Here I demostrate 2 more threats proving that a flexible redirect_uri is the Achilles Heel of OAuth.

Open redirect is not a severe vulnerability on its own. OWASP says it can lead to "phishing". Yeah, it's almost nothing, but wait:

URI Fragment & 302 Redirects.
What happens if we load and responds with Status 302 and Location:

What about #DATA? It's a so called URI Fragment a.k.a location.hash and it's never sent on the Server-side.

The browser simply appends exactly the same value to a new redirect destination. Yeah, it will load

I have a feeling that is not a reasonable browser feature. I have an even stronger feeling that it opens yet another critical flaw in OAuth.

Any chain of 302 redirects
on any allowed redirect_uri domain (Client's domain, sometimes Provider's domain too —
to any page with attacker's Javascript (not necessarily open redirect)
= stolen access_token = Game Over

  1. auth/ 
  2. We get header:
  3. When the browser loads it gets following header
  4. And now the browser navigates to
  5. our Javascript leaks the location.hash

and this is...
Why Facebook Adds #_=_

Every time Facebook Connect redirects you to another URL it adds #_=_. It is supposed to kill a "sticky" URI fragment which the browser carries through chain of 302 redirects.

Previously we could use something like
in redirect_uri for other clients as a redirector to our app.

Fb auth for Other client -> Fb auth for Our client#access_token=otherClientToken -> 302 redirect to our app with #access_token of Other client.

Stolen Client Credentials Threat
Consumer keys of official Twitter clients
Is it game over? Currently - yes, both for OAuth1 and 2. Let me explain again response_type=code flow:

  1. redirect to /authorize url with redirect_uri param
  2. it redirects back to redirect_uri?code=123#_=_
  3. now server side obtains access_token sending client creds + used redirect_uri (to prevent leaking through referrer) + code

So if we have client creds we only need to find a document.referrer leaking redirect_uri (it can be any page with <img src="external site..."> or open redirector)

How would you obtain an access_token if redirect_uri would be static?

Oh, wait, there is no such way, because stolen credentials are not a threat if redirect_uri is whitelisted!

The funniest thing with all these rants
Most of our OAuth hacks pwn the Provider and its users (only the most-common vulnerability pwns the Client's authentication). 

This makes me really curious what the hell is wrong with Facebook and why don't they care about their own Graph security and their own users. Why do they allow a flexible redirect_uri, opening an enormous attack surface (their own domain + client's app domain)?

We simply play Find-Chain-Of-302-Redirects, Find-XSS-On-Client and Leak-Credentials-Then-Find-Leaking-Redirect_uri games. Don't they understand it is clearly their business to protect themselves from non ideal clients?

A good migration technique could be based on recording the most used redirect_uri for every Client (most of rails apps use and then setting those redirect_uris as the whitelisted ones.

P.S. Although, as bounty hunters, me, @isciurus and Nir are OK with the current situation. There is so much $$$ floating around redirect_uri...

P.S.2 feel free to fix my language and grammar, I get many people asking to elaborate some paragraphs.

Thursday, March 14, 2013

Brute-Forcing Scripts in Google Chrome

A while ago I found leaking document.referrer vulnerability and even used it to hack Facebook.

It's Chrome's XSS Auditor again (severity = medium, no bounty again). Can be used with websites serving X-XSS-Protection: 1;mode=block header.

TL;DR there is a fast way to detect and extract content of your scripts.

Even after fixing document.referrer bug it is still possible to detect if Auditor  banned our payload checking location.href=='about:blank' (I remind you that about:blank inherits opener's origin).

XSS Auditor searches every string between "<script>" and first comma or (first 100 characters) against POST body, GET query string and location.hash content.

Scripts, events on* and javascript:* links often contain private information.

Following values can be extracted:
<a onclick="switch_off()"
<a href="javascript:fn(123)

I simply send tons of possible payloads and if some page was banned (payload was found in the source code) then we found which bunch contains the real content of script.

'map-reduce'-like technique:
  1. open 10 windows (10 for simplicity, we can use 25 windows. if target has no X-Frame-Options it can be way faster with <iframe>s) containing specific bunches of possible payloads:
    from 1 to 1000000, from 1000000 to 2000000, from 2000000 to 3000000 etc
  2. if, say, second window was banned we should do the same procedure and open new windows with:
    from 1 000 000 to 1 100 000, from 1 100 000 to 1 200 000 etc and so on
  3. finally let's open 10 windows with  1 354 030, 1 354 031, 1 354 032, 1 354 033 ... and detect exact value hidden inside of <script> on the target page.
Definitely pwns your privacy but is it..

Feasible to brute force CSRF token?
1) XSS auditor is case insensitive, it makes bruteforce simpler: a-z instead of a-zA-Z.
2) csrf tokens happen to be quite short, 6-10 symbols.
3) 36 ^ 6 = 2176782336
4) every bunch checks 5 000 000 variants, We need 500 bunches
5) 25 windows per 10 seconds.  - 200 seconds.
6) now we found bunch containing real value — keep map-reducing this bunch and find the exact value - another 50-60 seconds
8) this value is case insensitive - to exploit it you should provide all possible cases of letters. around 50 requests and one of them will be successful.

200s (find 5 000 000 bunch containing real token)
+ 50s (detect real token in specific bunch)
+ 10s (send 50 malicious requests using <form>+iframe with all possible cases)
= 260s

approximately, exploitation time for different token size:
6 characters — 4.5 minutes
7 characters — 120 minutes
8 characters — 72 hours
Saving checked bunches in the cookie (next time you will continue with other bunches) + using location.hash (it is not sent on server side and can be very long) = plausible CSRF token brute force.

PoC — extract "<script>pin=162621"

  • use 8+ characters in the CSRF token
  • all kinds of detections are bad. There is no innocent detections. A small weak spot can be used in a dangerous exploit.
    Today I found a way to brute-force 25 000 000 URLs / minute using location.hash detection (WontFix!). And keep making it faster. 
Bonus, how to keep malicious page opened for a long time:

Friday, March 8, 2013

Hacking Github with Webkit

Previously on Github: XSS, CSRF (My github followers are real, I gained followers using CSRF on bitbucket), access bypass, mass assignments (2 Issues Reported forever), JSONP leaking, open redirect.....

TL;DR: Github is vulnerable to cookie tossing. We can fixate _csrf_token value using a Webkit bug and then execute any authorized requests.

Github Pages

Plain HTML pages can served from These HTML pages may contain Javascript code.
Custom JS on your subdomains is a bad idea:
  1. If you have document.domain='' anywhere on the main domain, for example xd_receiver, then you can be easily XSSed from a subdomain
  2. Surprise, Javascript code can set cookies for the whole * zone, including the main website.

Webkit & cookies order

Our browsers send cookies this way:

Cookie:_gh_sess=ORIGINAL; _gh_sess=HACKED;

Please have in mind that Original _gh_sess and Dropped _gh_sess are two completely different cookies! They only share same name.
Also there is no way to figure out which one is and which is
Rack (a common interface for ruby web applications) uses the first one:

cookies.each { |k,v| hash[k] = Array === v ? v.first : v }

Here's another thing, Webkit  (Chrome, Safari, and the new guy, Opera) sends cookies ordering them not by Domain ( must go first), and even not by httpOnly (they should go first obviously).
It orders them by the creation time (I might be wrong here, but this is how it looks like).

First of all let's have a look at the HACKED cookie.

PROTIP — save it as decoder.rb and decode sessions faster:

ruby decoder.rb

{:session_id=>"5a78a4fa3d808ba417e9cf29f255884d", :_csrf_token=>"ST/74zgHusv6fLw2Ru/okDlmsby8GwEZGhzm0WP3BS4="}
  1. on a subdomain we create _gh_sess=HACKED;
  2.''). Browser sends: Cookie:_gh_sess=ORIGINAL; _gh_sess=HACKED;
  3. Server responds: Set-Cookie:_gh_sess=ORIGINAL; httponly ....
  4. This made our HACKED cookie older then freshly received ORIGINAL cookie. Repeat request: 
  5.''). Browser sends:
    Cookie: _gh_sess=HACKED; _gh_sess=ORIGINAL;
  6. Server response: Set-Cookie:_gh_sess=HACKED; httponly ....
  7. Voila, we fixated it in httponly cookie. Now both and cookies have the same HACKED value.
  8. destroy the Dropped cookie, the mission is accomplished:   document.cookie='_gh_sess=;;expires=Thu, 01 Jan 1970 00:00:01 GMT';
Initially I was able to break login (500 error for every attempt). I had some fun on twitter. Github staff banned my repo. Then I figured out how to fixate "session_id" and "_csrf_token" (they never get refreshed if already present)

It will make you a guest user (logged out) but after logging in values will remain the same.


  1. let's choose our target. We discussed XSS-privileges problem on twitter a few days ago. Any XSS on github can do anything: e.g. open source or delete a private repo. This is bad and Pagebox technique or Domain-splitting would fix this.

    We don't need XSS now since we fixated the CSRF token.
    (CSRF attack is almost as serious as XSS. Main profit of XSS - it can read responses. CSRF is write-only).

  2. So we would like to open source github/github, thus we need a guy who can technically do this. His name is the Githubber.
  3. I send an email to the Githubber.
    "Hey, check out new HTML5 puzzle!"
  4. the Githubber opens the game and it executes the following javascript — replaces his _gh_sess with HACKED (session fixation):

  5. HACKED session is user_id-less (guest session). It simply contains session_id and _csrf_token, no certain user is specified there.
    So the Game asks him explictely: please Star us on github (or smth like this) <link>.
    He may feel confused (a little bit) to be logged out. Anyway, he logs in again.
  6. user_id in session belongs to the Githubber, but _csrf_token is still ours!
  7. Meanwhile, the Evil game inserts <script src=/done.js> every 1 second.
    It contains done(false) by default — it means, keep submitting the form to iframe :

    <form target=irf action="" method="post">
    <input name="authenticity_token" value="ST/74zgHusv6fLw2Ru/okDlmsby8GwEZGhzm0WP3BS4="
  8. At the same time every 1 second I execute on my machine:
    git clone git:// 
  9. As soon as the repo is opensourced my clone request will be accepted. Then I change /done.js: "done(true)". This will make Evil game to submit similar form and make github/github private again:

    <form target=irf action="" method="post">
    <input name="authenticity_token" value="ST/74zgHusv6fLw2Ru/okDlmsby8GwEZGhzm0WP3BS4="
  10. the Githubber replies: "Nice game" and doesn't notice anything (github/github was open sourced for a few seconds and I cloned it). Oh, his CSRF token is still ST/74zgHusv6fLw2Ru/okDlmsby8GwEZGhzm0WP3BS4=

    . (only cookies reset will update it)

Fast fix — now github expires cookie, if 2 _gh_sess cookies were sent on*.
It kills HACKED just before it becomes older than ORIGINAL.

Proper fix would be using or another separate domain. Blogger uses as dashboard and for blogs.

Last time I promised to publish an OAuth security insight

This time I promise to write Webkit (in)security tips in a few weeks. There are some WontFix issues I don't like (related to privacy).

I reported the fixation issue privately only because I'm a good guy and was in a good mood.
Responsible disclosure is way more profitable with other websites, when I get a bounty and can afford at least a beer.
Perhaps, tumblr has a similar issue. I didn't bother to check

Sunday, March 3, 2013

Contributions, 2012

All the buzz started after the commit that changed my life.
here are some highlights of my contributions, kind of digest.

I did some basic stuff — every newcomer starts with it :P
tried to convince people to fix CSRF, showcasing shitloads of CSRF-vulnerable sites
content type verification for JSON input

then spent a while on Rails security:
shameful but effective whitelist by default for mass assignment
getting rid of CSRF-vulnerable 'match' from routes.rb
added default_headers (X-Frame-Options etc) to make Rails apps secure from clickjacking by default
escape_html_entities for JSON.dump
sending nil using XML/JSON params parsers
some whining and XSS showcases on ^$ regexps (no success here)
my presentation with common cases in Moscow
and just plain posts on the future of Rails security  1,2.

played with OAuth:
hijacking account, another hijacking, some other vulns and final post — OAuth1, OAuth2, OAuth...? 

browser security
disclosure of URL and HASH (quite slow but race-condition standard is a vulnerability anyway)

theoretical posts on how to make Web perfect

rethinking cookies: Origin Only
Pagebox - XSS sandbox

Last year was not so bad, next should be more productive.

I am going to focus on defensive security (pagebox), Ruby security gems (for rack based apps), authorization techniques (OAuth is getting more popular != better) and financial security.

Also there is not much to do with Rails - it's well secured now! Thus I am choosing a new framework, likely, nodejs or lift.

Friday, March 1, 2013

OAuth1, OAuth2, OAuth...?

TL;DR OAuth2 sucks.

Please don't think about OAuth2 as about the next generation of OAuth1. They are completely different like colors: OAuth1 is the green version, OAuth2 is the red version
The biggest OAuth1 provider - Twitter.
I bet ($100!) they are not switching to OAuth2 in the near future. Pros and cons:
+ becoming compatible with the rest of social networks
- making authorization flow insecure, like the rest of social networks

I am not telling OAuth1 is super secure — it was vulnerable to session fixation a few years ago. If you made user to approve 'oauth_token' issued for your account, then you could use same oauth_token again and sign in his account on the Client website.

It was fixed in oauth1.a. Wait, read again: it was fixed. None of oauth2 vulnerabilities i pointed out in my previous posts a year ago was adressed in the spec. OAuth1 is straight, concise, explicit and secure protocol. OAuth2 is the road to hell.
Here we go!

OAuth2 core vulnerabilities - parameters

I have no idea why, who and, generally speaking, what the fuck, but we can transfer "response_type" as a parameter in URL (not as a setting of your Client).

Vector 1. Set respones_type=token in authorization URL and use specially crafted redirect_uri to leak access_token from URI fragment. You can add Chrome vulns like we did to hack Facebook. Hashbang #! is very nice bug too. Controlled piece of code (XSS) or mobile app like Nir did. BTW avoid pre-approved Clients ( Facebook Messenger with full permissions)

Stolen access_token = Game Over.

response_type must be a constant value in application settings.

Vector 2. If spec was implemented properly then tampering redirect_uri to other, "leaky", values is pointless. Because to obtain access token you must send redirect_uri value with client creds. If actual redirect_uri was "leaky" and not equal real redirect_uri Client will not be able to obtain access_token for this code.

Vk (vkontakte) was vulnerable to this attack until Sep 2012 - redirect_uri wasn't required to obtain token. An <img> on client website could leak victim's 'code' through referrer and attacker could use same 'code' to log in victim's account.

redirect_uri should be a constant value in application settings.

'scope' (actions User should allow for your Client) is also transfered in query string. I can't call it a major vulnerability, rather ugly user experience but anyway this is sort of stupid, no? "Pay as much as you wish, up to you"

Checking permissions after obtaining access_token is barely a right solution. This is rather a work around. Much better:

  1. if it's a parameter in URL then User should be able to remove some permissions by clicking [X]. This is my human rights, fuck yeah!
  2. if you really need some scope so badly - set it in Client's settings. Now you are 100% sure that User granted these permissions.

Authentication vs Authorization

authorize = permit 3rd party Client to access your Resources (/me, /statuses/new)
authenticate = prove that "this guy" is "that guy" which has account on the website.

OAuth2 was designed for authorization purposes only, but now in 90% (yep!) of cases people authenticate with it too! They simply use /me endpoint and find a record in database by external_user_id and provider_name.

It leads to bad things happenning.

Vector 3The Most Common OAuth2 Vulnerability or how I found CSRF based account hijacking vuln in most of OAuth2 implementations (omniauth, django etc)
At some extent state-by-default is attr_accessible-by-default (you know what i mean). I think state should be a compulsory parameter. Otherwise we can invent a new compulsory csrf_token parameter doing exactly same job with "mnemonic" name.
Any implementation w/o 'state' is vulnerable to CSRF. It leads to session fixation (Alice is logged in as Mallory) and account hijacking (Mallory can log in as Alice because Alice connected Mallory Provider Account).

Vector 4One access_token to rule them all or make sure access_token belongs to your Client

I truly love signed_request (Facebook feature!), it provides information about Client that issued this access_token. I think we should stop using response_type=token and use signed_request only. Because it is a reliable way for mobile and client side apps to obtain access_token+client_id of this token+current user_id (less round trips).
If we make redirect_uri constant and introduce encrypted_request (same data but encrypted with client_secret) it would be just perfectly secure flow.

XSS Threat, Vector 5
Nowdays XSS cannot steal user's session because of httpOnly flag (I assume everyone uses it).

Wait.. does OAuth2-based authentication have any protection from possible XSS on Client's website? No, attacker gets carte blanche:

  1. create iframe with response_type=token in authorize URL, then slice location.hash containing access_token
  2. use response_type=code and set wrong state. If CSRF protection is implemented 'code' will not be used and attacker can hijack User's account simply using this 'code' (within 10 minutes). Friendly reminder for Providers: 'code' must be used only once and within couple of minutes — I got $2000 bounty from facebook for finding replay attack.
  3. if csrf protection is NOT implemented (wtf?!) you can try a cool trick - 414 error. Set super long state (5000 symbols), facebook has no limits on state but Client server most likely has. 'code' won't be used because of server error - slice it!
  4. 'state' fixation + using callback with Attacker's code to hijack account
Voila, both stolen access_token + stolen account on Client

  1. always obtain access_token for every received code. But don't use it if state was wrong. "Destroy" code, or attacker will use it.
  2. clear state cookie as soon as it was used. "Destroy" state or attacker will use it.
If there is session fixation on Client website (I often seen absence of CSRF token on login forms) then Provider account fixation is routinely possible

Phishing Threat, Vector 6
Can you imagine how simple phishing is: create a Client with slightly different name: e.g. Skype -> Skype App/Skype IM, copy picture, description from original Client, use domain.
  1. Verified Clients. Similar popup near Client name, like we have for twitter accounts.
  2. Add information about amount of Users this app has. I won't authorize "Skype IM" having only 12 users with such app installed.

Client Leaked All the Tokens.
What is Provider gonna do if some Client leaked all access_tokens at once (SQL injection for example). I think there should be kind of Antifraud system, monitoring token usage. And I am developing one btw, let me know if you wish to try.

I might be missing something, but why can't we use access_token + client creds to obtain a new access_token? refresh_token = access_token. If access_token is stolen it cannot be refreshed w/o client creds anyway. If client creds are stolen too = Game Over.

Infinite access
Dear Providers, please, add valid_thru parameter, I want to set for how long the Client has access to my Resources.

Last but not least threat, MITM
SSL and encryption. OAuth2 relies heavily on https. This makes framework simpler but way less secure.

so, OAuth...?

This is a sad story. And I don't know what to do and how to fix it.
Who to join? OAuth2.a? i am just a russian scriptkiddie nobody listens to :/

Framework, not protocol, they said.

coming a mess big frameworks authorization in