Критическая уязвимость в корпоративной версии популярнейшей системы контроля версий GitHub позволяет любому авторизованному пользователю выполнять произвольный код в системе. Для эксплуатации этой уязвимости требуется применить сразу несколько техник: инъекцию в кеширующий сервис и внедрение объектов.
 

Общие сведения

Не так давно я уже писал об уязвимости в GitHub Enterprise. Тогда из-за использования статического ключа для подписи данных в сессии любой пользователь, в том числе и анонимный, мог выполнить произвольный код в системе. Проблему успешно запатчили после очередного релиза, но не прошло и нескольких месяцев, как эксперт в области информационной безопасности Orange обнаруживает еще одну критическую уязвимость. В этот раз ее можно проэксплуатировать только от имени авторизованного пользователя, у которого имеются права на создание репозиториев или на управление уже существующими.

Корпоративная версия GitHub невероятно популярна. Я натыкаюсь на нее буквально на каждом втором крупном аудите. Важность получения доступа к серверу с исходниками проектов трудно переоценить, поэтому такие машины всегда в приоритете при пентесте. Что делает эту уязвимость обязательной к изучению и применению.

 

Стенд

С тестовым окружением все довольно просто. GitHub предоставляет пробную версию своего продукта сроком на 45 дней. Нам этого как раз должно хватить для того, чтобы разобраться с деталями проблемы.

Скачиваем уязвимую версию приложения (2.8.6) в формате переносимой виртуальной машины OVA (Open Virtualization Archive), затем регистрируемся и выбираем GitHub on-premises. После подтверждения аккаунта и авторизации качаем файл лицензии .ghl, который потребуется на этапе установки.

Загрузка файла лицензии в процессе установки GitHub Enterprise
Загрузка файла лицензии в процессе установки GitHub Enterprise

Для работы с виртуальной машиной я использую VirtualBox. После успешной установки нужно настроить саму виртуалку. Нам нужно добавить два дополнительных диска и указать IP-адрес, по которому будет доступен веб-интерфейс. Я выбрал Host-Only networking в качестве типа сетевого адаптера.

После успешного запуска машины переходим по ссылке https://айпи-адрес:8443/setup и проходим процесс установки. На этом этапе настоятельно рекомендую сгенерировать и добавить свой SSH-ключ и обязательно включить возможность регистрации новых пользователей. Система будет приходить в рабочее состояние довольно долго, поэтому наберись терпения.

Виртуалка с GitHub Enterprise, готовая к экспериментам
Виртуалка с GitHub Enterprise, готовая к экспериментам

После успешного запуска виртуалки переходим к следующей части нашего приключения.

 

Первые шаги

Продукт написан на солянке из разных языков программирования. Но в большинстве своем мы можем наблюдать здесь Ruby с использованием Ruby on Rails и веб-фреймворк Sinatra.

Прежде чем копаться в недрах приложения, нужно провести деобфускацию исходников. Разработчики решили прикрыть их от любопытных глаз и даже написали свою библиотеку для обфускации. В основе алгоритма лежит обычный XOR с ключом This obfuscation is intended to discourage GitHub Enterprise customers from making modifications to the VM. We know this 'encryption' is easily broken. и сжатие по алгоритму deflate.

Исходники GitHub Enterprise в обфусцированном виде
Исходники GitHub Enterprise в обфусцированном виде

Прочитать подробнее про это ты можешь, погуглив с запросом __ruby_concealer__.

Все это мы проделывали в прошлый раз, и скриптик на Ruby для автоматической деобфускации сорцов до сих пор дожидается тебя в моем репозитории.

Я не зря говорил о добавлении своего ключа. Теперь нужно войти с ним на машину по SSH: порт 122, пользователь admin. Скачиваем и запускаем скрипт деобфускации из директории /data и несколько минут спустя получаем чистые исходные коды.

Исходник, прошедший деобфускацию
Исходник, прошедший деобфускацию

Если не хочется заморачиваться со всеми этими стендами и тестированием, но есть желание взглянуть на исходники, то, специально для тебя, я залил их на файлообменник. Надеюсь, ребята из GitHub не сильно обидятся.

Теперь, когда стенд запущен и работает, нужно зарегистрировать нового пользователя и создать свой первый репозиторий.

Узнаваемый интерфейс GitHub
Узнаваемый интерфейс GitHub
 

Дорога к эксплоиту. SSRF

В GitHub есть такая вещь, как хуки. Это модная нынче штука, которая позволяет оповещать внешние сервисы о каких-либо событиях в системе. Например, при добавлении нового коммита система может отстукивать о его параметрах на указанный тобой адрес. Настраивается это в разделе Hooks & Services из меню Settings твоего репозитория.

Настройка Webhooks
Настройка Webhooks

Поле URL в форме добавления хука выглядит в шаблоне следующим образом.

/data/github/4ff81e7/app/views/integrations/hooks/_webhook_form.html.erb
10: <dl class="form-group required">
11:   <dt><%= form.label :url, "Payload URL" %></dt>
12:   <dd>
13:     <%= form.text_field :url, :placeholder => "https://example.com/postreceive", :class => "js-hook-url-field", :autocomplete => "off", :type => "url" %>
14:   </dd>
15: </dl>
...
31: <p class="flash flash-warn invalid-url-notice">
32:   <%= octicon("alert") %>
33:   <strong>This doesn’t look like a valid URL.</strong>
34: </p>

При добавлении URL должен пройти ряд проверок.

/data/github/4ff81e7/app/models/hook.rb
043:   validate :hook_is_unique
044:   validate :webhook_url_is_valid
...
354:   def hook_is_unique
...
362:     if not_unique
363:       GitHub.stats.increment("hooks.duped")
364:       errors.add(:base, "Hook already exists on this #{repo_hook? ? "repository" : "organization"}")
365:     end
366:   end
...
518:   def webhook_url_is_valid
519:     return unless webhook?
520:
521:     if config["url"].blank?
...
532:     limit = self.config_attribute_records.columns_hash['value'].limit
533:
534:     if url.length > limit
535:       errors.add(:base, "Config URL must have no more than #{limit} characters")
...
539:     begin
540:       Addressable::URI.parse(url)
541:     rescue Addressable::URI::InvalidURIError => message
542:       errors.add(:base, "Config URL is invalid: #{message}")
543:       return
544:     end
545:   end

Скрипт не допускает наличия одинаковых хуков в рамках одного репозитория и проверяет, чтобы длина адреса не была больше допустимой. И главная проверка на валидность самого адреса при помощи библиотеки Addressable.

Метод Addressable::URI.parse парсит переданный URL и проверяет его на соответствие спецификациям RFC 3986, RFC 3987 и RFC 6570. Это значит, что мы можем отправлять запросы на любые серверы. Никаких других проверок адреса не выполняется.

Только вот при попытке использовать локальные адреса localhost, 127.0.0.1 и тому подобные система возвращает ошибку Invalid host. Однако не локалхостом единым! Эту ошибку можно обойти, просто используя адрес 0.0.0.0 или сокращенно 0.

Давай проверим, как это работает. Поставим на прослушку порт 31337 (nc -lp 31337 -vvv) на виртуалке. И затем создадим веб-хук, указав в качестве URL http://0.0.0.0:31337/test. После нажатия кнопки «Добавить» к нам незамедлительно прилетает запрос.

SSRF в GitHub Enterprise. Отправка запроса на локальную машину
SSRF в GitHub Enterprise. Отправка запроса на локальную машину

Можем поздравить себя, мы обнаружили SSRF-уязвимость. Да не простую, а которая еще и показывает ответ сервера, если он оформлен как валидный HTTP response.

Теперь нужно придумать, что с ее помощью можно сделать.

Я неспроста так рьяно пытался пробиться на localhost. GitHub Enterprise — это большое и сложное приложение, поэтому внутри крутится некоторое количество вспомогательных сервисов. Взгляни на одну только строку статуса.

Запущенные сторонние сервисы
Запущенные сторонние сервисы

Тут нам и «Эластик», и «Редис», и «Мемкеш». Выбирай — не хочу!

Начнем с Memcached. Протокол общения с ним текстовый, поэтому можно попробовать провести инъекцию. Попробуем провернуть разделение запроса (HTTP Request Splitting), для этого создадим хук с символами перевода каретки.

http://0:31337/Hello%0D%0Aworld
Проверка атаки HTTP Request Splitting
Проверка атаки HTTP Request Splitting

Не вышло. Ладно, не отчаиваемся, на этом сервере еще есть чем поживиться.

 

Поиск подходящих сервисов

Теперь настало время посмотреть на открытые порты. Выполним команду sudo netstat -anp | grep -i LISTEN.

Список запущенных сервисов, которые слушают порт
Список запущенных сервисов, которые слушают порт

Вот такой внушительный список сервисов, которые доступны по сети. Есть где разгуляться, даже порт 1337 открыт 😉

Если помнишь, я говорил, что SSRF позволяет читать ответ. Это можно использовать во время тестов на проникновение по методике черного ящика. Например, посмотрим, что находится на пресловутом порте 1337. Создадим хук http://0:1337/, откроем его и проскроллим до Recent Deliveries. Там, во вкладке Response, можно увидеть ответ от сервера. Если хотим заново отправить запрос, то к вашим услугам кнопка Redeliver.

Продолжение статьи доступно только подписчикам

Вариант 1. Оформи подписку на «Хакер», чтобы читать все статьи на сайте

Подписка позволит тебе в течение указанного срока читать ВСЕ платные материалы сайта, включая эту статью. Мы принимаем оплату банковскими картами, электронными деньгами и переводами со счетов мобильных операторов. Подробнее о подписке

Вариант 2. Купи одну статью

Заинтересовала статья, но нет возможности оплатить подписку? Тогда этот вариант для тебя! Обрати внимание: этот способ покупки доступен только для статей, опубликованных более двух месяцев назад.


3 комментария

Подпишитесь на ][, чтобы участвовать в обсуждении

Обсуждение этой статьи доступно только нашим подписчикам. Вы можете войти в свой аккаунт или зарегистрироваться и оплатить подписку, чтобы свободно участвовать в обсуждении.

Check Also

Страдания с ReactOS. Почему в заменителе Windows работают трояны, но не работает Word

Сегодня в нашей кунсткамере демонстрируется необычайный организм — двадцатилетний зародыш …