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:
- 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
- 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 }
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="}
- on a subdomain we create _gh_sess=HACKED; Domain=.github.com
- window.open('https://github.com'). Browser sends: Cookie:_gh_sess=ORIGINAL; _gh_sess=HACKED;
- Server responds: Set-Cookie:_gh_sess=ORIGINAL; httponly ....
- This made our HACKED cookie older then freshly received ORIGINAL cookie. Repeat request:
- window.open('https://github.com'). Browser sends:
Cookie: _gh_sess=HACKED; _gh_sess=ORIGINAL; - Server response: Set-Cookie:_gh_sess=HACKED; httponly ....
- 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.
- destroy the Dropped cookie, the mission is accomplished: document.cookie='_gh_sess=; Domain=.github.com;expires=Thu, 01 Jan 1970 00:00:01 GMT';
It will make you a guest user (logged out) but after logging in values will remain the same.
Steps:
- 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). - So we would like to open source github/github, thus we need a guy who can technically do this. His name is the Githubber.
- I send an email to the Githubber.
"Hey, check out new HTML5 puzzle! http://blabla.github.com/html5_game" - the Githubber opens the game and it executes the following javascript — replaces his _gh_sess with HACKED (session fixation):
- 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. - user_id in session belongs to the Githubber, but _csrf_token is still ours!
- 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> - At the same time every 1 second I execute on my machine:
git clone git://github.com/github/github.git - 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> - 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)
@charliesome @homakov @github that would be awesome. open-source github.com/github/github :D
— Magnus Holm (@judofyr) March 5, 2013
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
Nice. One tactic recommended by OWASP to at least try to mitigate session fixation is to switch session-ids and CSRF tokens on successful login, and only set the user-id in the newly created session. Would that be effective here?
ReplyDelete> only set the user-id in the newly created session
Deletehow can you know if it was newly created?
yes, changing csrf token and session_id would be helpful, but there are other cookies and it is tedious ..
By "newly created session", I meant the one with the ID that just got assigned (so before the login event, it didn't exist).
Deletethis is really hard to track. app level session and user session are different things. If github would store user_id in active record session I would be able to hijack the whole account.
DeleteSo, will you publish the repo?
ReplyDeletewhat's the difference between
ReplyDeletedomain .github.com and domain github.com?
Thanks,
.github.com means *.github.com - Cookies is sent for any domain ending with github.com
DeleteSo, will you publish the repo?
ReplyDeleteI just read on hackernews your answer to this
someday.
DeleteYou could use the PATH attribute to set it to the specific github path request and all browsers will send your HACKED cookie first.
ReplyDeleteAnd the fix from Github can still be bypassed by Cookie Stacking (Knock out the original cookie and replace it with HACKED cookie)
Deleteyes, I forgot that PATH is more specific and i would need only 1 request. You are right.
DeleteGithub fixes it by refreshing csrf token after login - you cant hack main website now
Why don't Rails signed cookies protect against this? "If you change this key [Application.config.secret_token], all old signed cookies will become invalid." Why is the hacked cookie, which is not signed with secret_token, accepted by Rails at all? Shouldn't Rails be rejecting this cookie outright? Thank you.
ReplyDeletehacked cookie is legit signed cookie which i simply copy from my current browser session.
DeleteI see. Could this be fixed by creating a separate CSRF token for every session id?
Deleteit could be fixed by refreshing csrf token after login, because it will remove fixated value
DeleteCan you elaborate a little on the fragment bellow please?....i'm really trying hard to understand this.
ReplyDeleteso if on website www.github.com , there is javascript like
document.domain='github.com' .....what can happen?
"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".
i open x=window.open(path with such code) and do document.domain='site.com' on my subdomain. Both windows have same origin - site.com - and now subdomain can xss main domain
DeleteCould this attack be mitigated by using ensuring that the origin of the request URL matches the origin of the request referrer?
ReplyDeleteEg. This request was made to the origin github.com, but originated on page.github.com so discard it.
there is no way to know origin. csrf token is technique to do this
DeleteSorry, perhaps I'm misunderstanding the attack.
DeleteIn the demo, you make a HTTP AJAX request to github.com/protected/action from pages.github.com.
My understanding is that browsers include a "Referer" header when making an HTTP request - in this case, the AJAX request to the protected resource would have the Referer header value "pages.github.com".
GitHub could test whether origin of the URL of the protected resource matches the origin of the Referer header. In this case github.com != pages.github.com, so they could reject the request.
never rely on referrer,
Delete1 https doesnt send it
2 it's not AJAX, its just form
Can we fix this problem by signing all the cookies from the main domain? When server code receives the cookies string, it may just ignore all entries with incorrect signatures.
ReplyDeleteAll rails sessions are already signed. Thing is - i drop MY signed cookie. It's still signed but i know what csrf token is inside. And I 'fixate' it.
Delete