
Proof-of-concept и лабораторная работа для DoS в Gitea Actions artifact v4: неограниченный io.ReadAll в parseProtobufBody позволяет токену задания отправлять POST-запросы с телами CreateArtifact неограниченного размера.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · gitea-artifact-v4-readall
Gitea 1.27.3 - Gitea
job_summary.go уже ограничивает размер на MaxJobSummarySize (1 MiB) с помощью io.LimitReader. parseProtobufBody в artifact v4 выполняет io.ReadAll(ctx.Req.Body) без ограничения. CreateArtifact, Finalize, List, Get, Delete — все проходят через него. Случайного пользовательского PAT недостаточно. Нужен токен запущенного Actions job. Fork-PR jobs на 1.27.3 всё ещё требуют NeedApproval.
Job-summary усвоил LimitReader. Artifact v4 его не скопировал. Живой job может отправить POST CreateArtifact на 12 MiB, и Gitea буферизует всё это.
| ID | CVE пока нет |
| CWE | CWE-400 |
| CVSS | Medium: 6.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H |
| Продукт | Gitea |
| Затронуто | вплоть до v1.27.3 (146cc3e) с включёнными Actions |
| Аутентификация | токен запущенного Actions job |
| Лицензия | GNU Affero GPL v3.0 |
| Лаборатория | только 127.0.0.1 |
Из запущенного Actions job (своего репозитория или fork-PR, уже прошедшего NeedApproval) отправить POST с произвольно большим телом v4 CreateArtifact и заставить Gitea буферизовать его целиком. Job-summary на том же экземпляре уже отклоняет всё, что превышает 1 MiB.
Это давление на память процесса Gitea. Лаборатория подтвердила отсутствие ограничения (12 MiB -> 200). Процесс при этом не был убит. Это не RCE. Это не неаутентифицированный доступ. Украденный пользовательский PAT без живого job до этого пути не доходит.
Оставшаяся карта после волны GHSA включала обработку тел запросов Actions. Fork-PR jobs на 1.27.3 всё ещё требуют NeedApproval, так что это не пропуск неаутентифицированного fork-PR. Я посмотрел, как Actions API читает тела запросов. У job-summary есть число. У artifact v4 — нет.
Я поднял gitea/act_runner:0.2.13, создал hold workflow, чтобы существовал токен job, и отправил POST CreateArtifact на 12 MiB. HTTP 200 ok плюс signedUploadUrl. Затем job-summary на 8 MiB: 400 invalid summary. GET /api/v1/version всё ещё 200. Gitea остался жив. mem_limit лаборатории — 1g. Тело ограничено 12 MiB, чтобы хост не был предметом эксперимента. Только host executor. Без docker.sock.
Уже зафиксированные ошибочные пути: CreateArtifact без токена job (401 — это не баг); убийство процесса / OOM (SUCCESS — это 12 MiB -> 200, тогда как summary на 8 MiB -> 400); трактовка signedUploadUrl как RCE; неаутентифицированный fork-PR; reverse shell. Театр.
cd lab
./run.sh
Цель только http://127.0.0.1:18135. Внутри compose ROOT_URL — http://gitea:3000/, чтобы runner мог зарегистрироваться.
v4-create status=200 sent=12582912
v4-unbounded-readall=1
job-summary status=400 sent=8388608 invalid summary cap=MaxJobSummarySize=1048576
gitea-still-up status=200
SUCCESS GITEA-ARTIFACT-V4-READALL
Обернуть parseProtobufBody в io.LimitReader с ограничением под размер proto, по той же схеме, что и MaxJobSummarySize. CreateArtifact на 12 MiB должен падать так же, как уже падает job-summary на 8 MiB.
GNU Affero GPL v3.0. См. LICENSE. Только loopback-лаборатория. Без гарантий.