Содержание статьи
Общие сведения
Не так давно я уже писал об уязвимости в GitHub Enterprise. Тогда из-за использования статического ключа для подписи данных в сессии любой пользователь, в том числе и анонимный, мог выполнить произвольный код в системе. Проблему успешно запатчили после очередного релиза, но не прошло и нескольких месяцев, как эксперт в области информационной безопасности Orange обнаруживает еще одну критическую уязвимость. В этот раз ее можно проэксплуатировать только от имени авторизованного пользователя, у которого имеются права на создание репозиториев или на управление уже существующими.
Корпоративная версия GitHub невероятно популярна. Я натыкаюсь на нее буквально на каждом втором крупном аудите. Важность получения доступа к серверу с исходниками проектов трудно переоценить, поэтому такие машины всегда в приоритете при пентесте. Что делает эту уязвимость обязательной к изучению и применению.
Стенд
С тестовым окружением все довольно просто. GitHub предоставляет пробную версию своего продукта сроком на 45 дней. Нам этого как раз должно хватить для того, чтобы разобраться с деталями проблемы.
Скачиваем уязвимую версию приложения (2.8.6) в формате переносимой виртуальной машины OVA (Open Virtualization Archive), затем регистрируемся и выбираем GitHub on-premises. После подтверждения аккаунта и авторизации качаем файл лицензии .ghl, который потребуется на этапе установки.
Для работы с виртуальной машиной я использую VirtualBox. После успешной установки нужно настроить саму виртуалку. Нам нужно добавить два дополнительных диска и указать IP-адрес, по которому будет доступен веб-интерфейс. Я выбрал Host-Only networking в качестве типа сетевого адаптера.
После успешного запуска машины переходим по ссылке https://айпи-адрес:8443/setup
и проходим процесс установки. На этом этапе настоятельно рекомендую сгенерировать и добавить свой SSH-ключ и обязательно включить возможность регистрации новых пользователей. Система будет приходить в рабочее состояние довольно долго, поэтому наберись терпения.
После успешного запуска виртуалки переходим к следующей части нашего приключения.
Первые шаги
Продукт написан на солянке из разных языков программирования. Но в большинстве своем мы можем наблюдать здесь 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.
Прочитать подробнее про это ты можешь, погуглив с запросом __ruby_concealer__
.
Все это мы проделывали в прошлый раз, и скриптик на Ruby для автоматической деобфускации сорцов до сих пор дожидается тебя в моем репозитории.
Я не зря говорил о добавлении своего ключа. Теперь нужно войти с ним на машину по SSH: порт 122, пользователь admin. Скачиваем и запускаем скрипт деобфускации из директории /data
и несколько минут спустя получаем чистые исходные коды.
Если не хочется заморачиваться со всеми этими стендами и тестированием, но есть желание взглянуть на исходники, то, специально для тебя, я залил их на файлообменник. Надеюсь, ребята из GitHub не сильно обидятся.
Теперь, когда стенд запущен и работает, нужно зарегистрировать нового пользователя и создать свой первый репозиторий.
Дорога к эксплоиту. SSRF
В GitHub есть такая вещь, как хуки. Это модная нынче штука, которая позволяет оповещать внешние сервисы о каких-либо событиях в системе. Например, при добавлении нового коммита система может отстукивать о его параметрах на указанный тобой адрес. Настраивается это в разделе Hooks & Services из меню Settings твоего репозитория.
Поле 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-уязвимость. Да не простую, а которая еще и показывает ответ сервера, если он оформлен как валидный HTTP response.
Теперь нужно придумать, что с ее помощью можно сделать.
Я неспроста так рьяно пытался пробиться на localhost. GitHub Enterprise — это большое и сложное приложение, поэтому внутри крутится некоторое количество вспомогательных сервисов. Взгляни на одну только строку статуса.
Тут нам и «Эластик», и «Редис», и «Мемкеш». Выбирай — не хочу!
Начнем с Memcached. Протокол общения с ним текстовый, поэтому можно попробовать провести инъекцию. Попробуем провернуть разделение запроса (HTTP Request Splitting), для этого создадим хук с символами перевода каретки.
http://0:31337/Hello%0D%0Aworld
Не вышло. Ладно, не отчаиваемся, на этом сервере еще есть чем поживиться.
Поиск подходящих сервисов
Теперь настало время посмотреть на открытые порты. Выполним команду sudo netstat -anp | grep -i LISTEN
.
Вот такой внушительный список сервисов, которые доступны по сети. Есть где разгуляться, даже порт 1337 открыт 😉
Если помнишь, я говорил, что SSRF позволяет читать ответ. Это можно использовать во время тестов на проникновение по методике черного ящика. Например, посмотрим, что находится на пресловутом порте 1337. Создадим хук http://0:1337/
, откроем его и проскроллим до Recent Deliveries. Там, во вкладке Response, можно увидеть ответ от сервера. Если хотим заново отправить запрос, то к вашим услугам кнопка Redeliver.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»