SanDssrf — احتواء SSRF في Node.js
- Home
- المشاريع
نظرة عامة على المشروع
احتواءٌ لا مجرد حجب. عصر جديد في الدفاع ضد SSRF.
SanDssrf هي إجابتي عن سؤال ظللت أصطدم به وأنا أكسر دفاعات SSRF — بما فيها دفاعاتي أنا. فالمكتبات مثل DSSRF تتحقق من الرابط ثم تترك الطلب يمضي، وكل واحدة منها يمكن هزيمتها باختلاف في المحلِّلات أو بإعادة ربط DNS أو بصيغة IPv6 لم يُدرجها أحد. أما SanDssrf فتتوقف عن التحقق وتبدأ بالاحتواء: يُجرى الاتصال بالروابط التي يزوّدها المستخدم من داخل نطاق أسماء شبكي في Linux يضم شبكة داخلية محاكاة؛ فإن كان العنوان داخليًا وجّهته نواة Linux إلى شبكة خداعية غير ضارة، وإن كان عامًا خرج الطلب كالمعتاد. لا يوجد فحص نطاقات قابل للخطأ ولا سباق يمكن كسبه، فتختفي فئات كاملة من التجاوزات — بينما تواصل قاعدة بياناتك وذاكرتك المؤقتة وواجهاتك الداخلية العمل دون أي تغيير. المكتبة مكتوبة بـ Node.js خالص بلا أي اعتماديات، وصادرة برخصة MIT ومنشورة على npm باسم @insitetechjp/sandssrf.
التحديات
- اختلاف تحليل المُتحقِّق وعميل HTTP للرابط نفسه — فئة «اختلاف المحلِّلات» (parser differential) التي تهزم معظم مكتبات SSRF، بما فيها مكتبتي DSSRF.
- إعادة ربط DNS: قد تتغيّر إجابة DNS بين الفحص والاتصال، وكل تصميم «افحص ثم اتصل» فيه سباق يمكن خسارته.
- كل فحص للنطاقات في مساحة المستخدم يبعد عن التجاوز صيغة IPv6 واحدة منسية أو فرعًا واحدًا من نوع «غير معروف، إذن مسموح».
- لا يقتصر SSRF على HTTP — فبروتوكولات TCP الخام مثل Redis وPostgreSQL تحتاج إلى الحماية نفسها.
- احتواء الطلبات غير الموثوقة دون تعطيل قاعدة بيانات التطبيق نفسه وذاكرته المؤقتة وواجهاته الداخلية.
المنهج
- تبدأ عملية مساعدة في نطاق أسماء مستقل للمستخدم والشبكة (
unshare -Ur -n)، حيث يُثبَّت كل نطاق داخلي كمسار محلي عبر Linux AnyIP. - تحلّ العملية الأم اسم المضيف مرة واحدة فقط، ولا يعبر إلى صندوق العزل إلا العنوان الناتج، فلا يوجد استعلام ثانٍ يمكن تسميمه.
- تطابق أطول بادئة في جدول توجيه النواة هو ما يقرّر ما هو داخلي — لا محلِّل في مساحة المستخدم، ولا قائمة نطاقات قابلة للخطأ، ولا سباق يمكن كسبه.
- يُعاد المقبس المتصل عبر
SCM_RIGHTS؛ ونطاق أسماء المقبس ينتقل مع واصف الملف، فلا يمكنه الوصول إلا إلى صندوق العزل. - تصل الأهداف الداخلية إلى شبكة خداعية خاملة موسومة بالترويسة
x-sandssrf: mockويُطلق حدثblockedبالمضيف والطريقة والمسار؛ أما الوجهات العامة فتتصل فعليًا.
بضعة أسطر فقط
مرِّر Agent الخاص بصندوق العزل إلى المواضع التي تجلب روابط غير موثوقة فقط. إلى جانب Agent لـ http/https تتوفّر الدالة sandbox.connect() للبروتوكولات غير HTTP، ويظل التحقق من شهادة TLS قائمًا على اسم المضيف.
const { createSSRFSandbox } = require('@insitetechjp/sandssrf'); const sandbox = createSSRFSandbox(); const agent = sandbox.Agent(); sandbox.on('blocked', (e) => log.warn('SSRF attempt', e)); // user-supplied URL — contained await fetchWith(agent, userUrl); // everything else is untouched await pgcon.query('select 1');npm install @insitetechjp/sandssrf
الحدود بوضوح
«مكتبة الأمان التي تُخفي حدودها أسوأ من عدم وجودها.»
- يعمل على Linux فقط — ويتطلب
unshare(util-linux) وip(iproute2) ونطاقات مستخدم غير مميّزة. - قد يمنعه ملف seccomp الافتراضي في Docker؛ وعندها يرفض
ready()صراحةً بالرمزSANDSSRF_UNAVAILABLEبدل الفشل المفتوح. - لا تُحتوى إلا المقابس المُنشأة عبر Agent الخاص بصندوق العزل — أما
fetch()المباشر أو العمليات الفرعية أو الإضافات الأصلية فلا. - لا يقيّد المضيفين العامّين — فذلك يتطلب قائمة سماح للوجهات، وهي ضابط مختلف.
صُمّمت SanDssrf لتخلف DSSRF: فقد حوّلت الدروس المستفادة من الإرشادات الأمنية التي تلقتها DSSRF، ومن التجاوزات التي اكتشفتها ونشرتها بنفسي، إلى احتواءٍ بدل التحقق.