Proof-of-concept и лабораторная работа для Gitea IDOR (CWE-639) в GET /api/v1/user/keys/{id}, позволяющая аутентифицированному токену read:user перечислять SSH-ключи и deploy-ключи других пользователей.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · gitea-user-keys-idor
Gitea 1.27.3 - Gitea
GET /api/v1/user/keys/{id} находится в группе текущего пользователя (reqToken, rejectPublicOnly). Обработчик по-прежнему загружает строку по числовому ID без проверки владельца. В той же таблице хранятся пользовательские ключи, deploy-ключи и principals. Аналогичный обработчик GPG делает это правильно: GetGPGKeyForUserByID(ctx, ctx.Doer.ID, id). Приватные поля защищены. Публичный ключевой материал, заголовок и отпечаток — нет. ID последовательны.
Токен read:user перебирает 1, 2, 3 и инвентаризирует чужие SSH- и deploy-ключи. Открытая регистрация превращает это в баг для новых аккаунтов.
| ID | CVE пока нет |
| CWE | CWE-639 |
| CVSS | High: 7.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| Продукт | Gitea |
| Затронуто | вплоть до v1.27.3 (146cc3e) |
| Аутентификация | аутентифицированный (read:user токен) |
| Лицензия | GNU Affero GPL v3.0 |
| Лаборатория | только 127.0.0.1 |
Зарегистрироваться (открытая регистрация по умолчанию), выпустить токен read:user, перебрать /api/v1/user/keys/{id}. Он получает заголовки, отпечатки и публичный ключевой материал для ключей, которыми не владеет, включая deploy-ключи. Приватный ключ он не получает. Шелл он не получает. Он инвентаризирует, у кого есть доступ к чему.
GHSA по ключам в 1.27.3 был ограничен именем пользователя списком плюс individualPermsChecker. Это был не этот lookup.
Тот же тег, остаток: git HTTP redirect SSRF.
v1.27.3 закрыл волну GHSA 2026 года. Я всё равно остался на этом теге и прочитал обработчик ключей текущего пользователя рядом с GPG. GPG возвращает 404 для чужого id. SSH — нет.
seed.sh подкладывает заголовок ключа жертвы GHSA-GITEA-KEY-IDOR-WITNESS и ключ атакующего. В собственном списке атакующего свидетеля нет. GET /user/keys/1 от имени атакующего — есть. GPG с чужим id возвращает 404. Это и есть весь аргумент.
Уже зафиксированные неверные повороты: трактовка собственного списка атакующего как утечки (это не так — список ограничен, GET-by-id — нет); reverse shell. Театр.
Клиентские баги выглядят как баги продукта. Хелпер, который затеняет http.client, никогда не отправляет пакет. .recv() на HTTPResponse — это не .read(). Я упоминаю это один раз, потому что это стоило времени и на минуту выглядело так, будто 1.27.3 довёл дело до конца.
cd lab
./run.sh
Цель только http://127.0.0.1:18101.
add-key user=victim status=201 title=GHSA-GITEA-KEY-IDOR-WITNESS id=1
add-key user=attacker status=201 id=2
attacker-list status=200 ids=[2] (no witness)
get id=1 status=200 snippet contains GHSA-GITEA-KEY-IDOR-WITNESS
SUCCESS Gitea #1 user/keys IDOR
404, если только key.OwnerID == ctx.Doer.ID (или администратор сайта). Отклонять типы deploy/principal. Соответствовать GPG.
GetPublicKeyGNU Affero GPL v3.0. См. LICENSE. Только loopback-лаборатория. Без гарантий.