Make. Build. Break. Reflect.
969 subscribers
122 photos
1 video
1 file
130 links
Полезные советы, всратые истории, странные шутки и заметки на полях от @kruchkov_alexandr
Download Telegram
#gitlab #github #secrets #security

Спустили с небес на землю.

Недавно я решил, что я мамкин хакер(нет), даже отправил несколько репортов, от medium до high severity.
Вот буквально сегодня в ночи получил ответ. Вкратце: "спасибо, мы знаем, тикет закрыт, пробуйте ещё".

Основная суть всех моих репортов - "пользователь с привилегированным(не админ)/с ограниченным доступом(не админ)/со специальными не частыми у всех условиями, может получить доступ к секретам".

Расставлю сразу свою позицию - я никого ни в чём не обвиняю, мне позиция ясна, ответы понравились, я со всем согласен. Никаких интриг, просто общение между инженером и представителями компаний.
История в 3 частях.

Часть первая, GitLab.

Однажды мне надо было достать значения секрета, помеченного Masked and Hidden*.
У меня были права reporter в этом репозитории.
Базовые вещи echo $secret не давали результата, там было [MASKED] в аутпуте.
Я просто сделал
  script:
- echo "$password" | base64 > /tmp/password.txt
- cat /tmp/password.txt

скопировал значение и локально выполнил команду
echo Wml4TjRUbVdpRDAxbjdoCg== | base64 -d
ZixN4TmWiD01n7h (естесственно это пароль для статьи, вы чо, думали я реальный напишу?)

Сам себя поздравляю - я могу прочитать значение секрета, помеченного Masked and Hidden в 2 команды.
Отмечаю, что это работает для приватных моих репозиториев, а так же для всех проектов/репозиториях, куда меня добавили - то есть во всех для организации. Проверено на SelfHosted/Cloud.
То есть прямо сейчас ВСЕ ваши коллеги не девопсы/админы с ограниченными правами могут прочитать ВСЕ секреты в тех проектах/репозиториях, где у них есть ограниченные доступы. Даже к OpenAI и тратить токены компании или PAT к другими репозиториям с привилегированными правами(например CICD процессы GitOps между репозиториями)

На данный репорт я получаю ответ от https://hackerone.com/
According to current program policy, this reported finding is considered out-of-scope;
CI/CD Variable Disclosure - as it stands, we currently see our guidance to use external secret storage as sufficient for addressing reports that rely on disclosing Masked CI/CD variables to prove impact.
It is a known security rick, based on the official document
https://docs.gitlab.com/ci/variables/#cicd-variable-security
As a result, I'm closing this report as Informative. Your effort is nonetheless appreciated, and we wish that you'll continue to research and submit any future security issues you find.


Идем по линку и там
Masking a CI/CD variable is not a guaranteed way to prevent malicious users from accessing variable values. To ensure security of sensitive information, consider using external secrets and file type variables to prevent commands such as env/printenv from printing secret variables.
Gitlab.com сотрудничает с https://hackerone.com/ и значит их ответ = ответ GitLab.com.

Записываем себе - гитлаб не гарантирует ничего по сохранности секретов.

* В целом данная уязвимость не новая - опытные инженеры её и так знали и пользуются втихую.
Возможно даже на других CICD
😉
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍8🔥42😨1
#gitlab #github #secrets #security

История вторая. GitHub.

Однажды мне надо было в моём личном репозитории кое-чего вывести в лог текстом. Ну буквально echo "hello Barbie!"
В логах же я увидел "hello ***bie!"
Не сразу понял, пришлось сделать несколько тестов. Оказалось, что GirtHub при совпадении ЛЮБОЙ части ЛЮБОГО доступного(это важно) секрета маскирует символы звёздочкой.
То есть если у нас есть какой-то секрет, внутри которого есть "Bar" то при попытке напечатать Barbie GitHub будет маскировать Bar.
Любопытство вязло вверх и я написал простой воркфлоу.
jobs:
...
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: "3.x"
- name: Run print_secret script
run: python scripts/print_secret.py
env:
SUPERSECRET: ${{ secrets.AWS_ACCESS_KEY_ID }}
- name: Upload found secret artifact
uses: actions/upload-artifact@v4
with:
name: found-secret
path: found_secret.txt
if-no-files-found: error

Сам скрипт простой, как три копейки.
https://gist.github.com/kruchkov-alexandr/241bb761b183800e2220c10878089214
Что делает скрипт? Берёт на вход название секрета, печатает символы из чарсета, ожидает * при совпадении часть секрета, идёт дальше.
В логе это выглядит буквально
Run python scripts/print_secret.py
*???????????????????
**??????????????????
***?????????????????
****????????????????
*****???????????????
******??????????????
*******?????????????
********????????????
*********???????????
**********??????????
***********?????????
************????????
*************???????
**************??????
***************?????
****************????
*****************???
******************??
*******************?
********************

За 0.0001мс мы "расшифровываем" любой секрет. Просто заходим в артефакты, качаем файл и забираем значение секрета). Алгоритм простой, как три копейки - гитхаб нам сам показывает, какие и где символы он маскирует.
А количество звёздочек - длина секрета)

На самом деле у GitHub работает и через base64, как в первой истории, но со скриптом мне было просто интересно повозиться 😀
Прикольная же идея!


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

Получаю ответ:
Thanks for your submission! As noted at https://developer.github.com/v3/actions/secrets/#create-or-update-a-secret-for-a-repository ... "Anyone with write access to the repository can use this endpoint". It is intentional that write-access to a repository grants both read and write access to secrets. Naturally, write-access users can read all secrets. Likewise, write-access users are intended to be able to create/edit secrets because they should be able to edit/create workflows and would need to be able to configure secrets to do so. GitHub is aware that there may be some discrepancies between the API and what is apparently accessible from the web UI. GitHub is tracking several issues related to this feature and Actions generally. There may be modifications or more strict enforcement in the future, but we have nothing to announce at the moment.

Записываем себе - гитхаб не гарантирует ничего по сохранности секретов(когда мы же сами даём права в репозиторию*).
Никакой иронии - мне понятна позиция в целом.
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥76👍1
#gitlab #github #secrets #security

История третья. GitHub.

Третья история это не баг репорт, а собственное исследование в целях повышения опыта и экспертизы в вопросах безопасности.
НЕ ПОВТОРЯТЬ, ЭТО ПРОТИВОЗАКОННО, НЕЛЬЗЯ ПОВТОРЯТЬ
История лишь для ознакомления, повторять абсолютно запрещено, заметка исключительно в целях сокрытия СВОИХ проектов от глупых действий.

Все эти игрища были лишь в организации, где изначально у меня были хоть какие-то, но минимальные права.
Будет ли это работать при публичных репозиториях?
Как мне проверить это и защитить свои публичные репозитории от хакеров?

Немного теории. Выжимка для самых маленьких - вы ведь не хотите читать всю документацию GitHub.
1) Когда я запускаю у себя(личный репо, репо организации) воркфлоу - это МОЙ секьюрити контекст.
Мой securitycontext = мне доступны все секреты.
Если я сделаю форк от любого публичного репозитория, затем "что-то подменяю" в коде и отправлю pull request - это Fork а значит недоступен SecurityContext и секреты. То есть при любом форке - секреты недоступны, только если автор репозитория специально руками не сделает это(по умолчанию выключено).
Однако есть директива pull_request_target
https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#pull_request_target
Если в каком-то репозитории есть такая директива = при форке и пулл реквесте мы получаем доступ к SecurityContext.
2)GitHub не дурак и отслеживает любые изменения файлов воркфлоу.
Даже если у нас есть pull_request_target, но мы взяли и подменили workflow файл, добавив туда нечто типа run: python scripts/print_secret.py - этих изменений не будет в воркфлоу - есть дифф между оригиналом

Это всё теория, а что насчёт практики.
Чтобы "взломать все интернеты" нам надо соблюдение таких условий
- чтобы была опасная директива pull_request_target - "pull_request_target"
- чтобы директория была - path:.github/workflows/
- чтобы мы могли как-то где-то что-то запустить, НЕ изменяя файл воркфлоу, но заинжектить свой вредоносный код, а точнее защититься от такой ситуации в своих публичных репозиториях.

Как мы можем заинжектить, не меняя ничего в воркфлоу?
Нам надо найти куски кода, где в run есть:
- нечто типа python script.py (подменяем сам скрипт и он выполняется, извлекая секреты)
- pip install setup.py инжектим pre-install и выполнение нашего кода, получаем доступ к секретам)
-npm install/command (ну тут понятно - лепи что хочешь)
- всё подобное, что выше, но для других языков/утилит

В двух словах поиск будет типа
org:*****  path:.github/workflows/ "pull_request_target" "${{ secrets." "install"


В общем и целом это помогает злоумышленнику получить доступ к вашим секретам через форк и пулл реквест.
Однако когда я начал писать об этом репорт, я уже решил почитать документацию и везде написано, что такие действия опасные.
То есть тот, кто включил у себя pull_request_target - сам себе злобный буратино.
Та же ситуация с запуском скриптов из всех примеров выше.
Вероятно от https://hackerone.com/ я получу такой же ответ.
Репорт не стал писать. И слишком много условий и в документации есть миллионы ворнингов.

Итоги.
Первые две истории - это реальные репорты с реальным ответом. Все семь репортов мне закрыли с
Informative (Closed). Само собой без каких-либо выплат. Остальные пять репортов я тут публиковать не буду - их них ещё можно протянуть дальше кейсы и попробовать дальше эволюционировать в реальную уязвимость (они уровнем выше, чем эти истории на мой взгляд).
Третья же история это самостоятельное погружение "а как это работает".

Для себя же стоит запомнить:
- никогда не использовать в своих публичных репозиториях pull_request_target - при форке идёт передача SecurityContext и все секреты отдаются публично.
- нникогда не использовать в своих публичных репозиториях запуск сторонник скриптов типа python script.py
- не рассчитывать на сохранность всех секретов GitLab
- я ещё поиграю в хакера и попробую заработать на других потенциальных уязвимостях

И да - я опять поленился писать лонгрид в один пост, сорян.
1👍173🔥2😴1
#gitlab #troubleshooting #одинденьизжизни

Тихий убийца времени.


На новом проекте дали задачу: небольшие изменения в гошном операторе Kubernetes.
Секреты, cluster-scoped объекты, ничего космического.
Покрутил код, разобрался в архитектуре, поднял локальный kind-кластер, погонял тесты.
Makefile на месте, всё зелёное. Красота.

Коммичу, пушу MR.
Тесты падают. 🙃

Ну ладно, думаю. Я серьёзно менял логику, наверное поломал что-то. Бывает.
Возвращаюсь, перепроверяю. Локально гоняю - всё проходит.
Коммичу, пушу - снова падает.

Ок, уже интересно.

Открываю .gitlab-ci.yml, читаю джобу тестов строчка за строчкой.
Повторяю ровно то же самое локально - make test, всё зелёное.
В GitLab - красное.

Смотрю логи джобы внимательнее.
Один из тестов падает на скачивании image из container registry соседнего репозитория. Не хватает авторизации. Ага.

Проверяю:
- мой GitLab токен - валидный
- доступ в соседние репозитории (сайдкар-образы для тестов) - есть
- образы в registry - на месте
- пайплайн до моих изменений - зелёный, тесты проходили
Всё на месте, а не работает.🤡

Перепроверяю ещё тысячу раз.
Локально - 100% то же самое - работает.
В GitLab - нет.

Тут приходит мысль: а может дело не в коде, а в правах?
Пайплайн-то запускается от моего имени.
А у тех, кто запускал до меня, может быть что-то другое.

Иду смотреть настройки репозитория.
Settings - нет доступа.
Access Tokens - нет доступа.
Deploy Tokens - нет доступа.
У меня Developer, без права заглянуть хоть куда-то в настройки.

Пишу в личку коллеге, который точно Admin:
- Слушай, можешь по этой ссылке просто нажать Retry на джобе тестов?
Он жмёт.
Тесты проходят. Сука.

Я жму Retry.
Тесты падают. Сука.

Пишу главной команде DevOps (да, оксюморон - девопс пишет девопсам "спасити-памагити").
Объясняю ситуацию: так и так, тесты падают только когда пайплайн запускает мой юзер, предполагаю, что CI_JOB_TOKEN моего профиля не имеет нужных прав, он передаётся в кубер как image pull secret, а там авторизация не проходит.

Ну, после некоторых усилий удалось донести мысль. Ребята берут таймаут.

Через несколько дней/часов:
- Ретрай.
Ретраю.
Зелёное. 🎉

- А что было?
- Мы тоже долго дебажили, скормили все токены в Claude, но нашли.
У тебя в профиле
GitLab стояла галочка External User.

External. User. Сука.
Галочка. В профиле. Которую я не ставил. Которую даже не видел (у меня нет доступа к админ-панели). Которую кто-то когда-то поставил при создании моего аккаунта. Или не снял. Или поставил по дефолту в LDAP/SAML/SCIM-маппинге. Или просто потому что.

Что делает External User в GitLab:
- https://docs.gitlab.com/administration/external_users/
- внешний пользователь не имеет доступа к internal-репозиториям
- CI_JOB_TOKEN наследует ограничения профиля
- даже если у тебя есть явный Developer-доступ к проекту, некоторые internal-ресурсы (включая container registry соседних проектов) могут быть недоступны
- в логах пайплайна это выглядит как обычная 401/403 при pull image - ни слова про external user

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

Потрачено:
- ~6-7 часов моего времени на дебаг
- N часов времени DevOps-команды
- массу нервных клеток
- массу токенов в Claude

Выводы:
- если пайплайн падает только у вашего юзера, а у коллег - нет, проблема не всегда в коде.
Проблема иногда в правах. Не в правах репозитория, а в правах профиля.
- External User в GitLab - это тихий убийца. Никаких предупреждений в UI, никаких баннеров "вы external". Просто CI_JOB_TOKEN молча получает урезанные права.
- вы не увидите эту галочку сами - она в Admin Area, доступ только у администраторов GitLab.
- при онбординге на новый проект спросите: "А мой аккаунт точно не external?" Серьёзно. Одна галочка - полдня жизни.
- локальные тесты != CI тесты. Даже если команды идентичны. Контекст авторизации разный.
- и как обычно: во всём виноваты девопсы. Даже когда девопс - это ты сам. 😢
Please open Telegram to view this post
VIEW IN TELEGRAM
👍18🤡2💔1