لقد قيل الكثير عن شعبية NodeJS. الزيادة في عدد التطبيقات واضحة - NodeJS من السهل جدًا تعلمها ، ولديها عدد كبير من المكتبات ، بالإضافة إلى نظام بيئي متطور بشكل ديناميكي.لقد أعددنا توصيات لمطوري NodeJS استنادًا إلى أوراق الغش OWASP لمساعدتك في توقع مشكلات الأمان عند تطوير التطبيقات.يمكن تقسيم التوصيات الأمنية لتطبيقات NodeJS إلى الفئات التالية:- الأمان أثناء تطوير التطبيق ؛
- أمن الخادم ؛
- أمن المنصة ؛
أمان تطوير التطبيقات
تجنب جحيم رد الاتصالاستخدام وظائف رد الاتصال (رد الاتصال) هي واحدة من أعظم نقاط القوة في NodeJS ، ولكن عند تداخل عمليات رد الاتصال ، يمكنك بسهولة نسيان معالجة الخطأ في إحدى الوظائف. طريقة واحدة لتجنب الجحيم رد على استخدام الوعود. حتى إذا كانت الوحدة النمطية التي تستخدمها لا تدعم العمل بالوعود ، يمكنك دائمًا استخدام Promise.promisifyAll (). ولكن حتى باستخدام الوعود ، يجدر الانتباه إلى التعشيش. لتجنب خطأ الجحيم بشكل كامل ، التزم بسلسلة من الوعود "المسطحة".مثال على رد الاتصال:function func1(name, callback) {
setTimeout(function() {
}, 500);
}
function func2(name, callback) {
setTimeout(function() {
}, 100);
}
function func3(name, callback) {
setTimeout(function() {
}, 900);
}
function func4(name, callback) {
setTimeout(function() {
}, 3000);
}
func1("input1", function(err, result1){
if(err){
}
else {
func2("input2", function(err, result2){
if(err){
}
else{
func3("input3", function(err, result3){
if(err){
}
else{
func4("input 4", function(err, result4){
if(err){
}
else {
}
});
}
});
}
});
}
});
نفس الرمز باستخدام سلسلة مسطحة وعود:function func1(name, callback) {
setTimeout(function() {
}, 500);
}
function func2(name, callback) {
setTimeout(function() {
}, 100);
}
function func3(name, callback) {
setTimeout(function() {
}, 900);
}
function func4(name, callback) {
setTimeout(function() {
}, 3000);
}
func1("input1")
.then(function (result){
return func2("input2");
})
.then(function (result){
return func3("input3");
})
.then(function (result){
return func4("input4");
})
.catch(function (error) {
});
تحديد حجم الطلب.يمكن أن يكون تحليل نص الطلب عملية كثيفة الاستخدام للموارد. إذا لم تحدد حجم الطلب ، فسيتمكن المهاجمون من إرسال طلبات كبيرة بما يكفي يمكنها ملء كل مساحة القرص أو استنفاد جميع موارد الخادم ، ولكن في الوقت نفسه ، قد يكون تحديد حجم الطلب لجميع الحالات غير صحيح ، لأن هناك طلبات ، مثل تنزيل ملف. لذلك ، يوصى بوضع حدود لأنواع مختلفة من المحتوى. على سبيل المثال ، باستخدام إطار العمل السريع ، يمكن تنفيذ ذلك على النحو التالي:app.use(express.urlencoded({ limit: "1kb" }));
app.use(express.json({ limit: "1kb" }));
app.use(express.multipart({ limit:"10mb" }));
وتجدر الإشارة إلى أنه يمكن للمهاجم تغيير نوع محتوى الطلب والتحايل على القيود ، وبالتالي ، من الضروري التحقق مما إذا كان محتوى الطلب يتطابق مع نوع المحتوى المحدد في رأس الطلب. إذا كان التحقق من نوع المحتوى يؤثر على الأداء ، يمكنك فقط التحقق من أنواع أو استعلامات معينة أكبر من حجم معين.لا تحجب حلقة الأحداثأحد المكونات الهامة للغة هي حلقة الأحداث ، والتي تسمح لك فقط بتغيير سياق التنفيذ دون انتظار اكتمال العملية. ومع ذلك ، هناك عمليات حظر يجب على NodeJS انتظار اكتمالها قبل متابعة الرمز. على سبيل المثال ، معظم الطرق المتزامنة تحظر:const fs = require('fs');
fs.unlinkSync('/file.txt');
يوصى بإجراء مثل هذه العمليات بشكل غير متزامن:const fs = require('fs');
fs.unlink('/file.txt', (err) => {
if (err) throw err;
});
في الوقت نفسه ، لا تنس أن الرمز الذي يقف بعد المكالمة غير المتزامنة سيتم تنفيذه دون انتظار اكتمال العملية السابقة.على سبيل المثال ، في الرمز أدناه ، سيتم حذف الملف قبل قراءته ، مما قد يؤدي إلى حالة سباق.const fs = require('fs');
fs.readFile('/file.txt', (err, data) => {
});
fs.unlinkSync('/file.txt');
لتجنب ذلك ، يمكنك كتابة جميع العمليات في وظيفة غير مانعة للحظر:const fs = require('fs');
fs.readFile('/file.txt', (err, data) => {
fs.unlink('/file.txt', (err) => {
if (err) throw err;
});
});
التحقق منحقول الإدخال يعد التحقق من حقول الإدخال جزءًا مهمًا من أمان أي تطبيق. يمكن أن تتسبب أخطاء التحقق من الصحة في أن يصبح تطبيقك عرضة على الفور للعديد من أنواع الهجمات: إدخال sql و xss وحقن الأوامر وغيرها. لتبسيط التحقق من صحة النموذج ، يمكنك استخدام حزم المدقق ، mongo-express-Sanitize.الهروب من بيانات المستخدممن القواعد التي ستساعدك على حماية نفسك من هجمات xss هي حماية بيانات المستخدم. يمكنك استخدام مكتبة escape-html أو node-esapi لهذا الغرض.حافظ على السجلاتوبالإضافة إلى ذلك، أنها سوف تساعد في أخطاء التصحيح، وقطع الأشجار يمكن أن تستخدم لالاستجابة للحوادث. يمكنك قراءة المزيد حول الحاجة إلى التسجيل هنا.. واحدة من حزم تسجيل NodeJS الأكثر شعبية هي Winston و Bunyan. يوضح المثال أدناه كيفية استخدام Winston لإخراج السجلات إلى كل من وحدة التحكم والملف:var logger = new (Winston.Logger) ({
transports: [
new (winston.transports.Console)(),
new (winston.transports.File)({ filename: 'application.log' })
],
level: 'verbose'
});
التحكم في دورة الأحداثإذا كان الخادم الخاص بك في حالة حركة مرور شبكة مكثفة ، فقد يواجه المستخدمون صعوبات في توفر الخدمة الخاصة بك. هذا هو في الأساس هجوم DoS. في هذه الحالة ، يمكنك تتبع وقت الاستجابة ، وإذا تجاوز الوقت المحدد ، قم بإرسال رسالة إلى 503 Server Too Busy. يمكن أن تساعد وحدة toobusy-js.مثال على استخدام الوحدة:var toobusy = require('toobusy-js');
var express = require('express');
var app = express();
app.use(function(req, res, next) {
if (toobusy()) {
res.send(503, "Server Too Busy");
} else {
next();
}
});
اتخذ الاحتياطات ضد القوة الغاشمة، مرة أخرى ، تأتي الوحدات لإنقاذ. على سبيل المثال ، التعبير عن الغاشمة أو الحارس السريع. مثال للاستخدام:var bouncer = require('express-bouncer');
bouncer.whitelist.push('127.0.0.1');
bouncer.blocked = function (req, res, next, remaining) {
res.send(429, "Too many requests have been made. Please wait " + remaining/1000 + " seconds.");
};
app.post("/login", bouncer.block, function(req, res) {
if (LoginFailed){ }
else {
bouncer.reset( req );
}
});
يعد استخدام اختبار CAPTCHA إجراءً مضادًا شائعًا آخر ضد القوة الغاشمة. الوحدة النمطية المستخدمة بشكل متكرر للمساعدة في تنفيذ اختبار CAPTCHA هي svg-captcha.استخدام الرموز المميزة لـ CSRFإحدى الطرق الأكثر موثوقية للحماية من هجمات CSRF هي استخدام الرمز المميز لـ CSRF. يجب أن يتم إنشاء الرمز المميز مع إنتروبيا عالية ، ويتم فحصه بدقة ويرتبط بجلسة المستخدم. لضمان تشغيل رمز CSRF المميز ، يمكنك استخدام وحدة csurf.مثال للاستخدام:var csrf = require('csurf');
csrfProtection = csrf({ cookie: true });
app.get('/form', csrfProtection, function(req, res) {
res.render('send', { csrfToken: req.csrfToken() })
})
app.post('/process', parseForm, csrfProtection, function(req, res) {
res.send('data is being processed');
});
لا تنس أن تضيف الرمز المميز للحقل المخفي في الصفحة:<input type="hidden" name="_csrf" value="{{ csrfToken }}">
يمكنك قراءة المزيد عن رموز CSRF المميزة في مقالتنا .حذف المسارات غير الضرورية.يجب ألا يحتوي تطبيق الويب على صفحات لا يستخدمها المستخدمون ، لأن ذلك يمكن أن يزيد من سطح الهجوم. لذلك ، يجب تعطيل جميع توجيهات API غير المستخدمة. يجب الانتباه بشكل خاص إلى هذا السؤال إذا كنت تستخدم أطر Sails أو Feathers ، لأنها تنشئ نقاط نهاية API تلقائيًا.حماية نفسك من HPP (تلوث معلمات HTTP)بشكل افتراضي ، يضيف Express كافة المعلمات من الطلب إلى مصفوفة. توصي OWASP باستخدام وحدة hpp ، والتي تتجاهل جميع قيم المعلمات من req.query و / أو req.body وتختار ببساطة القيمة الأخيرة من بين القيم المكررة.var hpp = require('hpp');
app.use(hpp());
راقب القيم التي تم إرجاعها.على سبيل المثال ، يمكن لجدول المستخدم تخزين البيانات المهمة: كلمة المرور وعنوان البريد الإلكتروني وتاريخ الميلاد ، إلخ. لذلك ، من المهم إرجاع البيانات الضرورية فقط.على سبيل المثال: exports.sanitizeUser = function(user) {
return {
id: user.id,
username: user.username,
fullName: user.fullName
};
};
استخدم الواصفاتاستخدم الواصفات لوصف سلوك خاصية في عمليات مختلفة: قابل للكتابة - ما إذا كان من الممكن تغيير قيمة خاصية قابلة للتعداد - ما إذا كان من الممكن استخدام خاصية في حلقة .. للتكوين - ما إذا كان من الممكن استبدال خاصية. من المستحسن الانتباه إلى الخصائص المدرجة ، لأنه عند تحديد خاصية كائن ما ، تكون جميع هذه السمات صحيحة افتراضيًا. يمكنك تغيير قيمة العقارات على النحو التالي:var o = {};
Object.defineProperty(o, "a", {
writable: true,
enumerable: true,
configurable: true,
value: "A"
});
استخدام قوائم التحكم في الوصول (ACL)يمكن أن يساعد Acl في التمييز بين الوصول إلى البيانات بناءً على الأدوار. على سبيل المثال ، تبدو إضافة الإذن كما يلي:
acl.allow('guest', 'blogs', 'view')
acl.allow('member', 'blogs', ['edit', 'view', 'delete'])
Catch uncaughtExceptionبشكل افتراضي ، في حالة وجود استثناء غير مُدرج ، سيقوم NodeJS بإلقاء تتبع المكدس الحالي وإنهاء سلسلة التنفيذ. ومع ذلك ، يسمح لك NodeJS بتخصيص هذا السلوك. في حالة وجود استثناء غير مُدرج ، يتم رفع حدث غير مُسجَّل ، والذي يمكن اكتشافه باستخدام كائن العملية:process.on("uncaughtException", function(err) {
process.exit();
});
من الجدير بالذكر أنه عند حدوث استثناء غير معروف ، من الضروري مسح جميع الموارد المخصصة (على سبيل المثال ، واصفات الملفات ومعالجاتها) قبل إكمال العملية Z لتجنب الأخطاء غير المتوقعة. من المحبط جدًا أن يستمر البرنامج في العمل في حالة حدوث استثناء لم يتم اكتشافه.أيضًا ، عند عرض رسائل الخطأ ، يجب على المستخدم عدم الكشف عن معلومات الخطأ التفصيلية ، مثل تتبع المكدس.أمان الخادم
تعيين علامات للرؤوس عند العمل مع ملفات تعريف الارتباط.هناك العديد من العلامات التي يمكن أن تساعد في الحماية من الهجمات مثل xss و csrf: httpOnly ، مما يمنع الوصول إلى ملفات تعريف الارتباط من خلال جافا سكريبت ؛ آمن - يسمح بإرسال ملفات تعريف الارتباط فقط عبر HTTPS و SameSite ، مما يحدد القدرة على نقل ملفات تعريف الارتباط إلى مورد تابع لجهة خارجية.مثال للاستخدام:var session = require('express-session');
app.use(session({
secret: 'your-secret-key',
key: 'cookieName',
cookie: { secure: true, httpOnly: true, path: '/user', sameSite: true}
}));
تعيين رؤوس HTTP للأمانفيما يلي رؤوس وأمثلة لكيفية توصيلها لمساعدتك على حماية نفسك من عدد من الهجمات الشائعة. يتم تعيين الرؤوس باستخدام وحدة الخوذة• Strict-Transport-Security: HTTP Strict Transport Security (HSTS) يخبر المتصفح أنه لا يمكن الوصول إلى التطبيق إلا عبر HTTPSapp.use(helmet.hsts());
app.use(helmet.hsts("<max-age>", "<includeSubdomains>"));
• X-Frame-Options: تحدد ما إذا كان يمكن استخدام الصفحة في الإطار أو iframe أو التضمين أو الكائنapp.use(hemlet.xframe());
helmet.xframe('sameorigin');
helmet.xframe('allow-from', 'http://alloweduri.com');
• X-XSS-Protection: يسمح للمتصفح بالتوقف عن تحميل الصفحة إذا اكتشف هجوم XSS منعكسًا.var xssFilter = require('x-xss-protection');
app.use(xssFilter());
• خيارات X-Content-Type-Options: تُستخدم لمنع الهجمات باستخدام أنواع MIMEapp.use(helmet.noSniff());
• سياسة أمن المحتوى: يمنع الهجمات مثل هجمات XSS وحقن البياناتconst csp = require('helmet-csp')
app.use(csp({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
frameAncestors: ["'none'"],
imgSrc: ["'self'", "'http://imgexample.com'"],
styleSrc: ["'none'"]
}
}))
• التحكم في التخزين المؤقت وبراغما: لإدارة التخزين المؤقت ، وخاصة هذا العنوان يمكن أن يكون مفيدًا للصفحات التي تحتوي على بيانات حساسة. ومع ذلك ، تذكر أن تعطيل التخزين المؤقت في جميع الصفحات يمكن أن يؤثر على الأداء.app.use(helmet.noCache());
• خيارات تنزيل X: يمنع الرأس Inter Explorer من تنفيذ الملفات التي تم تنزيلهاapp.use(helmet.ieNoOpen());
• Expect-CT: شهادة الشفافية - آلية تم إنشاؤها لحل بعض المشاكل في البنية التحتية لشهادة SSL ، يخبر هذا الرأس المتصفح عن الحاجة إلى التحقق الإضافي من الشهادة في سجلات CTvar expectCt = require('expect-ct');
app.use(expectCt({ maxAge: 123 }));
app.use(expectCt({ enforce: true, maxAge: 123 }));
app.use(expectCt({ enforce: true, maxAge: 123, reportUri: 'http://example.com'}));
• X-Powered-By: رأس اختياري يُستخدم للإشارة إلى التقنية المستخدمة على الخادم. يمكنك إخفاء هذا العنوان على النحو التالي:app.use(helmet.hidePoweredBy());
بالإضافة إلى ذلك ، يمكنك تغيير القيمة لإخفاء معلومات حقيقية حول التقنيات التي تستخدمها:app.use(helmet.hidePoweredBy({ setTo: 'PHP 4.2.0' }));
أمن النظام الأساسي
تحديث الحزمالخاصة بك يعتمد أمان التطبيق الخاص بك على أمان الحزم التي تستخدمها ، لذلك من المهم استخدام أحدث إصدار من الحزمة. للتأكد من أن الحزمة التي تستخدمها لا تحتوي على ثغرات أمنية معروفة ، يمكنك استخدام قائمة OWASP الخاصة . يمكنك أيضًا استخدام المكتبة التي تتحقق من حزم الثغرات المعروفة Retire.js.لا تستخدم وظائف غير آمنة.هناك وظائف من المستحسن التخلص منها كلما أمكن ذلك. من بين هذه الوظائف ، يتم تقييم () ، الذي ينفذ سلسلة مأخوذة كوسيطة. بالاقتران مع إدخال المستخدم ، يمكن أن يؤدي استخدام هذه الوظيفة إلى نقاط ضعف في تنفيذ التعليمات البرمجية عن بُعد ، نظرًا لأنه لأسباب مماثلة ، فإن استخدام child_process.exec غير آمن أيضًا ، لأن الوظيفة تمرر الوسيطات المستلمة إلى bin / sh.بالإضافة إلى ذلك ، هناك عدد من الوحدات التي يجب استخدامها بحذر. على سبيل المثال ، الوحدة النمطية fs للعمل مع الملفات. إذا تم تمرير إدخال المستخدم الذي تم إنشاؤه بطريقة ما بطريقة معينة ، فقد يصبح تطبيقك عرضة لتضمين ملف محلي وانتقال الدليل.يجب استخدام وحدة vm ، التي توفر واجهة برمجة تطبيقات لتجميع التعليمات البرمجية وتشغيلها على جهاز ظاهري V8 ، فقط في وضع الحماية. يمكنكهنا التعرف على الوظائف الأخرى التي قد تجعل تطبيقك غير آمن.كن حذرًا عند استخدام التعبيرات العادية.يمكن كتابة تعبير عادي حتى تتمكن من تحقيق موقف ينمو فيه التعبير بشكل كبير ، مما قد يؤدي إلى رفض الخدمة. تسمى هذه الهجمات ReDoS. هناك العديد من الأدوات للتحقق مما إذا كانت التعبيرات العادية آمنة ، وأحدها هو vuln-regex-detector.قم بتشغيل اللنت بشكل دوريأثناء التطوير ، من الصعب مراعاة جميع توصيات السلامة ، وعندما يتعلق الأمر بتطوير الفريق ، ليس من السهل تحقيق الامتثال لجميع القواعد من قبل جميع أعضاء الفريق. لهذه الأغراض ، هناك أدوات لتحليل الأمان الثابت. مثل هذه الأدوات ، دون تنفيذ التعليمات البرمجية الخاصة بك ، تبحث عن نقاط الضعف فيها. بالإضافة إلى ذلك ، تسمح لك الوبر بإضافة قواعد مخصصة للعثور على الأماكن في التعليمات البرمجية التي قد تكون عرضة للخطر. الأكثر استخدامًا من الوبر هي ESLint و JSHint.استخدموضعًا صارمًا ، يحتوي Javascript على عدد من الوظائف غير الآمنة والقديمة التي لا يجب استخدامها. لاستبعاد إمكانية استخدام هذه الوظائف ، يتم أيضًا توفير وضع صارم.الالتزام بمبادئ السلامة العامةتركز التوصيات الموضحة على NodeJS ، لكن لا تنس مبادئ الأمان العامة التي يجب مراعاتها بغض النظر عن النظام الأساسي المستخدم.