Tuesday, January 28, 2014

A header is said to be a simple header if the header field name is an ASCII case-insensitive match for Accept, Accept-Language, or Content-Language or if it is an ASCII case-insensitive match for Content-Type and the header field value media type (excluding parameters) is an ASCII case-insensitive match for application/x-www-form-urlencoded, multipart/form-data, or text/plain.

CORS is really strict about headers. As you can see only Accept/Accept-Language/Content-Language can be replaced with arbitrary field values.

To be honest, I would get rid of all CORS headers but one: to perform state-changing requests you need to know csrf_token anyway, to read the response you need suitable Access-Control-Allow-Origin. The rest of headers is just legacy bullshit to "save" already broken apps. There's no point to "allow" headers, nor withCredentials.

I really hope pre-approved headers will be added, because currently CORS sends twice more requests than needed, which makes it slower than alternative cross domain transports & overloads API with pointless payloads.

Sunday, January 26, 2014

TL;DR Every website with "Connect Facebook account and log in with it" is vulnerable to account hijacking. Every website relying on signed_request (for example official JS SDK) is vulnerable to account takeover, as soon as an attacker finds a 302 redirect to other domain.

I don't think these will be fixed, as I've heard from the Facebook team that it will break compatibility. I really wish they would fix it though as you can see below, I feel these are serious issues.

I understand the business reasons why they might choose so, but from my perspective when you have to choose between security and compatibility, the former is the right bet. Let me quickly describe what these bugs are and how you can protect your websites.

This form logs victim in attacker's arbitrary account (even if user is already logged in, logout procedure is trivial). Now to all OAuth flows Facebook will respond with Attacker's profile information and Attacker's uid.

Every website with "Connect your Facebook to main account to login faster" functionality is vulnerable to account hijacking as long as attacker can replace your identity on Facebook with his identity and connect their Facebook account to victim's account on the website just loading CLIENT/fb/connect URL.

Once again: even if we cannot inject our callback with our code because of state-protection, we can re-login user to make Facebook do all the work for us!

Almost all server-side libraries and implementations are "vulnerable" (they are not, it's Facebook who's vulnerable!) : omniauth, django-social-auth, etc. And yeah, official facebook-php-sdk.

Mitigation: require CSRF token for adding a social connection. E.g. instead of /connect/facebook use /connect/facebook?authenticity_token=123qwe. It will make it impossible for an attacker to start the process by himself.

Facebook JS SDK and #signed_request
Since "redirect_uri" is flexible on Connect since its creation, Facebook engineers made it a required parameter to obtain "access_token" for issued "code". If the code was issued for a different (spoofed) redirect_uri, provider will respond with mismatch-error.

signed_request is special non-standard transport created by Facebook. It carries "code" as well, but this code is issued for an empty redirect_uri = "". Furthermore, signed_request is sent in a #fragment, so it can be leaked easily with any 302 redirect to attacker's domain.

And guess what — the redirect can even be on a subdomain. of our target! Attack surface gets so huge, no doubt you can find a redirecting endpoint on any big website.

Basically, signed_request is exactly what "code" flow is, but with Leak-protection turned off.

All you need is to steal victim's signed_request with a redirect to your domain (slice it from location.hash), then open the Client website, put it in the fbsr_CLIENT_ID cookie and hit client's authentication endpoint.

Finally, you're logged in as the owner of that signed_request. It's just like when you steal username+password.

Mitigation: it's hard to get rid from all the redirects. For example Facebook clients like soundcloud, songkick, foursquare are at the same time OAuth providers too, so they have to be able to redirect to 3rd party websites. Each redirect to their "sub" clients is also a threat to leak Facebook's token. Well, you can try to add #_=_ to "kill" fragment part..

It's better to stop using signed_request (get rid of JS SDK) and start using (slightly more) secure code-flow with protections I mentioned above.

Conclusion
In my opinion I'd recommend not using Facebook Connect in critical applications (nor with any other OAuth provider). Perhaps it's suitable quick login for a funny social game but never for a website with important data. Use oldschool passwords instead.

If you must use Facebook Connect, I recommend whitelisting your redirect_uri in app's settings and requiring user interaction (clicking some button) to start adding a new connection. I really hope Facebook will change their mind, to stay trustworthy identity provider.

As of Dec 7 2014 Facebook fixed first bug but there's a way to bypass it. I am not going to publish it because I don't want @isciurus to patch it again :)

Sunday, January 19, 2014

require 'sinatra'
get '/' do
redirect params[:to] if params[:to].start_with? 'http://host.com/'
end

Let's load /?to=http://host.com/?%0dX-Header:1 and see a new "injected" X-Header in Chrome (not in FF) because %0d aka \r is considered by Chrome as a valid headers' delimiter (don't really agree with this feature). OK, bad news are: Rack is the root of the problem. It uses \n internally as a delimiter for "arrays of cookies" so it blocks \n-based injections, but \r-based are working fine.

This means all web ruby software relying on Rack headers validation is vulnerable to header injection. Technically even Rails, they have "monkey patch" removing \0\r\n from "Location" header, but the rest of headers stay untouched.

Let's talk about header injection
Now it is not useful, IMO. Yes you can create some new headers and such, but what can you do with it finally? Set-Cookie to mess with session_id/csrf_token is only option, am I right?

Overall it is a low-severity issue which can only insert new cookies. When browser sees non-empty Location it ignores all other headers but Set-Cookie. All you can do is BOMBIN'

Saturday, January 18, 2014

TL;DR I can craft a page "polluting" CDNs, blogging platforms and other major networks with my cookies. Your browser will keep sending those cookies and servers will reject the requests, because Cookie header will be very long. The entire Internet will look down to you.

I have no idea if it's a known trick, but I believe it should be fixed. Severity: depends. I checked only with Chrome.

We all know a cookie can only contain 4k of data.

How many cookies can I creates? Many!

What cookies is browser going to send with every request? All of them!

How do servers usually react if the request is too long? They don't respond, like this:

If you're able to execute your own JS on SUB1.example.com it can cookie-bomb not only your SUB1 but the entire *.example.com network, including example.com.

Just set lots of 4k long cookies with Domain=.example.com so they will be sent with every request to *.example.com.
All requests will be ignored, because servers never process such long requests (the "Cookie" header will be like half a megabyte).

Victim is sad and crying. No more blogspot. No more github.io. Such sad user. Not wow.

It will last until the user realizes he needs to delete his cookies. Not all human beings are that smart though.

Proofs of Concept

Tip for hackers: you can "block" some exact path by specifying ;path=/some_path in the cookie bombs attributes. Your personal censorship!

Tip for browsers: limit amount of cookies on .example.com or send only sane number of them, but i'm not sure it's a pragmatic way.Tip for admins: instead of sub1.example.com use sandbox.sub1.example.com, which will limit impact of the cookie bomb to .sub1.example.com zone.Tip for users: if you was cookie-bombed remove "bombs" here:

Tuesday, January 14, 2014

If it wasn't MtGox I wouldn't even mention it — XSS/fixation/etc are web sec routines, and are not worth a blog post.

But it *is* MtGox. When I started checking bitcoin-related websites it was my target #1. First XSS was found in 5 minutes on payments.mtgox.com, few mins later I discovered session fixation leading to account takeover. Long story short, here's exploit:

2. Put your payload in window.name and redirect to "https://payment.mtgox.com/38131846-a564-487c-abfb-6c5be47bce27/e6325160-7d49-4a69-b40f-42bb3d2f7b91?payment[cancel]=cancel" (GET-accessible action). MtGox has X-Frame-Options so it won't work in iframe.

4. Get some guest SID with server side and fixate it using this XSS. It's called Cookie tossing, and our cookie shadows original SESSION_ID because more specific Path-s are sent first.

document.cookie="SESSION_ID=SID; Domain=.mtgox.com; Path=/code"

5. Close the window.

6. Someday user logs in, and his session will stay the same SID. Your server script should run cron task every 5 minutes, checking if SID is still "guest". As soon as user signs in you can use fixated SID to perform any actions on behalf of his account - "Session riding".

Timeline

Jan 11 - vuln reported

Jan 14 - vuln accepted and fixed in 3 hours.

FYI use nils@tibanne.com as "security@mtgox.com" (MtGox doesn't have neither bounty program nor email for reports).

Recap:
Even top-notch bitcoin websites are not as secure as payment providers should be. This vulnerability is really easy to find, so I suspect it's been used in the wild. Use 2 factor auth.

In no time bitcoin currency got some good value, but security level of bitcoin websites didn't play along.

Monday, January 13, 2014

TL;DR ///host.com is parsed as relative-path URL by server side libraries, but Chrome and Firefox violate RFC and load http://host.com instead, creating open-redirect vulnerability for library-based URL validations. This is WontFix, so don't forget to fix your code.

Think as developer.
Say, you need to implement /login?next_url=/messages functionality. Some action must verify that the next URL is either relative or absolute but located on the same domain.

What will you do? Let’s assume you will start with the easiest option - quick regexps and first-letters checks.
1. URL starts with /
Bypass: //host.com
2. URL starts with / but can’t start with //
Bypass: /\host.com
3. At this point you realize your efforts were lame and unprofessional. You will use URL parsing library, following all the RFC-s and such - \ is not allowed char in URL, all libraries wouldn't accept it. Much RFC, very standard.

1 is for path, 2 is for host, 3 is for ?
https://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#urls
>A scheme-relative URL is "//", optionally followed by userinfo and "@", followed by a host, optionally followed by ":" and a port, optionally followed by either an absolute-path-relative URL or a "?" and a query.
>A path-relative URL is zero or more path segments separated from each other by a "/", optionally followed by a "?" and a query.
A path segment is zero or more URL units, excluding "/" and "?".

Given we have base location as https://y.com and where will following URLs redirect?
/host.com is a path-relative URL and will obviously load https://y.com/host.com
//host.com is a scheme-relative URL and will use the same scheme, https, hence load https://host.com
The question is where ///host.com (also ////host.com etc) will redirect?

Out of question, it is a path-relative URL too. Third letter is /, so it can’t be a scheme-relative URL (which is only //, followed by host which doesn’t contain slashes).
It has 2 URL units which are empty strings, concatenated with / and supposed to load https://y.com///host.com

The thing is, both Chrome and Firefox parse it as a scheme-relative URL and load https://host.com Safari parses it as a path. Opera loads http://///x.com (?!).

http://www.sakurity.com/triple?to=///host.com#secret
where #secret can be access_token or auth code

Use a Library, Luke
Functionality like /login?to=/notifications is very common so can be found almost on any website. Now the question is how Good Programmers validate it?
As proved in the beginning of the post, best practise would be to use URL parser.

PHP (mad behavior, quite expected)
print_r( parse_url("///host.com"));
This doesn’t work (but should). You might be happy but wait, while all languages don’t parse /\host.com because it is not valid PHP gladly parses it as a path.

print_r( parse_url("/\host.com"));
Thus PHP is vulnerable too.

Security implications
Basically, with /\host.com and ///host.com we can get an open redirect for almost any website. Yeah. No matter you have “home made” parser or reliable server-side library - most likely it's vulnerable.

The only good protection is to respond with full path:
Location: http://myhost/ + params[:next]

Besides phishing, redirects can exploit many Single Sign On and OAuth solutions: 302 redirect leaks #access_token fragment, and even leads to total account takeover on websites with Facebook Connect (details soon).

TL;DR How can we use technique created to protect websites for Evil? (We used XSS Auditor for Evil before) There's a neat way: taking advantage of CSP we can detect whether URL1 does redirect to URL2 and even bruteforce /path of URL2/path. This is a conceptual vulnerability in CSP design (violation == detection), and there's no obvious way to fix it.Demo & playground: http://homakov.github.io/csp.html

What is CSP

CSP header tells browsers what "inline scripts" and/or 3rd party scripts (you must specify hosts) can be loaded/executed for this page. So if browser sees anything not allowed by the header it raises an exception (Violation). Chrome 16+, Safari 6+, and Firefox 4+ support CSP. IE 10 has very limited support.As soon as user hits HOST1 he gets 302 redirect to HOST2 and that's where violation happens, because HOST2 is not allowed by CSP. It sends POST request to /q with "blocked-uri":"http://HOST2" (only hostname, path is stripped for similar security reasons).

You can see the exception in console of Web Inspector (but you cannot catch it with JS).

Just console notice seemed not enough to standard's authors, thus they introduced "report-uri" directive which notifies host about the violation. User-agent sends POST request with JSON details about Violation.

All kinds of detections are bad.

Given HOST1 redirects to HOST2 for some users, we can detect if current user was redirected: let's specify HOST1 as allowed host in our CSP using following HTML:

We define in CSP ranges of URLs from id=1 to id=10000, from id=10000 to id=20000 and so on. Every violation will disclose if target id was listed in crafted CSP header. As soon as we reach bunch not raising an exception (10 seconds timeout is enough), we can split that bunch into 10 smaller bunches, 1000 per each, until we figure out target id:

<iframe src="/between?from=20000&to=21000"></iframe>

<iframe src="/between?from=21000&to=22000"></iframe>

For social network VK (m.vk.com/photos redirects to m.vk.com/photosMYID) process of guessing will take unfeasibly long while, because they have over 100 million accounts. But for smaller websites ranges are smaller and detection is real.

Achievement 3: with map-reduce style bruteforce we can detect /path (not ?query) of redirect destination (URL redirects to HOST2/path).
Onerror/onload detection
You might ask: "Will removal of report-uri directive fix this issue?"
No. It is more high level issue. Since CSP blocks not allowed URLs, it doesn't fire up "onload" event, just as X-Frame-Options block doesn't fire it up. Exploit gets even faster:
frame.src = 'data:text/html,<meta http-equiv="Content-Security-Policy" content="img-src '+allowed+';"><img src="'+url+'" onerror=parent.postMessage(1,"*") onload=parent.postMessage(2,"*")>'Achievement 4: we can really quickly detect if certain URL redirects to HOST2 utilizing onload/onerror events.
Mitigation?
IMO we should remove 'report-uri' and invoke 'onload' event even if URL was blocked by CSP. I know, it is confusing approach, but it's the only way to get rid of detection.
My initial report (31 Oct) was marked as Severity: none by Chrome and not going to be fixed soon. It will require fixing the standard anyway.
Related issue Pwning privacy with hash based URL detection

Thursday, January 9, 2014

Guess what - there's exact same vulnerability in Paypal Express Checkout flow (they will not fix it). Furthermore, tons of other payment-related providers can be vulnerable to the same attack. How does it work?

OAuth1-like flows are based on request_token/invoice id (for example https://bitpay.com/invoice?id=INVOICE). Before using %PROVIDER_NAME% your CLIENT is supposed to make API call with all parameters (client_id, client_secret, redirect_uri, amount, currency, etc). Provider will respond with request_token=TOKEN1. This is your token and it's tied to your Client account.

Wow, secure! - no URL parameters tampering like we have in OAuth2 (browse my blog a bit to find kilobytes of rants about it). But OAuth2 returns "code" which is not guessable. OAuth1 returns the same request_token it received in initial URL. Voila, fixation.

Thing with Paypal is: no matter who pays this invoice - you will only need to visit Client-Return-URL?token=TOKEN1 to add funds someone paid.

How can we trick a victim into paying our TOKEN1?
1. John, you must buy this book! (x=window.open('BOOK STORE'))
2. John clicks Pay with Paypal and gets redirected to Paypal?token=TOKEN2
3. our opener-page should somehow detect that John is about to pay. Hash-based cross domain URL detection can help. We fixate x.location to Paypal?token=TOKEN1
4. Payment is done, user is redirected to Client-Callback?token=TOKEN1 and doesn't get the book. TOKEN1 was issued for other Client session and only attacker can use it on callback.
5. Attacker uses TOKEN1 on return URL, Client does API call to make sure TOKEN1 is paid = Attacker gets the book.

Successfully tested this hack on Namecheap. This is what victim sees on first step (valid and trustworthy page)

Error after paying for TOKEN1

Attacker visits Callback?token=TOKEN1 to get victim's funds

Mitigation
Provider should issue another oauth_verifier random nonce to make sure the user using callback is the payer.

Is it really severe bug? I don't think so, but it should be fixed for sure.

Thursday, January 2, 2014

Playing with 302-based header injection (majority of web servers is not vulnerable to it btw) i found one tricky neat bug which can be really useful to leak ?query data by putting them in the #fragment.

Remark about the difference: fragment might seem to be more secure than query - no, I don't think so. There are just thousands of open-redirects out there leaking access_token-s. I personally found an open redirect leaking user's token on 2 out of 3 huge websites i checked. I only stopped looking for open redirects because I don't exploit and there's no market for "facebook access_tokens" (let's create one?)... whatever

Yes, ?query can be leaked with Referrers but you can easily deny them - you never can't be sure you don't have open redirects.

Tip 1. do not send sensitive info in #fragment because 302 redirect will leak this data.

Problem: many web servers are configured in a way to redirect http://site.com/%23lol to http://www.site.com/#lol - they kill initial encoding, putting query data in location.hash. And this is a vulnerability.

To demonstrate the bug in the wild I created this demo. Hard Mode solution by the way: http://www.sakurity.com/issue_token?hard_mode=1&uri=http://sakurity.com/triple?to=//egorhomakov.com%2523Any ?private_info can be turned into #private_info and be leaked easily with a 302 redirect.

Chain looks like this
Provider Issues a Token -> Client-WWW-redirector%23?Token -> Client-redirector#?Token -> Evil#?Token.
There were basically only two ways servers could "screw" path encoding: https and www related redirects.

Check if your server turns %23 into # and patch it if you don't want your Single Sign On to be hacked.
Be careful with encodings. Many servers are configured in a wrong way + Microsoft IIS is vulnerable by default.