
معلومات حول Kubernetes CVE-2020-8558، بما في ذلك استغلال دليل على المفهوم.
CVE-2020-8558 هي ثغرة أمنية في Kubernetes نُشرت لأن kube-proxy بشكل غير متوقع يجعل خدمات المضيف المرتبطة بـ localhost متاحة للآخرين على الشبكة. أضع التركيز على غير متوقع لأن هذه الثغرة ناتجة عن عيب في التصميم (إهمال) وليس عيبًا في التنفيذ (خلل). الكود يفعل بالضبط ما يفترض أن يفعله، لكننا فشلنا جميعًا في إدراك الآثار الأمنية لهذا القرار.
للسماح لعمليات المضيف بالوصول إلى خدمات NodePort عبر عنوان 127.0.0.1 (localhost)، يقوم kube-proxy بتعيين إعداد sysctl net.ipv4.conf.all.route_localnet=1. وفقًا لوثائق النواة، يجعل هذا الإعداد النواة "لا تعتبر عناوين الاسترجاع (loopback) كـ martian" - ونتيجة لذلك يمكن الوصول إليها من قبل عقد أخرى على الشبكة. هذا أمر مهم إذا كانت لديك خدمات حساسة غير موثقة (unauthenticated) وحمايتها الوحيدة هي ربطها بـ localhost!
حتى الآن، لا يزال مجتمع Kubernetes يعمل على تحديد أفضل طريقة لمعالجة CVE-2020-8558. الخياران الواضحان هما التوقف عن تعيين sysctl route_localnet في المقام الأول، أو حظر حزم localnet الموجهة بشكل غير مناسب باستخدام iptables. تم إصدار إصلاح باستخدام الاستراتيجية الأخيرة بالفعل في kubelet >= 1.18.4, 1.17.7, أو 1.16.11. يمكنك أيضًا تطبيقه بنفسك بالرجوع إلى مشكلة Kubernetes لهذه الثغرة، المرتبطة أدناه.
لماذا يستحق تعيين net.ipv4.conf.all.route_localnet=1 معرّف CVE؟ بشكل أساسي لأنه ينتهك حدسنا حول شبكات IP.
منذ ما لا يقل عن RFC 1122 لعام 1989، تم التعامل مع الحزم من شبكة localhost 127.0.0.1/8 بشكل خاص، محظورة من الظهور "خارج المضيف". (يرجى الاتصال بي على تويتر إذا كنت تعرف مرجعًا سابقًا لخصائص 127/8.) أي مضيف متوافق مع RFC لديه بشكل أساسي قاعدة جدار ناري ضمنية وغير قابلة للإزالة تمنع الوصول الخارجي إلى الخدمات المرتبطة بـ 127.0.0.1 (وعناوين IP أخرى في تلك الشبكة - جرب ping 127.127.127.127 إذا لم تفعل من قبل!) لقد أصبحنا نعتمد على هذا السلوك ونتوقعه. غالبًا ما نشغل خدمات حساسة بدون مصادقة أو تشفير، ونربطها بـ localhost للأمان. على سبيل المثال، خلفيات HTTP بنص عادي، مخزن القيم الرئيسية redis، ومنفذ api-server غير الآمن (insecure) القديم في Kubernetes كلها محمية عادةً من الاختراق بهذه الطريقة. لقد اعتدنا على هذا السلوك لدرجة أنه أصبح جزءًا راسخًا من إحساسنا البديهي بما يعنيه أن تكون مضيف IP. من هذا المنظور، من المفهوم أن العديد من الخبراء قد أغفلوا هذا العيب لفترة طويلة.
كيف يعمل؟
دعنا نسمي أي كيان بعنوان IP "عقدة". يتم إرسال حزم IP من عقدة إلى أخرى، مُعرَّفة بعنوان IP المصدر والوجهة في رأس الحزمة. كل عقدة IP هي إما موجه (يُسمى بوابة في RFC1122) أو مضيف. الفرق الأساسي هو أن المضيف عندما يتلقى حزمًا موجهة لعنوان شخص آخر، يتجاهلها. يقوم الموجه بالاطلاع على جدول التوجيه وإعادة إرسال (توجيه) الحزم في محاولة لتقريبها من وجهتها النهائية. سيعرف المضيف بعض العقد المتصلة محليًا؛ للوصول إلى عقد أخرى، يجب أن يرسل حزمه إلى موجه متصل محليًا. قد تكون تلك الاتصالات المحلية من نقطة إلى نقطة (مثل رابط PPP أو بعض الشبكات الافتراضية) أو وسيطة مشتركة (مثل Ethernet).
يمكن تشبيه صندوق البريد الخاص بك كرابط من نقطة إلى نقطة بين منزلك ومكتب البريد المحلي. لتوجيه حزمة عبر رابط من نقطة إلى نقطة، يحتاج المضيف فقط إلى تطبيق عنوان الوجهة الصحيح وإرسال الحزمة. (يحدث هذا على الطبقة الثالثة من نموذج OSI.) لتوجيه حزمة عبر رابط وسيط مشترك، يجب على المضيف أولاً إنشاء دائرة افتراضية من نقطة إلى نقطة عبر الوسيط المشترك. في شبكات Ethernet/IP، يتم ذلك بواسطة ARP، على الطبقة الثانية من نموذج OSI. بشكل أساسي، إذا كان بإمكانك إرسال حزمة ARP، يمكنك إخبار مضيف آخر "مرحبًا، أنا هنا" وسيصدقك. (عند القيام بذلك بشكل غير مناسب، يُسمى تسمم ذاكرة التخزين المؤقت ARP.) ثم يمكنك الاتصال بوضع عناوين Ethernet المصدر والوجهة المناسبة على حزمك.
لن تقوم عقدة عادية أبدًا بإرسال حزمة بعنوان وجهة 127.0.0.1، بسبب RFC 1122. إذا استقبلت عقدة عادية حزمة بعنوان وجهة 127.0.0.1، ستتجاهلها (تسقطها)، مرة أخرى بسبب RFC 1122. تعيين net.ipv4.conf.all.route_localnet=1 يغير ذلك - يسمح بإرسال واستقبال حزم 127.0.0.1 كما لو أنها ليست خاصة.
لذا، إذا كان لدى مهاجم اتصال محلي بعقدة هدف مع net.ipv4.conf.all.route_localnet=1، يمكن للمهاجم إرسال حزمة إليها بعنوان 127.0.0.1 كعنوان وجهة، وستستجيب عقدة الهدف بشكل مناسب كما لو أن 127.0.0.1 عنوان طبيعي تمامًا. أشهر طريقتين للحصول على اتصال محلي بعقدة هدف اليوم هما: أن تكون على نفس شبكة Ethernet (نطاق البث) مثل الهدف، أو أن تكون حاوية تعمل على الهدف.
لاحظ أنه عند التهيئة العادية، لن يسمح Linux للعقدة المهاجمة بإرسال حزم عادية موجهة إلى 127.0.0.1. يمكن تجاوز ذلك بإعادة تهيئة عقدة Linux المهاجمة (إذا كان لديهم صلاحيات الجذر)، أو عن طريق تزوير الحزم باستخدام مقبس خام (raw socket). تتطلب المقابس الخام فقط قدرة Linux kernel CAP_NET_RAW، والتي تُعطى افتراضيًا للحاويات غير المميزة (unprivileged). هذا يعني أن حاوية غير مميزة تحت سيطرة المهاجم قادرة على استغلال CVE-2020-8558.
باختصار، إذا كنت تستخدم kube-proxy أو تقوم بأشياء ذكية مع net.ipv4.conf.*.route_localnet، فأنت مكشوف. يجب أن تقضي بعض الوقت في نمذجة التهديدات لتحديد مدى خطورة هذا التعرض لك والتخطيط لاستراتيجية تخفيف مناسبة.
أساسيًا، كل مضيف Linux مع تعيين net.ipv4.conf.all.route_localnet=1 يكون عرضة للخطر. ما إذا كانت هذه الثغرة مثيرة لاهتمام المهاجم تعتمد على عدة عوامل:
لتقييم CVE-2020-8558، يجب أن تتخيل مهاجمين بقدرات مختلفة، وتجيب على هذه الأسئلة من وجهة نظر هؤلاء المهاجمين. (كتاب Adam Shostack "Threat Modeling: Designing for Security" يصف هذه العملية بتفصيل كبير.) مهاجمان ذوا صلة يجب أن تأخذهما في الاعتبار هما مهاجم لديه عقدة على شبكة Ethernet الخاصة بك، ومهاجم يمكنه تشغيل كود في بود غير مميز (unprivileged pod) على مضيفك. قد يكون هناك مهاجمون آخرون مثيرون للاهتمام يجب أن تأخذهم في الاعتبار أيضًا، اعتمادًا على بيئتك واحتياجاتك.
للتوضيح، إليك مثال تم معالجته جزئيًا:
المضيف بالتأكيد متاح لكلا المهاجمين؛ لقد افترضنا ذلك في كل حالة.
قد تكون الحزم مفلترة أو لا. ستحتاج إلى التحقق. في العديد من بيئات السحابة والشبكات المحلية المُدارة بإحكام، يتم حظر الحزم إذا فشل عنوان IP الوجهة في مطابقة عنوان Ethernet المتوقع من قبل الشبكة. هذا قد يكون بحد ذاته نهاية اللعبة للمهاجم الذي لديه عقدة. إذا كانت جميع عقدك تحتوي على قواعد جدار ناري محلية مناسبة (مثل تلك المقدمة من kubelet محدثة)، فسيؤدي ذلك إلى فشل كلا المهاجمين.
من المحتمل أن تكون هناك خدمات أكثر إثارة للاهتمام مما تعتقد. من الواضح أن منفذ api-server غير الآمن في Kubernetes هو هدف جذاب للغاية، ويجب عليك تعطيله إذا استطعت. افحص جميع العمليات المرتبطة بعناوين IP في شبكة 127.0.0.0/8: هل لديها مصادقة قوية؟ إذا لم يكن الأمر كذلك، فقد تتعرض عبر CVE-2020-8558. حتى لو كانت جميع خدمات localhost العادية آمنة، فإن الخدمات المؤقتة يمكن أن تكون مصدر قلق أيضًا. على سبيل المثال، غالبًا ما يُستخدم إعادة توجيه منفذ SSH لتجاوز قيود الشبكة لأغراض مؤقتة مصرح بها. افتراضيًا، يتم ربط منافذ SSH المعاد توجيهها بـ localhost بحيث يُسمح بالوصول المؤقت فقط للمستخدمين المصرح لهم. مع CVE-2020-8558، تصبح عمليات إعادة التوجيه "الآمنة" هذه متاحة للمهاجمين أيضًا.
بافتراض أن لديك صلاحيات الجذر على جهاز Linux في نفس نطاق البث (broadcast domain) مثل الهدف، فإن إعدادات التهيئة التالية ستسمح لك باستغلال CVE-2020-8558:
ip addr add 127.0.0.2/8 dev lo
ip addr del 127.0.0.1/8 dev lo
ip route add 127.0.0.1/32 via YOUR-TARGET-HERE
sysctl net.ipv4.conf.all.route_localnet=1
نظرًا لأن بعض الخدمات المهمة (سعال، سعال، systemd-resolved) تعمل على عنوان في نطاق 127.0.0.0/8، نضيف عنوانًا جديدًا لمنع تعطيل المضيف. بعد ذلك، يجب على المضيف أن ينسى عنوانه الافتراضي 127.0.0.1/8. بعد ذلك، نوجه النواة لتوجيه حركة المرور الخاصة بـ 127.0.0.1 عبر الشبكة إلى هدفك، الذي يعرف كيفية الوصول إلى 127.0.0.1. أخيرًا، نضبط sysctl سيئ السمعة الذي كان سيمنع هذا التهيئة من العمل.
نص Python بسيط لاختبار CVE-2020-8558 عن طريق إرسال حزم خام. يمكن أن يكون هذا سطرًا واحدًا في scapy، لكنني أردت إضافة القليل من وسائل الراحة المنزلية. يرسل حزمة إلى 127.0.0.1 عبر هدفك، ويتحقق مما إذا كان هناك رد.
نص Python لاستغلال CVE-2020-8558 عن طريق السماح لتطبيقات العميل TCP أو UDP العادية بالتواصل مع عنوان IP localhost عن بعد عبر حزم مزورة. شغّل هذا النص، ثم استخدم أي عميل TCP أو UDP عادي (مثل kubectl أو nc) للاتصال بـ fakedestination الخاص بك (198.51.100.1 افتراضيًا).
لاحظ أن fakedestination يجب أن يكون عنوان IP لا يستجيب أبدًا للحزم ويجب أن يكون مسارك إليه عبر نفس الواجهة التي تصل بها إلى هدفك. في الحالة المعتادة، سيكون كل من fakedestination والهدف قابلين للوصول عبر واجهة البوابة الافتراضية الخاصة بك، ولن يكون هذا مشكلة كبيرة.
نظرًا لأن هذا النص يستخدم مقابس خام (raw sockets) لإرسال واستقبال حزم "localhost"، فإنه يعمل بشكل جيد داخل حاوية غير مميزة عادية.
مشكلة Kubernetes لهذه الثغرة على GitHub
Adam Shostack: نمذجة التهديدات
تحية خاصة إلى Ian Coldwater, Brad Geesaman, Duffie Cooley, و Laurent Bernaille. شكرًا على الأفكار والنصائح والضحكات، يا جميعًا. Honk the planet!