Skip to content
KitploitKITPLOIT
FerramentasBlog
Log in
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2021-28378 — Prova de conceito detalhada e análise da CVE-2021-28378, uma vulnerabilidade de XSS armazenado no Gitea permitindo injeção arbitrária de código, escalonamento de privilégios e execução remota de código por meio de hooks do git. | Kitploit
Ferramentas/GitHubGitHub/pandatix/cve-2021-28378
Análise de VulnerabilidadesExploraçãoExploração de Aplicações WebTestes de PenetraçãoAprendizado e EducaçãoDesenvolvimento de Payloads
GitHubpandatix/cve-2021-28378

CVE-2021-28378

Prova de conceito detalhada e análise da CVE-2021-28378, uma vulnerabilidade de XSS armazenado no Gitea permitindo injeção arbitrária de código, escalonamento de privilégios e execução remota de código por meio de hooks do git.

Ver Repositório
414há 8 mesesAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

CVE-2021-28378

Detalhes sobre este CVE aqui.

Este CVE tem como alvo uma falta de escape de strings no lado do cliente a partir de conteúdo obtido do servidor. Permite que um atacante injete código arbitrário facilmente, ao criar um comentário numa issue ou pull request. Isto foi introduzido no commit 7d7ab1eeae43d99fe329878ac9c8db5e45e2dee5, no final de janeiro de 2020, mas devido a um squash não podemos saber exatamente quem fez isto para investigar bugs semelhantes introduzidos.

Antes de mais, vamos perceber de onde vem esta vulnerabilidade. Para isso, vejamos o commit que corrige este CVE.

Antes:

  • Primeiro:
    labels += `<div class="ui label" style="color: ${color}; background-color:#${label.color};">${label.name}</div>`;
  • Segundo:
    html: `
<div>
    <p><small>${issue.repository.full_name} on ${createdAt}</small></p>
    <p><span class="${color}">${svg(octicon)}</span> <strong>${issue.title}</strong> #${index}</p>
    <p>${body}</p>
    ${labels}
</div>
`

Podemos ver que não há qualquer escape HTML em label.name, issue.repository.full_name, issue.title e body, que são strings que um utilizador pode manipular e, portanto, injetar código arbitrário. Observe que issue.repository.full_name não pode realmente ser manipulado para injetar código, pois tem de seguir demasiadas regras ([\w._-]+) que não podemos contornar com este CVE. É exatamente isto que o PR corrige ao convertê-los para htmlEscape.

Depois:

  • Primeiro:
    labels += `<div class="ui label" style="color: ${color}; background-color:#${label.color};">${htmlEscape(label.name)}</div>`;
  • Segundo:
    html: `
<div>
    <p><small>${htmlEscape(issue.repository.full_name)} on ${createdAt}</small></p>
    <p><span class="${color}">${svg(octicon)}</span> <strong>${htmlEscape(issue.title)}</strong> #${index}</p>
    <p>${htmlEscape(body)}</p>
    ${labels}
</div>
`

Compreendendo a vulnerabilidade

Vamos dar uma rápida olhada no ficheiro web_src/js/features/contextpopup.js, para perceber como desencadear o CVE.

...
const {AppSubUrl} = window.config;

export default function initContextPopups() {
  const refIssues = $('.ref-issue');
  if (!refIssues.length) return;

  refIssues.each(function () {
    const [index, _issues, repo, owner] = $(this).attr('href').replace(/[#?].*$/, '').split('/').reverse();
    issuePopup(owner, repo, index, $(this));
  });
}

function issuePopup(owner, repo, index, $element) {
  $.get(`${AppSubUrl}/api/v1/repos/${owner}/${repo}/issues/${index}`, (issue) => {
...
    for (let i = 0; i < issue.labels.length; i++) {
...
      labels += `<div class="ui label" style="color: ${color}; background-color:#${label.color};">${label.name}</div>`;
    }
...

    $element.popup({
      variation: 'wide',
      delay: {
        show: 250
      },
      html: `
<div>
  <p><small>${issue.repository.full_name} on ${createdAt}</small></p>
  <p><span class="${color}">${svg(octicon, 16)}</span> <strong>${issue.title}</strong> #${index}</p>
  <p>${body}</p>
  ${labels}
</div>
`
    });
  });
}

O que vemos aí é que para cada nó com a classe ref-issue, vamos adicionar um popup vulnerável. Agora, precisamos de encontrar onde um ref-issue é enviado ao utilizador: vamos fazer grep (e manter apenas resultados interessantes, portanto nenhum ficheiro de teste ou semelhantes)! O seguinte número de commit corresponde à tag 1.12.4, a versão padrão do ficheiro oficial docker-compose.yml, que é afetada por este CVE.

cd gitea
git checkout 8a51c48eb6367513e0518bcd412e64f6cbfe5e1a
grep -r ref-issue

modules/markup/html.go:		replaceContent(node, m[0], m[1], createLink(link, id, "ref-issue"))
modules/markup/html.go:		replaceContent(node, m[0], m[1], createLink(link, orgRepoID, "ref-issue"))
modules/markup/html.go:		link = createLink(com.Expand(ctx.metas["format"], ctx.metas), reftext, "ref-issue")
modules/markup/html.go:			link = createLink(util.URLJoin(setting.AppURL, ctx.metas["user"], ctx.metas["repo"], path, ref.Issue), reftext, "ref-issue")
modules/markup/html.go:			link = createLink(util.URLJoin(setting.AppURL, ref.Owner, ref.Name, path, ref.Issue), reftext, "ref-issue")
modules/markup/sanitizer.go:	sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`ref-issue`)).OnElements("a")

Agora sabemos que não há conteúdo estático com a classe ref-issue, mas é gerado dinamicamente. O último não é interessante pois é apenas uma regra de política de saneamento. Os outros são interessantes, veremos porquê.

Vamos mergulhar em modules/markup/html.go.

func fullIssuePatternProcessor(ctx *postProcessCtx, node *html.Node) {
	if ctx.metas == nil {
		return
	}
	m := getIssueFullPattern().FindStringSubmatchIndex(node.Data)
	if m == nil {
		return
	}
	link := node.Data[m[0]:m[1]]
	id := "#" + node.Data[m[2]:m[3]]

	// extract repo and org name from matched link like
	// http://localhost:3000/gituser/myrepo/issues/1
	linkParts := strings.Split(path.Clean(link), "/")
	matchOrg := linkParts[len(linkParts)-4]
	matchRepo := linkParts[len(linkParts)-3]

	if matchOrg == ctx.metas["user"] && matchRepo == ctx.metas["repo"] {
		// TODO if m[4]:m[5] is not nil, then link is to a comment,
		// and we should indicate that in the text somehow
		replaceContent(node, m[0], m[1], createLink(link, id, "ref-issue"))

	} else {
		orgRepoID := matchOrg + "/" + matchRepo + id
		replaceContent(node, m[0], m[1], createLink(link, orgRepoID, "ref-issue"))
	}
}

Também precisaremos de entender o que getIssueFullPattern faz.

func getIssueFullPattern() *regexp.Regexp {
	if issueFullPattern == nil {
		appURL := setting.AppURL
		if len(appURL) > 0 && appURL[len(appURL)-1] != '/' {
			appURL += "/"
		}
		issueFullPattern = regexp.MustCompile(appURL +
			`\w+/\w+/(?:issues|pulls)/((?:\w{1,10}-)?[1-9][0-9]*)([\?|#]\S+.(\S+)?)?\b`)
	}
	return issueFullPattern
}

Vamos resumir para entender que trabalho é feito ali.

Em fullIssuePatternProcessor, primeiro precisamos de ter a certeza de que ctx.metas não é nil (o que assumimos ser verdade pois é construído por funções/métodos superiores na stack). Depois verificamos se os dados do nó correspondem a um caminho como http://mygitea.com/gituser/myrepo/issues/1. Se houver correspondência, substituímos por um link. Se o link for sobre o mesmo utilizador e repositório, substituímos pelo padrão #<issue_id>, caso contrário por <user>/<repo>#<issue_id>. Em ambos os casos, uma classe ref-issue é adicionada para gerar o popup.

Os outros resultados do grep fazem algo semelhante.

Hora de brincar!

Agora que temos o contexto, que consiste em substituir um link como http://mygitea.com/gituser/myrepo/issues/1 por um link mais curto, podemos deduzir onde tal tarefa é realizada: nas issues e PRs, onde se pode adicionar uma label e/ou postar um comentário.

Para isso, vamos supor que tens um repositório com permissões de escrita.

Etiqueta

Vai ao teu repositório e cria uma nova etiqueta com um nome contendo código arbitrário (como <script>alert('label.name')</script>).

Issue

Vai às issues do teu repositório e cria uma nova issue com código arbitrário no título e no corpo, e adiciona-lhe a etiqueta anterior.

Vamos executar algum código

Cria uma nova issue em qualquer repositório e adiciona no seu corpo um link para a tua issue injetada, como http://mygitea.com/gituser/myrepo/issues/1. Será substituído pelo link que vimos anteriormente, e quando alguém colocar o rato sobre o link, o código arbitrário será executado: um popup é exibido embutindo label.name.

Hora do exploit

Baixar ferramenta