Критическая уязвимость в корпоративной версии популярнейшей системы контроля версий 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.

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

Cтатьи из последних выпусков журнала можно покупать отдельно только через два месяца после публикации. Чтобы читать эту статью, необходимо купить подписку.

Подпишись на журнал «Хакер» по выгодной цене!

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

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

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

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

Check Also

LUKS container vs Border Patrol Agent. Как уберечь свои данные, пересекая границу

Не секрет, что если ты собрался посетить такие страны как США или Великобританию то, прежд…