赏金$10000的GitHub漏洞

公众号程序猿DD

共 4034字,需浏览 9分钟

 · 2020-11-07

点击上方蓝色“程序猿DD”,选择“设为星标”

回复“资源”获取独家整理的学习资料!

本文来源: https://www.anquanke.com/post/id/220963

本文是翻译文章,文章原作者William Bowling,文章来源:devcraft.io
原文地址:https://devcraft.io/2020/10/19/github-gist-account-takeover.html

0x01 前言

安全研究员William Bowling在研究GitHub用于生成url的每种方法过程中,找到了可用于创建所需令牌的方法url_for,并实现了Gist账户接管,最终获得$10000赏金。

0x02 漏洞发现

url_for方法经常被用来生成指向其他控制器的链接。虽然无法找到任何地方可以作为旁路使用,但也发现了一些点,调用url_for与用户一个可控的哈希。这时候,哈希中的任何额外的参数都会被附加到url中作为一个查询字符串。通过查看档,发现有相当多的选项是可以控制的:

1 .:only_path – 如果为true,返回相对的URL。默认为false

2 .:protocol – 要连接的协议,默认为http

3 .:host – 指定链接的目标主机。如果:only_path为false,则必须显式或通过default_url_options提供该选项

4 .:subdomain – 指定链接的子域,使用tld_length将子域与主机分割开来。如果为false,则删除链接主机部分的所有子域

5 .:domain – 指定链接的域,使用tld_length将域从主机中分割出来

6 .:tld_length – TLD id 组成的标签数,只有在提供 :subdomain 或 :domain 时才使用。默认值为ActionDispatch::Http::URL.tld_length,而默认值为1

7 .:port – 可选择指定连接的端口

8 .:anchor – 附加在路径上的锚名称

9 .:params – 要附加到路径上的查询参数

10 .:trailing_slash – 如果为true,则在路径后面添加一个斜线,如”/archive/2009/“

11 .:script_name – 指定相对于域根的应用程序路径。如果提供了,则预置应用程序路径

我以前在其他应用程序中看到过一些比较常见的选项,比如:protocol:host 选项被列入黑名单/删除,或者:only_path 被设置为 true 以防止被使用(即使是 brakeman 建议这样做是安全的),但以前从未见过 :script_name param。它被path_for方法使用,如果它存在,将被用在路径的开头:

def path_for(options)
path = options[:script_name].to_s.chomp("/")
path << options[:path] if options.key?(:path)

add_trailing_slash(path) if options[:trailing_slash]
add_params(path, options[:params]) if options.key?(:params)
add_anchor(path, options[:anchor]) if options.key?(:anchor)

path
end

GitHub上有几个地方使用了下面类似的代码创建链接:

class="link" href="<%= url_for(request.query_parameters.merge(only_path: true)) %>">
Click me

这就意味着,如果使用字符串?script_name=javascript:alert(1)//最终会生成以下html:

class="link" href="javascript:alert(1)//user/repo/...">
Click me

然而,这只是一个低严重性的反射型XSS,需要点击,也被CSP所阻止,但这仍然可以看做一个有趣的bug。

关注公众号:后端面试那些事儿,每天学一点,一起进大厂!

随后,我发现另一个地方使用url_for与可控参数,这次是作为重定向的一部分。这段代码在应用程序控制器中,做了如下操作(方法/参数名称已被更改):

 before_action :check_source

def check_source
source = params["source"]
return redirect_to(check_source_redirect_url) if source == "message"
end

def check_source_redirect_url
query = Addressable::URI.parse(request.env["REQUEST_URI"]).query_values || {}
filtered_params = query.except("source", "token").merge(only_path: true)
url_for(filtered_params)
end

由于使用only_path: true,通常只允许使用现有主机的URL,只保留查询参数。但如果使用script_name就会得到一些有趣的结果,script_name不需要以斜杠开头,当与redirect_to一起使用时,可以被附加到host中:

curl -i 'http://local.dev?source=message&script_name=ggg'
HTTP/1.1 302 Found
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Location: http://local.devggg/welcome/index
Content-Type: text/html; charset=utf-8
Cache-Control: no-cache
X-Request-Id: 7c8eedfa-f552-4d5a-bbcd-295f4e7fd9c0
X-Runtime: 0.002744
Transfer-Encoding: chunked

You are being "http://local.devggg/welcome/index"
>redirected.

由于域名的结尾是可控的,如果用.attacker.domain作为script_name,就会重定向到他们的域名。 

关注公众号:程序猿DD,与一线架构师共成长

0x03 漏洞利用

第二天,我和corb3nik聊起开放重定向的影响,他提到 OAuth tokens 是很挖掘的目标。回头再看这个重定向bug,我发现它其实很厉害,因为它在应用控制器中很早就被影响,这意味着将影响几乎所有的路径(所有的控制器都会扩展应用控制器)。

GitHub自带一些内置的OAuth应用,其中一个就是针对Gist的。GitHub Gist与GitHub是同一个rails应用,只是在不同的主机名后面,拥有有不同的路径。当登录Gist时,通过正常的OAuth流程是一大堆重定向,看起来像这样:

1 .https://github.com/login/oauth/authorize?client_id=7e0a3cd836d3e544dbd9&redirect_uri=https://gist.github.com/auth/github/callback

2 .https://gist.github.com/auth/github/callback?browser_session_id=XXX&code=YYY

3 .https://gist.github.com/auth/github

4 .https://github.com/login/oauth/authorize?client_id=7e0a3cd836d3e544dbd9&redirect_uri=https%3A%2F%2Fgist.github.com%2Fauth%2Fgithub%2Fcallback&response_type=code&state=ZZZ

5 .https://gist.github.com/auth/github/callback?browser_session_id=XXX&code=YYY&state=ZZZ

6 .https://gist.github.com/

为了成功登录Gist,攻击者只需要browser_session_idcode,因为client_id是公开的,state param可以由攻击者生成,因为它只是为了防止CSRF。

初始重定向redirect_uri携带有code和 browser_session_id ,所以我试着在其中添加 script_name=.wbowling.info。结果成功了,我被重定向到我自己的域名,并添加了所需的参数。

在一个新建的浏览器隐私页面,我去https://gist.github.com/auth/github/callback,抓取一个有效的状态参数,然后使用这份browser_session_idcode 和 state参数,成功登录了账户。

由于GitHub和Gist使用不同的会话令牌,虽然它不允许访问github.com,但允许完全访问Gist。

最终我因为这个发现,获得了$10000的赏金。


往期推荐

仅用六个字符来完成Hello World,你能做到吗?

StackOverflow热帖:Java整数相加溢出怎么办?

星巴克是如何处理订单的?

MIT黑科技:通过手机记录的咳嗽数据检测是否感染新冠病毒

10个你可能不曾用过却很有用的 LINUX 命令


扫一扫,关注我

一起学习,一起进步

每周赠书,福利不断


深度内容

推荐加入


最近热门内容回顾   #技术人系列

浏览 52
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报