LIMB
Вход Регистрация

тема

Приватный репозиторий, говоришь? Мило.

bezmyatezhnost · · 4 ответов · 949 просмотров

Спойлер: soundtrack
Не удалось прогрузить медиа
Всех приветствую, сегодня мы с вами окунёмся в то, как GitHub, самый популярный хостинг кода на планете, хранит абсолютно всё. И когда я говорю всё, я имею в виду ВСЁ. Удалённые репозитории, приватные коммиты, протухшие форки, секреты, которые человек запушил в три часа ночи, осознал ошибку, удалил репу, сменил пароль, перекрестился и лёг спать. Спойлер - зря лёг.

Не удалось прогрузить медиа

​
Помните в первой статье я рассказывал, как какой-то индус на гитхабе выложил чуть ли не все исходники приложения Optimum, и мы на этом построили целый чекер? Так вот, тот самый индус, даже если бы он очканул и снёс свою репу, данные бы НИКУДА не делись. Вообще. Ни-ку-да. GitHub по своей архитектуре физически не умеет удалять коммиты из так называемой fork-сети. Это не баг. Это не недоработка. Это, цитирую, "working as designed". Ну спасибо, Майкрософт, ну красавцы, чё. Называется эта прелесть CFOR - Cross Fork Object Reference. Звучит наукообразно, аля из какой-то академической статьи, а на деле способ получить доступ к данным из удалённых и приватных репозиториев, имея лишь хэш коммита. А хэш, как вы скоро узнаете, можно и побрутить. И нет, я не шучу, четыре долбаных символа, и GitHub сам додумает остальное. Чтоб вы понимали масштаб: мы возьмём официальный репозиторий OpenAI - openai-cookbook, у которого 12 тысяч с лишним форков. Люди форкают, вставляют свои API-ключи, эксперименты, данные, потом удаляют форки, и думают что всё, пронесло. А коммиты, вот они, висят, доступны через оригинальную репу OpenAI. sk-???????????? прямо в диффах. Из удалённого форка. Который удалили год назад. Кароче, красотища.

Не удалось прогрузить медиа

​
Сегодня мы разберём что такое CFOR и почему это работает, как устроены fork-сети, как брутфорсить хэши коммитов через GraphQL API, и самое вкусное,покажем это всё на живом примере, с реальными найденными секретами (замазанными, мы же не совсем отбитые). Ну, почти.

Не удалось прогрузить медиа
​

Окей, давайте разберёмся как GitHub устроен под капотом, ибо без этого всё дальнейшее будет выглядеть как какая-то чёрная магия. Когда вы форкаете репозиторий на гитхабе, он не копирует все данные куда-то отдельно. Ну сами подумайте, 12 тысяч форков openai-cookbook, каждый весит допустим 500мб, это 7.5 терабайт одного и того же кода, какой дурак будет так делать. По этому GitHub использует fork network, все форки и оригинал делят между собой одно общее хранилище объектов (object store). Коммиты, блобы, деревья, всё в одной куче. И вот тут начинается самое весёлое. Когда кто-то делает коммит в своём форке, коммит падает в общее хранилище. А когда этот кто-то удаляет свой форк, репозиторий да, удаляется, ссылки удаляются, но сам коммит из хранилища? Хрен там. Он становится осиротевшим. И гитхаб его не чистит. Не через день, не через месяц, не через год. Никогда, блин. Ибо сборщик мусора (git gc) работает на уровне всей fork-сети, а не отдельного репозитория. А когда в сети тысячи форков, каждый из которых теоретически может ссылаться на любой объект, ну, проще вообще нихера не трогать, чем случайно кому-то репу сломать. Логика железная, да? Да. Кароче, итого: юзер форкнул репу, запушил коммит с секретом, удалил форк. Секрет всё ещё доступен через оригинальную репу. По прямой ссылке. Без авторизации. Без каких-либо прав. Просто зная хэш коммита. И кстати, работает это в обе стороны, из форка можно вытащить коммиты приватного оригинала, если форк был сделан до того как репу закрыли. Такие дела. Ладно, погнали к практике, а то я сам уже от теории засыпаю.

Не удалось прогрузить медиа

​
Щас многие спросят, ну ок, коммиты висят, но хэш SHA-1 это же 40 символов, 16^40 комбинаций, это перебирать до тепловой смерти вселенной, бессмыслица полная. А фишка в том, что GitHub принимает короткие SHA. Можно обратиться к коммиту по первым 4 символам хэша, и если это однозначно идентифицирует коммит в хранилище, гитхаб сам вернёт полный хэш. Четыре hex-символа - это 16^4 = 65 536 комбинаций. Это перебирается минут за 15-20. Мы это тупо сделали и я вам щас покажу.

Не удалось прогрузить медиа

​
А теперь ещё веселее. GitHub GraphQL API позволяет в одном запросе проверять сразу пачку SHA. То есть мы не шлём 65 тысяч запросов по одному как полудурки, а пакуем в батчи по 50 штук и шлём ~1300 запросов. С десятью тредами это дело на 10 минут. По этому давайте уже запускать эту штуку.

Не удалось прогрузить медиа
​

Для начала нужен GitHub токен с правами на repo. Идём в Settings ~> Developer settings ~> Personal access tokens ~> Tokens (classic), генерим токен, ставим галку на repo, всё, ничего сложного. И тут момент, который меня убил. Скрипт для эксплуатации CFOR лежит... на самом гитхабе. Прямо вот тут. То есть, подождите, гитхаб хостит у себя инструмент, который эксплуатирует его же собственную архитектурную "фичу"? И не банит? Не удаляет? А, ну да, формально это не эксплойт, это просто обращение к API по документации. Ну гитхаб, ну молодцы, чё сказать. Скрипт написан просто, давайте разберём чё он делает. Первым делом лезет в REST API гитхаба и собирает все известные коммиты репозитория, те что и так видны в публичной истории, их перебирать смысла нет. Дальше генерит все возможные 4-символьные hex-комбинации, это 65 536 штук, и выкидывает те что совпадают с началом уже известных коммитов, чтоб не тратить запросы впустую. Потом пакует оставшееся в батчи по 50 и шлёт через GraphQL, в одном запросе проверяя сразу пачку коротких SHA. Если гитхаб на какую-то комбинацию возвращает полный 40-символьный хэш, значит в хранилище есть коммит которого нет в публичной истории, то есть он из чьего-то форка, возможно уже удалённого. Ну и последний этап, скачивает .diff каждого найденного коммита и складывает в папку, чтоб потом прогрепать на секреты. Запускаем и... оно просто стоит и сыпет ссылками. Сыпет, сыпет, сыпет.

Не удалось прогрузить медиа

​
Я чесгря сначала сел тупо пялиться в консоль как баран на новые ворота, не понимая когда это закончится. Потом дошло что ему надо перебрать все 65к комбинаций, и это нормально что он так долго молотит. По итогу нашлось 4523 коммита. Четыре с половиной тысячи коммитов из чужих форков, доступных через оригинальную репу OpenAI. Охренеть? Охренеть. Теперь их надо прочесать. Накидываем простенький скрипт который проходит по всем .diff файлам и грепает по паттернам:
Code:
import os
import re

DIFF_DIR = "путь где лежат диффы"

patterns = {
    "OpenAI API Key": r"sk-[a-zA-Z0-9]{20,}",
    "OpenAI Project Key": r"sk-proj-[a-zA-Z0-9_-]{20,}",
    "GitHub Token": r"gh[pousr]_[a-zA-Z0-9]{36,}",
    "AWS Access Key": r"AKIA[0-9A-Z]{16}",
    "Telegram Bot Token": r"[0-9]{8,10}:[a-zA-Z0-9_-]{35}",
    "Generic Secret": r"(?i)(?:api_key|apikey|secret_key|access_token|auth_token)\s*[=:]\s*['\"]([a-zA-Z0-9_\-]{16,})['\"]",
}

found_secrets = []

files = [f for f in os.listdir(DIFF_DIR) if f.endswith((".diff", ".patch"))]

for filename in files:
    filepath = os.path.join(DIFF_DIR, filename)
    try:
        with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
            content = f.read()
    except Exception:
        continue

    commit_hash = filename.replace(".diff", "").replace(".patch", "")

    for secret_type, pattern in patterns.items():
        matches = re.findall(pattern, content)
        for match in matches:
            entry = {
                "type": secret_type,
                "value": match,
                "commit": commit_hash,
                "url": f"https://github.com/openai/openai-cookbook/commit/{commit_hash}",
            }
            found_secrets.append(entry)

seen = set()
unique = []
for s in found_secrets:
    if s["value"] not in seen:
        seen.add(s["value"])
        unique.append(s)

with open("secrets.txt", "w", encoding="utf-8") as f:
    for s in unique:
        f.write(f"[{s['type']}] {s['value']}\n")
        f.write(f"  {s['url']}\n\n")
Тут базовый набор, но расширять можно до бесконечности. Stripe ключи, SSH-ключи, токены Slack, Discord, JWT, тупо дописываете паттерн и погнали. Чем больше регулярок, тем больше найдёте, тут всё просто. Можно вообще забить на паттерны и просто сделать grep в повершелле:
Select-String -Path "путь\к\папке_с_диффами\*" -Pattern "sk-","AKIA","password","secret","token"
Грубо, тупо, зато быстро, и глазами видно где что лежит.

Не удалось прогрузить медиа
​

Ну и по результатам, держитесь. Из одного репозитория openai-cookbook мы достали реальные OpenAI API ключи в формате sk-, AWS Access Key в формате AKIA, ключи от Pinecone, Stripe тестовые ключи, какие-то UUID которые явно были чьими-то секретами. Причём между плейсхолдерами типа sk-xxxxxxxx и YOUR_OPENAI_API_KEY лежат вполне себе боевые ключи, которые люди воткнули в свой форк, поигрались с примерами из кукбука, потом снесли форк к чертям думая что всё, чисто, можно спать спокойно. Ага, щас. Оно вот, через оригинальную репу OpenAI доступно кому угодно. И это, напоминаю, один репозиторий. Один. Представьте масштаб если натравить на tensorflow с 75к форков, или на kubernetes с 43к, или на first-contributions где одни новички которые слово .gitignore впервые в жизни видят. Там вообще Клондайк будет, я уверен.

Не удалось прогрузить медиа

​
Ну что, давайте подводить итоги этого безобразия. GitHub не удаляет коммиты из fork-сети, и это не баг, это их архитектурное решение, которое они сами подтвердили. Удалённый форк не равно удалённые данные. Приватный репозиторий не равно недоступные данные, если форк был сделан до приватизации (словно в совок окунулся). Брутфорс хэшей реален, 65 тысяч комбинаций перебираются за минуты. На одном openai-cookbook нашлось 4500+ коммитов из чужих форков, и среди них реальные API-ключи, AWS креды, и прочая радость. Всем пока, не болейте!
👍64 ❤️9 😁6 🤔19 😢14
Ответbezmyatezhnost

Спойлер: soundtrack Не удалось прогрузить медиа Всех приветствую, сегодня мы с вами окунёмся в то, как GitHub, самый популярный хостинг кода на планете, хранит абсолютно всё. И когда я говорю всё, я имею в виду ВСЁ…

Если на коммит никто не ссылается, то gtihub примерно в течении нескольких недель удалит физически коммиты (где могли быть секреты): https://github.com/orgs/community/discussions/190183

Также юзер у которого утекли секреты может обратиться в поддержку github, пишут что они оперативно удаляют или накрайняк отозвать секреты. Вообщем стоит надеяться на новичка или ленивого разраба ;) 

Вы вот нашли ключи openai, проверили их? Удалось найти ценный секрет например shodan, censys, zoomeye ключи или для AI с балансом, а не пустышку?

Не удалось прогрузить медиа

Anyone can Access Deleted and Private Repository Data on GitHub ◆ Truffle Security Co.

You can access data from deleted forks, deleted repositories and even private repositories on GitHub. And it is available forever. This is known by GitHub, and intentionally designed that way.
Не удалось прогрузить медиа
trufflesecurity.com
Не удалось прогрузить медиа

TruffleHog now finds all Deleted & Private Commits on GitHub ◆ Truffle Security Co.

TruffleHog can now enumerate Cross Fork Object References (and deleted git history), and then scan them for secrets.
Не удалось прогрузить медиа
trufflesecurity.com
1 в 1. Тема уже раскрыта исследователем в 2024 году просто на английском языке.
👍2
Не удалось прогрузить медиа


Кстати вот варианты секретов которые можно найти в репозиориях github. Из интересного под спам это mailgun/twitter key/telegram bot token
👍3
Ответcybergangster

Если на коммит никто не ссылается, то gtihub примерно в течении нескольких недель удалит физически коммиты (где могли быть секреты): https://github.com/orgs/community/discussions/190183 Также юзер у которого утекли…

В данном случае вынужден согласиться с вашей рецензией. Гайдов по работе с вектором Information Disclosure(раскрытие потенциально ценной информации) через Git-примочки навалом даже на том же ютубчике. Опять же, статью можно дополнить, если дораскрыть тему с используемыми для этого инструментами\фреймворками вроде того же траффлхога, также можно описать работу с гитами уже по данным, полученным с самого таргета(рце на тачку получили, забрали оттуда гитовые листики из конфигов, поехали искать).

Про почти что мгновенную зачистку в ручном порядке при обращении к ТП - да. Потому, здесь, как и с прочими ответвлениями Information Disclosure как вектора, надежда только на невнимательность девопса или конкретного девелопера, это нельзя ставить в претензию, весь вектор работает сугубо на "ляпах".

Про саму статью - раскрыто неплохо, но стоит показать, как и откуда еще можно оттолкнуться. Парсинг, изучение конфигов на заполученных тачках, но думаю, сама по себе она оплачена может быть.. материал редкий.
👍1
Ответcybergangster

Если на коммит никто не ссылается, то gtihub примерно в течении нескольких недель удалит физически коммиты (где могли быть секреты): https://github.com/orgs/community/discussions/190183 Также юзер у которого утекли…

Так как я сейчас активно занимаюсь парсером github
Приведу немного статистки из своих результатов
на 16 тысяч найденных валидных ключей, при помощи crof было добыто всего 248 
из них реально ценных по факту 0, так как нашлись всякие newsapi, mapbox и прочие никому не нужные
👍1

Гостевой просмотр. Чтобы отвечать и ставить реакции, войдите или зарегистрируйтесь.