أوراق الغش الأمني: Nodejs



لقد قيل الكثير عن شعبية NodeJS. الزيادة في عدد التطبيقات واضحة - NodeJS من السهل جدًا تعلمها ، ولديها عدد كبير من المكتبات ، بالإضافة إلى نظام بيئي متطور بشكل ديناميكي.

لقد أعددنا توصيات لمطوري NodeJS استنادًا إلى أوراق الغش OWASP لمساعدتك في توقع مشكلات الأمان عند تطوير التطبيقات.

يمكن تقسيم التوصيات الأمنية لتطبيقات NodeJS إلى الفئات التالية:

  • الأمان أثناء تطوير التطبيق ؛
  • أمن الخادم ؛
  • أمن المنصة ؛


أمان تطوير التطبيقات


تجنب جحيم رد الاتصال

استخدام وظائف رد الاتصال (رد الاتصال) هي واحدة من أعظم نقاط القوة في NodeJS ، ولكن عند تداخل عمليات رد الاتصال ، يمكنك بسهولة نسيان معالجة الخطأ في إحدى الوظائف. طريقة واحدة لتجنب الجحيم رد على استخدام الوعود. حتى إذا كانت الوحدة النمطية التي تستخدمها لا تدعم العمل بالوعود ، يمكنك دائمًا استخدام Promise.promisifyAll (). ولكن حتى باستخدام الوعود ، يجدر الانتباه إلى التعشيش. لتجنب خطأ الجحيم بشكل كامل ، التزم بسلسلة من الوعود "المسطحة".

مثال على رد الاتصال:

function func1(name, callback) {
   setTimeout(function() {
      // operations
   }, 500);
}
function func2(name, callback) {
   setTimeout(function() {
      // operations
   }, 100);
}
function func3(name, callback) {
   setTimeout(function() {
      // operations
   }, 900);
}
function func4(name, callback) {
   setTimeout(function() {
      // operations
   }, 3000);
}

func1("input1", function(err, result1){
   if(err){
      // error operations
   }
   else {
      //some operations
      func2("input2", function(err, result2){
         if(err){
            //error operations
         }
         else{
            //some operations
            func3("input3", function(err, result3){
               if(err){
                  //error operations
               }
               else{
                  // some operations
                  func4("input 4", function(err, result4){
                     if(err){
                        // error operations
                     }
                     else {
                        // some operations
                     }
                  });
               }
            });
         }
      });
   }
});

نفس الرمز باستخدام سلسلة مسطحة وعود:

function func1(name, callback) {
   setTimeout(function() {
      // operations
   }, 500);
}
function func2(name, callback) {
   setTimeout(function() {
      // operations
   }, 100);
}
function func3(name, callback) {
   setTimeout(function() {
      // operations
   }, 900);
}
function func4(name, callback) {
   setTimeout(function() {
      // operations
   }, 3000);
}

func1("input1")
   .then(function (result){
      return func2("input2");
   })
   .then(function (result){
      return func3("input3");
   })
   .then(function (result){
      return func4("input4");
   })
   .catch(function (error) {
      // error operations
   });

تحديد حجم الطلب.

يمكن أن يكون تحليل نص الطلب عملية كثيفة الاستخدام للموارد. إذا لم تحدد حجم الطلب ، فسيتمكن المهاجمون من إرسال طلبات كبيرة بما يكفي يمكنها ملء كل مساحة القرص أو استنفاد جميع موارد الخادم ، ولكن في الوقت نفسه ، قد يكون تحديد حجم الطلب لجميع الحالات غير صحيح ، لأن هناك طلبات ، مثل تنزيل ملف. لذلك ، يوصى بوضع حدود لأنواع مختلفة من المحتوى. على سبيل المثال ، باستخدام إطار العمل السريع ، يمكن تنفيذ ذلك على النحو التالي:

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) => {
  // perform actions on file content
});
fs.unlinkSync('/file.txt');

لتجنب ذلك ، يمكنك كتابة جميع العمليات في وظيفة غير مانعة للحظر:

const fs = require('fs');
fs.readFile('/file.txt', (err, data) => {
  // perform actions on file content
  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()) {
        // log if you see necessary
        res.send(503, "Server Too Busy");
    } else {
    next();
    }
});

اتخذ الاحتياطات ضد القوة الغاشمة

، مرة أخرى ، تأتي الوحدات لإنقاذ. على سبيل المثال ، التعبير عن الغاشمة أو الحارس السريع. مثال للاستخدام:

var bouncer = require('express-bouncer');
bouncer.whitelist.push('127.0.0.1'); // whitelist an IP address
// give a custom error message
bouncer.blocked = function (req, res, next, remaining) {
    res.send(429, "Too many requests have been made. Please wait " + remaining/1000 + " seconds.");
};
// route to protect
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 في التمييز بين الوصول إلى البيانات بناءً على الأدوار. على سبيل المثال ، تبدو إضافة الإذن كما يلي:

// guest is allowed to view blogs
acl.allow('guest', 'blogs', 'view')
// allow function accepts arrays as any parameter
acl.allow('member', 'blogs', ['edit', 'view', 'delete'])

Catch uncaughtException

بشكل افتراضي ، في حالة وجود استثناء غير مُدرج ، سيقوم NodeJS بإلقاء تتبع المكدس الحالي وإنهاء سلسلة التنفيذ. ومع ذلك ، يسمح لك NodeJS بتخصيص هذا السلوك. في حالة وجود استثناء غير مُدرج ، يتم رفع حدث غير مُسجَّل ، والذي يمكن اكتشافه باستخدام كائن العملية:

process.on("uncaughtException", function(err) {
    // clean up allocated resources
    // log necessary error details to log files
    process.exit(); // exit the process to avoid unknown state
});

من الجدير بالذكر أنه عند حدوث استثناء غير معروف ، من الضروري مسح جميع الموارد المخصصة (على سبيل المثال ، واصفات الملفات ومعالجاتها) قبل إكمال العملية 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) يخبر المتصفح أنه لا يمكن الوصول إلى التطبيق إلا عبر HTTPS

app.use(helmet.hsts()); // default configuration
app.use(helmet.hsts("<max-age>", "<includeSubdomains>")); // custom configuration

• X-Frame-Options: تحدد ما إذا كان يمكن استخدام الصفحة في الإطار أو iframe أو التضمين أو الكائن

app.use(hemlet.xframe()); // default behavior (DENY)
helmet.xframe('sameorigin'); // SAMEORIGIN
helmet.xframe('allow-from', 'http://alloweduri.com'); //ALLOW-FROM uri

• X-XSS-Protection: يسمح للمتصفح بالتوقف عن تحميل الصفحة إذا اكتشف هجوم XSS منعكسًا.

var xssFilter = require('x-xss-protection');
app.use(xssFilter());

• خيارات X-Content-Type-Options: تُستخدم لمنع الهجمات باستخدام أنواع MIME

app.use(helmet.noSniff());

• سياسة أمن المحتوى: يمنع الهجمات مثل هجمات XSS وحقن البيانات

const csp = require('helmet-csp')
app.use(csp({
   directives: {
       defaultSrc: ["'self'"],  // default value for all directives that are absent
       scriptSrc: ["'self'"],   // helps prevent XSS attacks
       frameAncestors: ["'none'"],  // helps prevent Clickjacking attacks
       imgSrc: ["'self'", "'http://imgexample.com'"],
       styleSrc: ["'none'"]
    }
}))

• التحكم في التخزين المؤقت وبراغما: لإدارة التخزين المؤقت ، وخاصة هذا العنوان يمكن أن يكون مفيدًا للصفحات التي تحتوي على بيانات حساسة. ومع ذلك ، تذكر أن تعطيل التخزين المؤقت في جميع الصفحات يمكن أن يؤثر على الأداء.

app.use(helmet.noCache());

• خيارات تنزيل X: يمنع الرأس Inter Explorer من تنفيذ الملفات التي تم تنزيلها

app.use(helmet.ieNoOpen());

• Expect-CT: شهادة الشفافية - آلية تم إنشاؤها لحل بعض المشاكل في البنية التحتية لشهادة SSL ، يخبر هذا الرأس المتصفح عن الحاجة إلى التحقق الإضافي من الشهادة في سجلات CT

var 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 ، لكن لا تنس مبادئ الأمان العامة التي يجب مراعاتها بغض النظر عن النظام الأساسي المستخدم.

All Articles