
تحقق قالب Consul من المكان الذي يشير إليه الارتباط الرمزي (symlink) أثناء تقييم القالب، لكن جلب التبعية اللاحق قرأ المسار الأصلي. إعادة توجيه الارتباط بين هاتين العمليتين حوّلت مرجع ملف داخل الصندوق الرملي إلى كشف ملف خارج الصندوق الرملي.
تحقّق Consul Template من نقطة ارتباط رمزية (symlink) أثناء تقييم القالب، لكن جلب الاعتماد اللاحق قرأ المسار الأصلي. إعادة توجيه الرابط بين هاتين العمليتين حوّلت مرجع ملف داخل الصندوق الرملي إلى كشف ملف خارج الصندوق الرملي.
عثرت على هذه المشكلة أثناء مراجعة HashiCorp Consul Template، ومعي سؤال أمني محدد جدًا:
إذا كان sandbox_path يتحقق من رابط رمزي أثناء تقييم القالب، فهل تبقى قراءة الملف اللاحقة مرتبطة بنفس الهدف المُتحقَّق منه؟
في هذه الحالة، كانت الإجابة: لا.
محلّل القالب file حلّ المسار وطبّق الصندوق الرملي المُعدَّل أثناء تقييم القالب. ولكن بعد ذلك الفحص، أنشأ اعتماد ملف (dependency) باستخدام المسار الخام الأصلي.
تم جلب ذلك الاعتماد لاحقًا.
إذا أعاد المهاجم توجيه الرابط الرمزي في الفجوة بين التحقق وجلب الاعتماد، يمكن لـ Consul Template قراءة ملف خارج الصندوق الرملي. وإذا تمت استعادة الرابط قبل العرض التالي، نجح تحقق الصندوق الرملي مرة أخرى، وتم عرض المحتوى الخارجي المخزَّن مؤقتًا أيضًا.
أصبحت تلك المشكلة CVE-2026-5061.
نشرة HashiCorp: HCSEC-2026-12
نشرة IBM: النشرة الأمنية CVE-2026-5061
CVE: CVE-2026-5061
تم الإصلاح في: 0.42.0
photo0
رابط رمزي داخل الصندوق الرملي يتحكم فيه المهاجم -> تحقق الصندوق الرملي يحلّه إلى هدف آمن -> يخزّن الاعتماد المسار الخام الأصلي -> المهاجم يعيد توجيه الرابط خارج الصندوق الرملي -> جلب الاعتماد يقرأ ملفًا خارجيًا -> المهاجم يستعيد الرابط الآمن -> ينجح التحقق التالي -> يُعرض المحتوى الخارجي المخزَّن مؤقتًا
Consul Template هو أداة عرض قوالب لبيانات Consul وVault.
يمكنه العمل باستمرار، ومراقبة تغييرات الاعتمادات، وعرض البيانات في ملفات أو متغيرات بيئة لتستهلكها التطبيقات.
محلّل القالب file يقرأ ملفًا محليًا ويدرج محتوياته في المخرجات المعروضة.
نظرًا لأن قراءة الملفات المحلية قد تكشف أسرارًا يمكن للعملية قراءتها، يوفر Consul Template خيار sandbox_path كحدود لهذا المحلّل.
الخاصية الأمنية الموثّقة واضحة:
file يجب أن تقع داخل الصندوق الرملي المُعدَّلهذا يجعل sandbox_path حدودًا أمنية حقيقية.
السؤال المهم لم يكن ما إذا كان المسار يبدو واقعًا تحت الصندوق الرملي.
السؤال الحقيقي كان:
هل يبقى الملف الذي تتم قراءته هو نفس الملف الذي اجتاز تحقق الصندوق الرملي؟
في الإصدارات المعرضة للثغرة، لم يكن كذلك.
غالبًا ما تفشل الصناديق الرملية لنظام الملفات عند الفجوة بين التحقق من المسار والوصول إلى الملف.
النمط الشائع هو:
هذا الافتراض غير آمن عندما يمكن للمهاجم تعديل رابط رمزي أو أي تحويل ملفات مكافئ بين العمليتين.
جعل Consul Template هذا السطح مثيرًا للاهتمام بشكل خاص لأن تقييم القالب وجلب الاعتمادات كانا مرحلتين منفصلتين.
ذلك الانفصال خلق السؤال الأمني الصحيح:
هل جلب الاعتماد مرتبط بالهدف المُتحقَّق منه، أم أنه يحلّ المسار الذي يتحكم فيه المهاجم مرة أخرى؟
كانت تلك هي الحدود التي ركزت عليها.
كان السبب الجذري عبارة عن عدم تطابق بين وقت الفحص ووقت الاستخدام (TOCTOU) بين:
template/funcs.godependency/file.goفي النسخة المختبرة، كان fileFunc() يقوم بما يلي:
normalized := strings.TrimSpace(s)
err := pathInSandbox(sandboxPath, normalized)
if err != nil {
return "", err
}
d, err := dep.NewFileQuery(s)
محلّل التحقق حلّ الروابط الرمزية بشكل صحيح قبل فحص الاحتواء:
sandboxResolved, err := filepath.EvalSymlinks(filepath.Clean(sandbox))
targetResolved, err := filepath.EvalSymlinks(filepath.Clean(path))
rel, err := filepath.Rel(sandboxResolved, targetResolved)
if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
return fmt.Errorf("'%s' is outside of sandbox", path)
}
إذًا فحص الصندوق الرملي نفسه فهم الهدف المُحلَّل.
لكن ذلك الهدف المُحلَّل تم تجاهله.
NewFileQuery() خزّن السلسلة الأصلية بدلًا من ذلك:
return &FileQuery{
stopCh: make(chan struct{}, 1),
path: s,
}, nil
ثم قرأ جلب الاعتماد اللاحق ذلك المسار مرة أخرى:
data, err := os.ReadFile(d.path)
هذه هي الثغرة بالكامل.
الرمز فحص حلًا واحدًا لنظام الملفات، ثم استخدم حلًا آخر لاحقًا.
لأن الرابط الرمزي قابل للتغيير.
لا يحتاج المهاجم إلى جعل هدف خارج الصندوق الرملي يجتاز pathInSandbox() مباشرة.
يحتاج فقط إلى تغيير ما يحلّ إليه المسار الخام المعتمد مسبقًا بعد التحقق وقبل أن يقوم Fetch() بالقراءة.
التسلسل هو:
pathInSandbox()FileQuery يسجّل مسار الرابط الخامos.ReadFile(d.path) يحلّ الرابط مرة أخرىتلك الخطوة الأخيرة مهمة.
استعادة الرابط الآمن لم تُزل السر الذي تم جلبه بالفعل من ذاكرة التخزين المؤقت للاعتمادات.
التمييز المهم هو تجاوز ضابط أمني صريح.
لم يكن هذا مجرد:
"الملف تغيّر بينما كان Consul Template يراقبه"
مراقبة الملفات للتغييرات سلوك متوقع.
المشكلة الحقيقية كانت:
مسار اجتاز قيد الصندوق الرملي الموثّق يمكن استخدامه لاحقًا لقراءة ملف خارج ذلك الصندوق الرملي
هذا فشل مباشر لحدود الثقة.
كان التطبيق قد اتخذ بالفعل قرارًا أمنيًا:
لكن الجلب اللاحق لم يكن مرتبطًا بالهدف الذي برّر ذلك القرار.
هذا هو ما حوّل قابلية تغيّر نظام الملفات العادية إلى ثغرة أمنية.
بنيت أداة إعادة إنتاج مستقلة حول التسلسل الدقيق لتقييم القالب وجلب الاعتماد.
الإعداد المتحكم به استخدم:
مسار إعادة الإنتاج كان:
file بحيث ينجح تحقق الصندوق الرملي ويُسجَّل المسار الخام كاعتماد.os.ReadFile(d.path) يقرأ عبر الرابط المعاد توجيهه.السلوك المرصود كان:
تلك المقارنة بالتجزئة كانت مهمة.
أثبتت أن القيمة المعروضة النهائية لم تكن محتوى آمنًا قديمًا، أو أثر اسم ملف، أو أثرًا جانبيًا لمسار خطأ.
كانت المحتوى الحرفي (byte-exact) للملف خارج الصندوق الرملي.
أقوى إثبات لمشكلة TOCTOU يجب أن يتحكم في الخط الزمني.
مجرد إظهار أن رابطًا رمزيًا يمكن أن يشير خارج الصندوق الرملي سيكون أضعف لأن pathInSandbox() كان يرفض بالفعل هدفًا خارجيًا عندما يرصده.
الادعاء الأمني اعتمد على إثبات كل هذه الحالات بالترتيب:
لهذا فصلت أداة إعادة الإنتاج بين تحقق القالب، وجلب الاعتماد، واستعادة الرابط، ثم العرض التالي بشكل صريح.
لم يعتمد إثبات المفهوم على توقيت عشوائي أو تخمين متكرر.
قاد دورة الحياة المعرضة للثغرة بشكل حتمي.
هذا جعل الدفاع عن السبب الجذري والتأثير أسهل بكثير.
تتطلب هذه المشكلة تأثيرًا محليًا على نظام الملفات.
يحتاج المهاجم إلى وصول كافٍ لإنشاء الرابط الرمزي المعني أو استبداله أو إعادة توجيهه، أو أي مسار مرتبط مكافئ، خلال النافذة المعرضة للثغرة.
كما يجب أن تكون عملية Consul Template:
تلك المتطلبات مهمة.
لم تكن هذه قراءة ملف عشوائية عن بُعد غير مصادق عليها من أي نشر افتراضي.
لكن ضمن نموذج الثقة المحلي المتأثر، كان التأثير ذا معنى:
sandbox_path الموثّقيزداد الخطر عندما تعمل العملية مع إمكانية الوصول إلى بيانات اعتماد حساسة، أو إعدادات الخدمة، أو الرموز، أو المفاتيح الخاصة.
صنّف HashiCorp المشكلة على النحو التالي:
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N
الدرجة الأساسية المنشورة هي:
4.7 / Medium
تعكس هذه الدرجة القيود الحقيقية:
هذا تصنيف معقول.
المشكلة ضيقة من حيث المتطلبات المسبقة للمهاجم، لكن تجاوز الصندوق الرملي وكشف الملفات الناتج كلاهما ملموس.
نشرة HashiCorp تسرد:
Affected: consul-template up to 0.41.4
Fixed: consul-template 0.42.0
تم شحن الإصلاح في إصدار 0.42.0 بتاريخ 15 أبريل 2026.
كان الإصلاح صغيرًا وعالج الارتباط المكسور مباشرة.
بدلًا من التحقق من الهدف المُحلَّل ثم بناء الاعتماد من المدخلات الخام، يعيد الرمز المُصحَّح المسار المُحلَّل ويعطيه إلى NewFileQuery():
resolvedPath, err := resolveSandboxedPath(
sandboxPath,
strings.TrimSpace(s),
)
if err != nil {
return "", err
}
d, err := dep.NewFileQuery(resolvedPath)
الخاصية الأمنية تغيّرت من:
إلى:
هذا يغلق مسار إعادة توجيه الرابط المبلغ عنه لأن تغيير الرابط الأصلي لم يعد يغيّر المسار المخزَّن بواسطة الاعتماد.
أضاف التصحيح أيضًا اختبار انحدار مركّزًا يغطي التسلسل الدقيق:
هذا هو نوع المعالجة الذي تريده لهذه الثغرة:
أبلغت عن هذه المشكلة بشكل خاص إلى أمن HashiCorp في 20 مارس 2026.
تضمن التقرير:
أصلح HashiCorp المشكلة في 0.42.0 ونشر HCSEC-2026-12 بتاريخ 12 مايو 2026.
نشرت IBM نشرة أمنية مقابلة لنفس CVE.
اعترفت النشرتان الرسميتان بالتقرير باسم:
Mohamed Abdelaal (0xmrma)
الدرس الرئيسي بسيط:
التحقق من المسار ليس كافيًا إذا كانت عملية الملف اللاحقة يمكن أن تحلّ ذلك المسار إلى كائن مختلف
تلك القاعدة تنطبق أبعد من Consul Template بكثير.
إنها مهمة في أي مكان يقوم فيه الكود بما يلي:
الخاصية الأمنية الأعمق ليست:
"سلسلة المسار بدت آمنة مرة واحدة"
إنها:
الكائن المستخدم في العملية الحساسة يجب أن يكون الكائن الذي اجتاز التحقق
في هذه الحالة، تحقق Consul Template من حلّ وجلب حلًا آخر.
تلك الفجوة كانت كافية.
sandbox_path حدودًا أمنية صريحة للملفات المحليةpathInSandbox() حلّ وتحقق من هدف الرابط الرمزي بشكل صحيحFileQuery احتفظ بالمسار الأصلي المتأثر بالمهاجمos.ReadFile0.42.0 أصلح الثغرة بربط الاعتماد بالمسار المُحلَّل والمُتحقَّق منهلم تكن هذه الثغرة عن تجاوز فحص بادئة سلسلة.
كانت عن الزمن والهوية.
تحقق Consul Template من أين يشير الرابط أثناء تقييم القالب. طلب جلب الاعتماد لاحقًا من نظام الملفات نفس السؤال مرة أخرى. يمكن للمهاجم تغيير الإجابة بين هاتين اللحظتين.
لهذا أصبحت CVE-2026-5061.
تم الإصلاح في consul-template 0.42.0.