
Forgejo 16.0.5 — высокий уровень — удалённо — IDOR — GET /api/v1/user/keys/{id} возвращает SSH-ключи и deploy-ключи других пользователей
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · forgejo-keys-idor
Разбор: https://abraxaslabs.tech/research/forgejo-keys-idor
Класс: IDOR Доступ: Удалённый
Forgejo 16.0.5 (8d24b7d) - Forgejo
GET /api/v1/user/keys/{id} находится в группе текущего пользователя (reqToken, область пользователя). Обработчик по-прежнему загружает public_key по числовому ID без проверки владельца. В той же таблице хранятся пользовательские ключи, deploy-ключи и principals. GPG на этой версии работает правильно: GetGPGKeyForUserByID(ctx, ctx.Doer().ID, id). Приватные дополнительные поля защищены. Материал публичного ключа, заголовок и отпечаток — нет. ID идут последовательно.
Токен с read:user перебирает 1, 2, 3 и инвентаризирует чужие SSH- и deploy-ключи.
| ID | CVE пока нет |
| Класс | IDOR |
| Доступ | Удалённый (аутентифицированный read:user / сессия) |
| CWE | CWE-639 |
| CVSS | Высокий: 7.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| Продукт | Forgejo |
| Затронуто | по v16.0.5 (8d24b7d) |
| Аутентификация | аутентифицированный (read:user токен или сессия) |
| Лицензия | GNU Affero GPL v3.0 |
| Лаборатория | только 127.0.0.1 |
Зарегистрировать или использовать любой аккаунт, выпустить токен read:user (или использовать сессионную cookie), перебирать /api/v1/user/keys/{id}. Он получает заголовки, отпечатки и материал публичных ключей для ключей, которыми не владеет. Он не получает приватный ключ. Он не получает shell. Он инвентаризирует, у кого есть доступ к чему.
| Привилегия | Метод / sink | Разведка / целостность / доступность | Доказано в лаборатории или выведено |
|---|---|---|---|
| Чтение заголовка, отпечатка и материала SSH-ключа другого пользователя | GET /api/v1/user/keys/{id} от чужого владельца | Разведка (конфиденциальность инвентаря публичных ключей) | Доказано в лаборатории. Заголовок-свидетель FORGEJO-KEYS-IDOR-WITNESS на id 1; список атакующего содержит только id 2 |
| Инвентаризация deploy-ключей тем же способом | Тот же обработчик, строки KeyTypeDeploy в public_key (OwnerID 0) | Разведка | Выведено из таблицы. В лаборатории был размещён пользовательский SSH-ключ, а не deploy-ключ |
| Чтение principals SSH-сертификатов | Тот же обработчик, строки KeyTypePrincipal. ListPublicKeys исключает этот тип | Разведка | Выведено из таблицы |
| Просмотр ключей, принадлежащих ограниченным или приватным пользователям | Нет проверки видимости при этом поиске | Разведка | Выведено из обработчика |
Дополнительный JSON (user, key_type, read_only) | appendPrivateInformation только для администратора или владельца | Разведка | Доказано в лаборатории как закрытое: ответ для не-владельца не содержал поля user |
Закрыто на этом маршруте: материал приватного ключа, shell, GET /users/{username}/keys (публичный список по имени пользователя), GPG GET /api/v1/user/gpg_keys/{id} (404 на чужой id), DeletePublicKey (403 для владельца/администратора). Кросс-репозиторный GET /repos/{owner}/{repo}/keys/{id} возвращает 404, если RepoID не совпадает.
Патчи безопасности v16.0.5 (#14370) были git-apply RCE и OpenID CSRF. Они не закрыли этот поиск. Тот же класс, что и gitea-user-keys-idor, независимо всё ещё открыт здесь.
Я зафиксировал Forgejo v16.0.5 после волны 2026-09. В Gitea 1.27.3 этот поиск всё ещё присутствовал. Forgejo разошёлся, поэтому я перечитал обработчик ключей текущего пользователя рядом с GPG, вместо того чтобы копировать пакет Gitea. GPG возвращает 404 на чужой id. SSH — нет.
Ошибочные повороты уже зафиксированы: трактовка собственного списка атакующего как утечки (это не так — список ограничен по области, а GET-by-id — нет); трактовка DISABLE_REGISTRATION=true на установщике как закрытия (достаточно любого существующего аккаунта); обратная оболочка. Театр.
python3 forgejo-keys-idor-Abraxas-Labs.py
или:
cd lab
./run.sh
Нацеливайтесь только на http://127.0.0.1:19310. Образ codeberg.org/forgejo/forgejo:16.0.5. Два аккаунта. Свидетель FORGEJO-KEYS-IDOR-WITNESS.
IOC planted victim-id=1 attacker-id=2
IOC attacker-list status=200 ... title=attacker-own-key
IOC get id=1 status=200 ... FORGEJO-KEYS-IDOR-WITNESS
IOC gpg-neg id=1 status=404
SUCCESS forgejo-keys-idor victim-id=1 get-http=200 list-has-id=no gpg-http=404 FORGEJO-KEYS-IDOR-WITNESS
404, если только key.OwnerID == ctx.Doer().ID (или администратор сайта). Отклонять KeyTypeDeploy / KeyTypePrincipal на этом маршруте. Соответствовать GetGPGKeyForUserByID.
GetPublicKeyGNU Affero GPL v3.0. См. LICENSE. Только для лаборатории с loopback. Без гарантий.