
كان المساعد writeToFile في Consul Template يفتح وجهة يقدّمها المُشغِّل مباشرةً ويتبع مكوّنات المسار المرتبطة، مما سمح للمخرجات المُولَّدة بالخروج عن الدليل المقصود والكتابة فوق ملف موجود مسبقًا.
الأداة المساعدة writeToFile في Consul Template كانت تفتح الوجهة التي يوفّرها المشغِّل مباشرةً وتتتبّع مكوّنات المسار المرتبطة (روابط)، مما يسمح للمخرجات المُولَّدة بالخروج من الدليل المقصود واستبدال ملف موجود مسبقًا.
اكتشفت هذه المشكلة أثناء مراجعة HashiCorp Consul Template، مع سؤال أمني مباشر يتعلق بنظام الملفات في ذهني:
إذا استلمت writeToFile مسارًا يبدو أنه داخل الدليل المقصود، فهل تتحقق من المكان الذي سيكتب فيه نظام الملفات البيانات فعلًا؟
في هذه الحالة، كان الجواب: لا.
كانت أداة القالب المساعدة writeToFile تفتح المسار النهائي الذي يوفّره المستخدم مباشرةً عبر os.Create() أو os.OpenFile().
هذه العمليات كانت تتتبّع الروابط الرمزية (symbolic links) ونقاط التحام الدلائل (directory junctions) وأي إعادة توجيه مكافئة في نظام الملفات موجودة مسبقًا في مسار الوجهة.
هذا يعني أن سلسلة المسار قد تبقى ضمن الجذر المقصود للمشغِّل بينما تقع الكتابة الفعلية في مكان آخر.
في إثبات المفهوم المُتحكَّم به، أعاد دليل أصلي مرتبط (رابط) توجيه المخرجات المُولَّدة إلى خارج الشجرة المقصودة وتسبّب في استبدال ملف هدف موجود مسبقًا.
أصبحت هذه المشكلة CVE-2026-14361.
نشرة HashiCorp: HCSEC-2026-20
نشرة IBM: النشرة الأمنية CVE-2026-14361
CVE: CVE-2026-14361
تم الإصلاح في: 0.42.1
photo0
operator-supplied destination appears inside intended root -> attacker pre-positions linked parent or final path component -> writeToFile opens the path directly -> filesystem resolves the write outside the intended directory -> rendered secret is redirected -> preexisting target may be overwritten
تقوم Consul Template بتوليد (عرض) البيانات من مصادر مثل Consul وVault.
تسمح الأداة المساعدة writeToFile لقالب بكتابة محتوى مختار إلى ملف محلي منفصل مع تطبيق المالك والمجموعة ووضع الصلاحيات المطلوبة.
توثيق HashiCorp يعرض تحديدًا هذه الأداة المساعدة مع مواد البنية التحتية للمفاتيح العامة (PKI):
private key -> writeToFile /my/path/to/cert.key
certificate authority -> writeToFile /my/path/to/cert.pem
certificate -> append to /my/path/to/cert.pem
هذا يجعلها أكثر من مجرد أداة إخراج عادية.
المحتوى الذي يعبر هذه الحدود قد يتضمن:
السؤال المهم لم يكن ما إذا كانت writeToFile تستطيع إنشاء اسم ملف مطلوب.
السؤال الحقيقي كان:
هل تكتب العملية في موقع نظام الملفات الذي يقصده المشغِّل، أم فقط في أي كائن يتحلَّل إليه المسار وقت الفتح؟
في الإصدارات المصابة، كانت تثق في الخيار الثاني.
أدوات الكتابة المساعدة هي أسطح أمنية عالية القيمة لأنها تنتقل من بيانات التطبيق إلى التعديل على نظام الملفات.
الإخفاقات المثيرة للاهتمام غالبًا ليست اجتياز المسار التقليدي ../.
إنها إخفاقات في التحليل (resolution):
هذا مهم بشكل خاص عندما تعمل العملية بصلاحيات نظام ملفات أعلى من صلاحيات المهاجم.
قد لا يتمكن مهاجم محلي بصلاحيات منخفضة من استبدال ملف حساس مباشرة.
لكن إذا استطاعوا التأثير على مكوّن مسار ضمن دليل كتابة مقصود، فقد تقوم عملية Consul Template بصلاحيات أعلى بتنفيذ الكتابة نيابةً عنهم.
تلك كانت الحدود التي ركّزت عليها.
لم أتعامل مع هذا كمراجعة عامة لاجتياز المسار.
المسار المقدَّم لم يكن بحاجة إلى مقاطع ...
كان بإمكانه البقاء معجميًا (lexically) داخل الجذر المقصود طوال الوقت.
السؤال الأقوى كان:
هل تُرفض مكوّنات الوجهة المرتبطة قبل كتابة المحتوى الحساس؟
هذا السؤال مهم في الحالتين:
الحالة الثانية مفيدة بشكل خاص لأن السجلات والإعدادات ما زالت تعرض مسارًا يبدو بريئًا تحت الدليل المتوقع.
نظام الملفات يحلّه في مكان آخر.
السبب الجذري كان إنشاء ملف مباشر يعتمد على المسار دون تحقق يراعي الروابط (links).
في النسخة المختبرة، كانت writeToFile() تختار واحدة من مساري فتح.
وضع الإلحاق (append) كان يستخدم:
f, err = os.OpenFile(
path,
os.O_APPEND|os.O_WRONLY|os.O_CREATE,
perm,
)
وضع الكتابة العادي كان يستخدم:
dirPath := filepath.Dir(path)
if _, err := os.Stat(dirPath); err != nil {
err := os.MkdirAll(dirPath, os.ModePerm)
if err != nil {
return "", err
}
}
f, err = os.Create(path)
لا أحد من المسارين كان يرفض مكوّنات الوجهة المرتبطة قبل فتح الملف.
هذا مهم لأن:
os.Create(path) يتبع إعادة توجيه نظام الملفات الموجودة ويقتطع (truncate) الملف المُحلَّلos.OpenFile(path, ...) يتبع مكوّنات المسار المرتبطة أثناء وضع الإلحاقنفس الافتراض القائم على المسار استمر بعد الكتابة.
كانت الملكية والصلاحيات تُطبَّق باستخدام المسار مرة أخرى:
err = os.Chown(path, uid, gid)
err = os.Chmod(path, perm)
هذا يعني أن عمليات البيانات الوصفية (metadata) كانت أيضًا مرتبطة باسم مسار قابل للتغيير بدلًا من واصف الملف المفتوح بالفعل.
لأن المهاجم يمكنه تجهيز إعادة التوجيه قبل تشغيل writeToFile.
لا يُشترط سباق (race) احتمالي للهجوم الأساسي.
التسلسل واضح ومباشر:
writeToFile تفتحه مباشرةهذا هو جوهر الثغرة بالكامل.
صحيح أن أنظمة التشغيل تتبع الروابط الرمزية عادةً أثناء فتح الملفات بناءً على المسار.
لكن هذا لا يجعل هذا سلوك تطبيق آمنًا.
السؤال الأمني ليس:
"هل تصرّفت Go وفق ما هو موثّق؟"
السؤال الحقيقي هو:
هل تحققت أداة مساعدة تكتب بيانات حساسة مُولَّدة من أن الوجهة المُحلَّلة تطابق موقع المشغِّل المقصود؟
في الإصدارات المصابة، لم تكن تفعل.
هذا التمييز مهم لأن Consul Template قد تعمل:
اتباع إعادة توجيه نظام ملفات يضعها المهاجم في هذا السياق يخلق مشكلة حقيقية في الصلاحيات وحدود الثقة.
بنيت أداة إعادة إنتاج مستقلة حول سلوك writeToFile الدقيق في commit المختبر.
الإعداد المُتحكَّم به استخدم:
writeToFileتدفق إعادة الإنتاج كان:
السلوك الملاحَظ كان:
writeToFile اتبعت إعادة التوجيههذا أثبت جزأي الادعاء:
رابط رمزي في المكوّن النهائي كان سيثبت بالفعل اتباع الروابط غير الآمن.
لكن مكوّنًا أصليًا مرتبطًا يثبت نقطة تشغيلية أقوى: