أفضل ممارسات الأمان
الهدف
يهدف هذا المستند إلى توسيع نطاق نموذج التهديد الحالي وتقديم إرشادات شاملة حول كيفية تأمين تطبيق Node.js.
محتويات الوثيقة
- أفضل الممارسات: طريقة مبسطة وموجزة لرؤية أفضل الممارسات. يمكننا استخدام هذه المشكلة أو هذا الدليل كنقطة انطلاق. من المهم ملاحظة أن هذه الوثيقة خاصة بـ Node.js، إذا كنت تبحث عن شيء واسع النطاق، ففكر في أفضل ممارسات OSSF.
- شرح الهجمات: توضيح وتوثيق الهجمات التي نذكرها في نموذج التهديد بلغة بسيطة مع بعض أمثلة الشفرة (إن أمكن).
- مكتبات الجهات الخارجية: تحديد التهديدات (هجمات انتحال الهوية المطبعية، الحزم الضارة...) وأفضل الممارسات فيما يتعلق باعتماد وحدات Node، إلخ...
قائمة التهديدات
إنكار خدمة خادم HTTP (CWE-400)
هذا هجوم يصبح فيه التطبيق غير متوفر للغرض الذي صمم من أجله بسبب طريقة معالجة طلبات HTTP الواردة. لا يلزم أن تكون هذه الطلبات مصممة عمداً من قبل جهة خبيثة: يمكن للعميل الخاطئ التكوين أو المعيب أيضًا إرسال نمط من الطلبات إلى الخادم ينتج عنه إنكار خدمة.
يتم استلام طلبات HTTP بواسطة خادم Node.js HTTP ويتم تسليمها إلى شفرة التطبيق عبر مُعالِج الطلبات المسجل. لا يقوم الخادم بتحليل محتوى جسم الطلب. لذلك، فإن أي إنكار للخدمة ناتج عن محتويات الجسم بعد تسليمها إلى مُعالِج الطلبات ليس ثغرة أمنية في Node.js نفسها، لأن من مسؤولية شفرة التطبيق التعامل معها بشكل صحيح.
تأكد من أن خادم الويب يعالج أخطاء المقبس بشكل صحيح، على سبيل المثال، عند إنشاء خادم بدون مُعالِج للأخطاء، سيكون عرضة لإنكار الخدمة.
import net from 'node:net'
const server = net.createServer(socket => {
// socket.on('error', console.error) // هذا يمنع الخادم من التعطل
socket.write('Echo server\r\n')
socket.pipe(socket)
})
server.listen(5000, '0.0.0.0')
إذا تم إجراء طلب خاطئ، فقد يتعطل الخادم.
مثال على هجوم إنكار الخدمة الذي لا يسببه محتوى الطلب هو Slowloris. في هذا الهجوم، يتم إرسال طلبات HTTP ببطء ومجزأة، جزءًا واحدًا في كل مرة. حتى يتم تسليم الطلب الكامل، سيحافظ الخادم على الموارد المخصصة للطلب الجاري. إذا تم إرسال عدد كافٍ من هذه الطلبات في نفس الوقت، فستصل سرعان ما كمية الاتصالات المتزامنة إلى أقصاها مما يؤدي إلى إنكار الخدمة. هذه هي الطريقة التي يعتمد بها الهجوم ليس على محتويات الطلب ولكن على توقيت ونمط الطلبات التي يتم إرسالها إلى الخادم.
تخفيضات
- استخدم وكيلًا عكسيًا لاستقبال طلبات التوجيه إلى تطبيق Node.js. يمكن أن توفر وكلاء العكس التخزين المؤقت، وتوازن التحميل، وقائمة سوداء IP، إلخ، مما يقلل من احتمالية نجاح هجوم DoS.
- قم بتكوين أوقات انتظار الخادم بشكل صحيح، بحيث يمكن إسقاط الاتصالات التي تكون خاملة أو حيث تصل الطلبات ببطء شديد. راجع أوقات الانتظار المختلفة في
http.Server
، خاصةًheadersTimeout
، وrequestTimeout
، وtimeout
، وkeepAliveTimeout
. - قم بتحديد عدد مقابس مفتوحة لكل مضيف وفي المجموع. راجع مستندات http، خاصةً
agent.maxSockets
، وagent.maxTotalSockets
، وagent.maxFreeSockets
، وserver.maxRequestsPerSocket
.
إعادة ربط DNS (CWE-346)
هذا هجوم يمكن أن يستهدف تطبيقات Node.js التي يتم تشغيلها مع تمكين مفتش التصحيح باستخدام مفتاح --inspect.
بما أن مواقع الويب المفتوحة في متصفح ويب يمكنها إجراء طلبات WebSocket و HTTP، فيمكنها استهداف مفتش التصحيح الذي يعمل محليًا. يتم عادةً منع هذا بواسطة نهج المصدر نفسه الذي يتم تنفيذه بواسطة متصفحات حديثة، والذي يحظر البرامج النصية من الوصول إلى الموارد من مصادر مختلفة (بمعنى أن موقع ويب ضار لا يمكنه قراءة البيانات المطلوبة من عنوان IP محلي).
ومع ذلك، من خلال إعادة ربط DNS، يمكن للمهاجم التحكم مؤقتًا في أصل طلباته بحيث تبدو وكأنها مصدرها عنوان IP محلي. يتم ذلك من خلال التحكم في كل من موقع الويب وخادم DNS المستخدم لحل عنوان IP الخاص به. راجع ويكي إعادة ربط DNS لمزيد من التفاصيل.
تخفيضات
- تعطيل المفتش على إشارة SIGUSR1 عن طريق إرفاق مستمع
process.on(‘SIGUSR1’, …)
به. - لا تقم بتشغيل بروتوكول المفتش في الإنتاج.
كشف معلومات حساسة لطرف غير مصرح له (CWE-552)
يتم دفع جميع الملفات والمجلدات المضمنة في الدليل الحالي إلى سجل npm أثناء نشر الحزمة.
هناك بعض الآليات للتحكم في هذا السلوك عن طريق تحديد قائمة حظر باستخدام .npmignore
و.gitignore
أو عن طريق تحديد قائمة سماح في package.json
إجراءات التخفيف
- استخدام
npm publish --dry-run
لسرد جميع الملفات المراد نشرها. تأكد من مراجعة المحتوى قبل نشر الحزمة. - من المهم أيضًا إنشاء ملفات تجاهل وصيانتها مثل
.gitignore
و.npmignore
. من خلال هذه الملفات، يمكنك تحديد الملفات/المجلدات التي يجب عدم نشرها. تسمح خاصية الملفات فيpackage.json
بعملية عكسية وهي قائمة-- المسموح بها
. - في حالة حدوث تسريب، تأكد من إلغاء نشر الحزمة.
تهريب طلبات HTTP (CWE-444)
هذا هجوم ينطوي على خادمين HTTP (عادةً وكيل وتطبيق Node.js). يرسل العميل طلب HTTP يمر أولاً عبر الخادم الأمامي (الوكيل)، ثم يتم إعادة توجيهه إلى الخادم الخلفي (التطبيق). عندما يفسر الخادم الأمامي والخلفي طلبات HTTP الغامضة بشكل مختلف، توجد إمكانية لمهاجم لإرسال رسالة ضارة لن تراها الواجهة الأمامية ولكن سيرىها الخادم الخلفي، مما يُدخلها فعليًا عبر خادم الوكيل.
راجع CWE-444 للحصول على وصف وأمثلة أكثر تفصيلاً.
بما أن هذا الهجوم يعتمد على تفسير Node.js لطلبات HTTP بشكل مختلف عن خادم HTTP (تعسفي)، فقد يكون الهجوم الناجح ناتجًا عن ثغرة أمنية في Node.js أو الخادم الأمامي أو كليهما. إذا كانت طريقة تفسير Node.js للطلب متسقة مع مواصفات HTTP (راجع RFC7230)، فلا يُعتبر ذلك ثغرة أمنية في Node.js.
إجراءات التخفيف
- لا تستخدم خيار
insecureHTTPParser
عند إنشاء خادم HTTP. - قم بتكوين الخادم الأمامي لتوحيد الطلبات الغامضة.
- راقب باستمرار ثغرات تهريب طلبات HTTP الجديدة في كل من Node.js والخادم الأمامي المختار.
- استخدم HTTP/2 من النهاية إلى النهاية وقم بتعطيل ترقية HTTP إلى أسفل إن أمكن.
الكشف عن المعلومات من خلال هجمات التوقيت (CWE-208)
هذا هجوم يسمح للمهاجم بتعلم معلومات حساسة محتملة، على سبيل المثال، بقياس الوقت الذي يستغرقه التطبيق للاستجابة لطلب. هذا الهجوم ليس خاصًا بـ Node.js ويمكنه استهداف جميع بيئات التشغيل تقريبًا.
الهجوم ممكن كلما استخدم التطبيق سرًا في عملية حساسة للوقت (مثل، الفرع). ضع في اعتبارك معالجة المصادقة في تطبيق نموذجي. هنا، تتضمن طريقة المصادقة الأساسية البريد الإلكتروني وكلمة المرور كبيانات اعتماد. يتم استرداد معلومات المستخدم من الإدخال الذي قدمه المستخدم من قاعدة بيانات DBMS بشكل مثالي. عند استرداد معلومات المستخدم، تتم مقارنة كلمة المرور بمعلومات المستخدم المستردة من قاعدة البيانات. يستغرق استخدام مقارنة السلاسل المضمنة وقتًا أطول للقيم ذات الطول نفسه. هذه المقارنة، عند تشغيلها لكمية مقبولة، تزيد بشكل غير مرغوب فيه من وقت استجابة الطلب. من خلال مقارنة أوقات استجابة الطلب، يمكن للمهاجم تخمين طول وقيمة كلمة المرور في كمية كبيرة من الطلبات.
تخفيف حدة المشكلة
- يكشف واجهة برمجة تطبيقات التشفير عن دالة
timingSafeEqual
لمقارنة القيم الحساسة الفعلية والمتوقعة باستخدام خوارزمية وقت ثابت. - لمقارنة كلمات المرور، يمكنك استخدام scrypt المتوفرة أيضًا على وحدة التشفير الأصلية.
- بشكل عام، تجنب استخدام الأسرار في العمليات ذات الوقت المتغير. يشمل ذلك التفرع على الأسرار، وعندما يمكن أن يكون المهاجم موجودًا في نفس البنية التحتية (مثل، نفس جهاز الحوسبة السحابية)، باستخدام سر كمؤشر إلى الذاكرة. كتابة رمز وقت ثابت في JavaScript أمر صعب (جزئيًا بسبب JIT). لتطبيقات التشفير، استخدم واجهات برمجة التطبيقات المشفرة المضمنة أو WebAssembly (للخوارزميات غير المُنفذة بشكل أصلي).
الوحدات الطرفية الثالثة الضارة (CWE-1357)
حاليًا، في Node.js، يمكن لأي حزمة الوصول إلى موارد قوية مثل الوصول إلى الشبكة. علاوة على ذلك، نظرًا لأنها لديها أيضًا إمكانية الوصول إلى نظام الملفات، فيمكنها إرسال أي بيانات إلى أي مكان.
جميع الأكواد التي تعمل في عملية node لديها القدرة على تحميل وتشغيل أكواد تعسفية إضافية باستخدام eval()
(أو ما يعادلها). يمكن لجميع الأكواد التي لديها حق الوصول إلى الكتابة في نظام الملفات تحقيق الشيء نفسه من خلال الكتابة في ملفات جديدة أو موجودة يتم تحميلها.
يحتوي Node.js على آلية سياسة تجريبية¹ للإعلان عن المورد المُحمل على أنه غير موثوق به أو موثوق به. ومع ذلك، لم يتم تمكين هذه السياسة افتراضيًا. تأكد من تثبيت إصدارات التبعية وتشغيل فحوصات أوتوماتيكية للثغرات باستخدام سير عمل شائع أو نصوص npm. قبل تثبيت حزمة، تأكد من صيانة هذه الحزمة وتضمين جميع المحتويات التي تتوقعها. كن حذرًا، فإن رمز المصدر على GitHub ليس هو نفسه دائمًا الذي تم نشره، قم بالتحقق منه في node_modules
.
هجمات سلسلة التوريد
تحدث هجمة سلسلة التوريد على تطبيق Node.js عندما يتم اختراق أحد تبعياته (إما مباشرة أو انتقالية). يمكن أن يحدث هذا إما بسبب كون التطبيق متساهلاً للغاية في تحديد التبعيات (السماح بالتحديثات غير المرغوب فيها) و/أو الأخطاء المطبعية الشائعة في المواصفات (ضعيفة أمام الاختراق المطبعي).
يمكن للمهاجم الذي يتحكم في حزمة المنبع نشر إصدار جديد به رمز ضار. إذا كان تطبيق Node.js يعتمد على هذه الحزمة دون أن يكون صارماً بشأن الإصدار الآمن للاستخدام، فيمكن تحديث الحزمة تلقائيًا إلى أحدث إصدار ضار، مما يعرض التطبيق للخطر.
يمكن أن تحتوي التبعيات المحددة في ملف package.json
على رقم إصدار دقيق أو نطاق. ومع ذلك، عند تثبيت التبعية على إصدار دقيق، لا يتم تثبيت تبعياتها الانتقالية نفسها. هذا لا يزال يجعل التطبيق عرضة للتحديثات غير المرغوب فيها/غير المتوقعة.
متجهات الهجوم المحتملة:
- هجمات الاختراق المطبعي
- تسميم ملف القفل
- القائمين على الصيانة المخترقين
- الحزم الضارة
- التباس التبعيات
إجراءات التخفيف
- منع npm من تنفيذ البرامج النصية التعسفية باستخدام
--ignore-scripts
- بالإضافة إلى ذلك، يمكنك تعطيله عالميًا باستخدام
npm config set ignore-scripts true
- بالإضافة إلى ذلك، يمكنك تعطيله عالميًا باستخدام
- تثبيت إصدارات التبعية على إصدار ثابت محدد، وليس نطاق إصدار أو مصدر قابل للتغيير.
- استخدام ملفات القفل، التي تثبت كل التبعيات (المباشرة والانتقالية).
- استخدم إجراءات التخفيف لتسميم ملف القفل.
- أتمتة فحوصات الثغرات الأمنية الجديدة باستخدام CI، باستخدام أدوات مثل npm-audit.
- يمكن استخدام أدوات مثل
Socket
لتحليل الحزم باستخدام التحليل الثابت للعثور على السلوكيات الخطرة مثل الوصول إلى الشبكة أو نظام الملفات.
- يمكن استخدام أدوات مثل
- استخدم
npm ci
بدلاً منnpm install
. هذا يفرض ملف القفل بحيث تتسبب أية عدم اتساقات بينه وبين ملفpackage.json
في حدوث خطأ (بدلاً من تجاهل ملف القفل بصمت لصالحpackage.json
). - تحقق بعناية من ملف
package.json
بحثًا عن أخطاء/أخطاء مطبعية في أسماء التبعيات.
انتهاك وصول الذاكرة (CWE-284)
تعتمد الهجمات القائمة على الذاكرة أو الكومة على مزيج من أخطاء إدارة الذاكرة و مُخصّص ذاكرة قابل للاستغلال. مثل جميع أوقات التشغيل، Node.js معرضة لهذه الهجمات إذا كانت مشاريعك تعمل على جهاز مشترك. إن استخدام كومة آمنة مفيد لمنع تسريب المعلومات الحساسة بسبب تجاوزات المؤشرات ونقصها.
لسوء الحظ، الكومة الآمنة غير متوفرة على Windows. يمكن العثور على مزيد من المعلومات في مستندات Node.js حول الكومة الآمنة.
إجراءات التخفيف
- استخدم
--secure-heap=n
اعتمادًا على تطبيقك حيث n هو الحد الأقصى لحجم البايت المخصص. - لا تقم بتشغيل تطبيق الإنتاج الخاص بك على جهاز مشترك.
التصحيح باستخدام قرد (CWE-349)
يشير التصحيح باستخدام قرد إلى تعديل الخصائص في وقت التشغيل بهدف تغيير السلوك الحالي. مثال:
// eslint-disable-next-line no-extend-native
Array.prototype.push = function (item) {
// overriding the global [].push
}
إجراءات التخفيف
تُفعّل علامة --frozen-intrinsics
خصائص جوهريّة مجمدة تجريبيّة¹، مما يعني أن جميع كائنات ووظائف JavaScript المدمجة مجمدة بشكل متكرر. لذلك، لن يُلغي المقطع التالي السلوك الافتراضي لـ Array.prototype.push
// eslint-disable-next-line no-extend-native
Array.prototype.push = function (item) {
// overriding the global [].push
}
// Uncaught:
// TypeError <Object <Object <[Object: null prototype] {}>>>:
// Cannot assign to read only property 'push' of object '
ومع ذلك، من المهم أن تذكر أنه لا يزال بإمكانك تعريف متغيرات عالمية جديدة واستبدال المتغيرات العالمية الحالية باستخدام globalThis
globalThis.foo = 3; foo; // you can still define new globals 3
globalThis.Array = 4; Array; // However, you can also replace existing globals 4
لذلك، يمكن استخدام Object.freeze(globalThis)
لضمان عدم استبدال أي متغيرات عالمية.
هجمات تلويث النموذج الأولي (CWE-1321)
يشير تلويث النموذج الأولي إلى إمكانية تعديل أو حقن خصائص في عناصر لغة Javascript عن طريق إساءة استخدام استخدام _proto، _constructor، prototype، وخصائص أخرى موروثة من النماذج الأولية المدمجة.
const a = { a: 1, b: 2 }
const data = JSON.parse('{"__proto__": { "polluted": true}}')
const c = Object.assign({}, a, data)
console.log(c.polluted) // true
// Potential DoS
const data2 = JSON.parse('{"__proto__": null}')
const d = Object.assign(a, data2)
d.hasOwnProperty('b') // Uncaught TypeError: d.hasOwnProperty is not a function
هذا ثغرة أمنية محتملة موروثة من لغة JavaScript.
أمثلة
- CVE-2022-21824 (Node.js)
- CVE-2018-3721 (مكتبة تابعة لجهات خارجية: Lodash)
حلول
- تجنب عمليات دمج متكررة غير آمنة، انظر CVE-2018-16487.
- تطبيق عمليات التحقق من صحة مخطط JSON للطلبات الخارجية/غير الموثوقة.
- إنشاء كائنات بدون نموذج أولي باستخدام
Object.create(null)
. - تجميد النموذج الأولي:
Object.freeze(MyObject.prototype)
. - تعطيل خاصية
Object.prototype.__proto__
باستخدام علم--disable-proto
. - التحقق من وجود الخاصية مباشرةً على الكائن، وليس من النموذج الأولي باستخدام
Object.hasOwn(obj, keyFromObj)
. - تجنب استخدام الطرق من
Object.prototype
.
عنصر مسار بحث غير مُتحكم به (CWE-427)
يقوم Node.js بتحميل الوحدات باتباع خوارزمية حل الوحدات. لذلك، فإنه يفترض أن الدليل الذي يتم فيه طلب وحدة (require) موثوق.
بذلك، يعني أن سلوك التطبيق التالي متوقع. بافتراض بنية الدليل التالية:
- app/
- server.js
- auth.js
- auth
إذا كان server.js يستخدم require('./auth')
فسوف يتبع خوارزمية حل الوحدات ويحمل auth بدلاً من auth.js
.
حلول
يمكن استخدام آلية السياسة مع التحقق من النزاهة التجريبية¹ لتجنب التهديد أعلاه. بالنسبة للدليل الموصوف أعلاه، يمكن استخدام policy.json
التالي
{
"resources": {
"./app/auth.js": {
"integrity": "sha256-iuGZ6SFVFpMuHUcJciQTIKpIyaQVigMZlvg9Lx66HV8="
},
"./app/server.js": {
"dependencies": {
"./auth": "./app/auth.js"
},
"integrity": "sha256-NPtLCQ0ntPPWgfVEgX46ryTNpdvTWdQPoZO3kHo0bKI="
}
}
}
لذلك، عند طلب وحدة auth، سيقوم النظام بالتحقق من النزاهة ويرمي خطأ إذا لم تتطابق مع المتوقع.
» node --experimental-policy=policy.json app/server.js
node:internal/policy/sri:65
throw new ERR_SRI_PARSE(str, str[prevIndex], prevIndex);
^
SyntaxError [ERR_SRI_PARSE]: Subresource Integrity string "sha256-iuGZ6SFVFpMuHUcJciQTIKpIyaQVigMZlvg9Lx66HV8=%" had an unexpected "%" at position 51
at new NodeError (node:internal/errors:393:5)
at Object.parse (node:internal/policy/sri:65:13)
at processEntry (node:internal/policy/manifest:581:38)
at Manifest.assertIntegrity (node:internal/policy/manifest:588:32)
at Module._compile (node:internal/modules/cjs/loader:1119:21)
at Module._extensions..js (node:internal/modules/cjs/loader:1213:10)
at Module.load (node:internal/modules/cjs/loader:1037:32)
at Module._load (node:internal/modules/cjs/loader:878:12)
at Module.require (node:internal/modules/cjs/loader:1061:19)
at require (node:internal/modules/cjs/helpers:99:18) {
code: 'ERR_SRI_PARSE'
}
ملاحظة، يُنصح دائمًا باستخدام --policy-integrity
لتجنب طفرات السياسة.
الميزات التجريبية في الإنتاج
لا يُنصح باستخدام الميزات التجريبية في الإنتاج. قد تعاني الميزات التجريبية من تغييرات جوهرية إذا لزم الأمر، ولا تكون وظائفها مستقرة بشكل آمن. ومع ذلك، نُقدر التعليقات المُقدمة للغاية.
أدوات OpenSSF
تقود OpenSSF العديد من المبادرات التي قد تكون مفيدة جدًا، خاصة إذا كنت تخطط لنشر حزمة npm. تتضمن هذه المبادرات ما يلي:
- بطاقة OpenSSF تقوم بطاقة التقييم بتقييم مشاريع المصدر المفتوح باستخدام سلسلة من عمليات فحص مخاطر الأمان الآلية. يمكنك استخدامها لتقييم نقاط الضعف والاعتماديات في قاعدة بياناتك البرمجية بشكل استباقي واتخاذ قرارات مدروسة بشأن قبول نقاط الضعف.
- برنامج شارة أفضل الممارسات OpenSSF يمكن للمشاريع أن تُصدق على نفسها طواعية من خلال وصف كيفية امتثالها لكل ممارسة أفضل. سيؤدي هذا إلى إنشاء شارة يمكن إضافتها إلى المشروع.