简介
GitHub企业版(GHE)的代码默认是混淆的,但是有脚本可以把他们恢复为常规ruby文件.
影响版本:GitHub Enterprise < 2.21.4
修复该漏洞的版本: GitHub Enterprise 2.21.4 released fixing the issue
git基本命令
git revert: 撤销某个单一的commit. 本文中的"撤销"就是gitrevert
。
历史漏洞
参考 https://enterprise.github.com/releases/2.17.6/notes
GitHub Enterprise 2.17.6 August 13, 2019
漏洞原理:使用以一个-
字符开头的"分支名称"(branch names),向git命令中注入选项,允许攻击者截断服务器上的文件。
我认为这个漏洞是一个很好的开始,我看看GitHub企业版(GHE)是否存在类似的漏洞。
发现新漏洞
我开始搜索git进程被调用的所有位置,然后追溯参数,查看它们是否是用户可控的,以及是否已正确清理(sanitised)。
- 发现情况是这样的:
- 大多数地方要么将用户控制的数据放在--命令后面,以使它永远不会被解析为选项
- 要么进行检查以确保它是有效的sha1或commit hash值,并且不以
-
开头
过了一会儿,我找到了reverse_diff
方法,该方法进行了2次"提交"(commits),最终git diff-tree
与它们一起运行,并且唯一的检查是对于存储库(sha,branch,tag等)都存在有效的git引用(git references)。追溯可知,此函数由revert_range
在之前的2个wiki"提交"(commits)之间进行"撤销"(reverting)时使用的方法调用。
因此,发送POST请求到user/repo/wiki/Home/_revert/57f931f8839c99500c17a148c6aae0ee69ded004/1967827bcd890246b746a5387340356d0ac7710a
会将值(实际参数)57f931f8839c99500c17a148c6aae0ee69ded004
和1967827bcd890246b746a5387340356d0ac7710a
传入reverse_diff
,调用该函数。
完美,我checked out了一个repo(仓库),并通过命令git push origin master:--help
pushed出一个新的分支 名为–help
,之后尝试发送POST请求到user/repo/wiki/Home/_revert/HEAD/--help
。但是没有成功,返回的提示信息是422 Unprocessable Entity
。
为什么会这样?查看服务器日志后发现,是因为 CSRF 令牌无效。事实证明,rails现在具有基于表单的CSRF token,这些token是根据要POST的路径
生成的。
没有检查查询参数,但是在本例中,路由设置为只允许"提交"(commits)的路径参数。
"撤销"(revert)的形式以及有效token是由"wiki比较模板"(wiki compare template)生成的,但遗憾的是,它的验证更加严格,且要求commit具有有效的sha hashes。这意味着我们无法为–help
分支提供有效的表单(form)和token,仅能为具有有效的sha hashes的commit提供:有效的表单(form)和token。
深挖rails中的valid_authenticity_token?
方法(你没看错这个方法最后面是个问号),可以发现,绕过每个表单(form)CSRF的另一种方法是使用全局token,因为存在这样一个"代码路径"(code path),可以在转换时使现有表单向后兼容。
as there is a code path to make existing forms backwards compatible while transitioning.
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