أفضل الممارسات الأمنية
أفضل الممارسات الأمنية في Node.js
الأمان أمر بالغ الأهمية لأي تطبيق إنتاجي. تواجه تطبيقات Node.js تهديدات أمنية مختلفة، من هجمات الحقن إلى ثغرات التبعيات. يغطي هذا الدرس الممارسات الأمنية الأساسية لحماية تطبيقاتك.
OWASP أهم 10 لـ Node.js
يحتفظ مشروع Open Web Application Security Project (OWASP) بقائمة بأخطر مخاطر أمان تطبيقات الويب:
- التحكم في الوصول المكسور: فحوصات التفويض غير الصحيحة
- إخفاقات التشفير: تشفير ضعيف، أسرار مكشوفة
- الحقن: حقن SQL و NoSQL والأوامر
- التصميم غير الآمن: ضوابط أمان مفقودة
- التكوين الخاطئ للأمان: بيانات اعتماد افتراضية، أخطاء مفصلة
- المكونات الضعيفة: تبعيات قديمة
- إخفاقات المصادقة: كلمات مرور ضعيفة، إدارة الجلسة
- إخفاقات سلامة البيانات: إلغاء تسلسل غير آمن
- إخفاقات التسجيل: مراقبة غير كافية
- تزوير طلب من جانب الخادم (SSRF): عمليات إعادة توجيه غير مصادق عليها
منع حقن SQL
يحدث حقن SQL عندما يتم تضمين بيانات غير موثوقة في استعلامات SQL دون تعقيم مناسب:
// سيئ: عرضة لحقن SQL
app.get('/users/:id', async (req, res) => {
const userId = req.params.id;
const query = `SELECT * FROM users WHERE id = ${userId}`;
// يمكن للمهاجم إرسال: /users/1 OR 1=1
const user = await db.query(query);
res.json(user);
});
// جيد: استخدام الاستعلامات ذات المعاملات (البيانات المحضرة)
app.get('/users/:id', async (req, res) => {
const userId = req.params.id;
const query = 'SELECT * FROM users WHERE id = ?';
const user = await db.query(query, [userId]);
res.json(user);
});
// أفضل: استخدام ORM (مثل Sequelize، TypeORM)
app.get('/users/:id', async (req, res) => {
const user = await User.findByPk(req.params.id);
res.json(user);
});
// التحقق من صحة الإدخال مع express-validator
const { param, validationResult } = require('express-validator');
app.get('/users/:id',
param('id').isInt().toInt(),
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const user = await User.findByPk(req.params.id);
res.json(user);
}
);
منع حقن NoSQL
قواعد بيانات NoSQL مثل MongoDB معرضة أيضًا لهجمات الحقن:
// سيئ: عرضة لحقن NoSQL
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// يمكن للمهاجم إرسال: {username: {$gt: ""}, password: {$gt: ""}}
const user = await User.findOne({ username, password });
if (user) {
res.json({ success: true });
}
});
// جيد: تعقيم المدخلات
const mongoSanitize = require('express-mongo-sanitize');
app.use(mongoSanitize());
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// التحقق من أنواع الإدخال
if (typeof username !== 'string' || typeof password !== 'string') {
return res.status(400).json({ error: 'إدخال غير صالح' });
}
const user = await User.findOne({ username });
if (!user) {
return res.status(401).json({ error: 'بيانات اعتماد غير صالحة' });
}
// استخدام bcrypt لمقارنة كلمات المرور
const isValid = await bcrypt.compare(password, user.passwordHash);
if (isValid) {
res.json({ success: true });
} else {
res.status(401).json({ error: 'بيانات اعتماد غير صالحة' });
}
});
منع البرمجة النصية عبر المواقع (XSS)
تقوم هجمات XSS بحقن نصوص برمجية ضارة في صفحات الويب التي يشاهدها مستخدمون آخرون:
// تثبيت helmet لرؤوس الأمان
const helmet = require('helmet');
app.use(helmet());
// تثبيت xss-clean لتعقيم إدخال المستخدم
const xss = require('xss-clean');
app.use(xss());
// سيئ: عرض إدخال المستخدم غير المعقم
app.get('/profile/:username', async (req, res) => {
const user = await User.findOne({ username: req.params.username });
res.send(`<h1>${user.bio}</h1>`); // ثغرة XSS!
});
// جيد: تهريب HTML في القوالب
const escapeHtml = require('escape-html');
app.get('/profile/:username', async (req, res) => {
const user = await User.findOne({ username: req.params.username });
res.send(`<h1>${escapeHtml(user.bio)}</h1>`);
});
// أفضل: استخدام محرك القوالب الذي يهرب تلقائيًا
app.set('view engine', 'ejs'); // أو pug، handlebars، إلخ.
app.get('/profile/:username', async (req, res) => {
const user = await User.findOne({ username: req.params.username });
res.render('profile', { user }); // يهرب EJS تلقائيًا بشكل افتراضي
});
// سياسة أمان المحتوى (CSP)
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'trusted-cdn.com'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
}
}));
الحماية من تزوير الطلبات عبر المواقع (CSRF)
تخدع هجمات CSRF المستخدمين المصادق عليهم لأداء إجراءات غير مرغوب فيها:
const csrf = require('csurf');
const cookieParser = require('cookie-parser');
app.use(cookieParser());
// وسيط حماية CSRF
const csrfProtection = csrf({ cookie: true });
// إضافة رمز CSRF إلى النماذج
app.get('/form', csrfProtection, (req, res) => {
res.render('form', { csrfToken: req.csrfToken() });
});
// التحقق من صحة رمز CSRF في POST
app.post('/submit', csrfProtection, (req, res) => {
// يتم التحقق من صحة رمز CSRF تلقائيًا
res.json({ success: true });
});
// لمسارات API، استخدم نمط الإرسال المزدوج لملفات تعريف الارتباط
app.use((req, res, next) => {
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'DELETE') {
const token = req.headers['x-csrf-token'];
const cookieToken = req.cookies.csrfToken;
if (!token || token !== cookieToken) {
return res.status(403).json({ error: 'رمز CSRF غير صالح' });
}
}
next();
});
تدقيق التبعيات
التبعيات الضعيفة هي خطر أمني كبير. قم بتدقيق وتحديث حزمك بانتظام:
// تشغيل npm audit للتحقق من الثغرات
npm audit
// إصلاح الثغرات تلقائيًا (إن أمكن)
npm audit fix
// فرض الإصلاح (قد يقدم تغييرات كسرية)
npm audit fix --force
// إنشاء تقرير تدقيق مفصل
npm audit --json > audit-report.json
// استخدام Snyk للمراقبة المستمرة
npm install -g snyk
snyk auth
snyk test // الاختبار للثغرات
snyk monitor // مراقبة المشروع باستمرار
snyk wizard // إصلاح الثغرات التفاعلي
// إضافة إلى سكريبتات package.json
{
"scripts": {
"audit": "npm audit",
"audit:fix": "npm audit fix",
"security:check": "snyk test"
}
}
// استخدام npm-check-updates للحفاظ على التبعيات الحالية
npm install -g npm-check-updates
ncu // التحقق من التحديثات
ncu -u // تحديث package.json
npm install // تثبيت الحزم المحدثة
رؤوس الأمان
تحمي رؤوس أمان HTTP من الهجمات الشائعة:
const helmet = require('helmet');
// استخدام helmet مع تكوين مخصص
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'trusted-cdn.com'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"],
}
},
hsts: {
maxAge: 31536000, // سنة واحدة
includeSubDomains: true,
preload: true
},
noSniff: true, // X-Content-Type-Options: nosniff
frameguard: { // X-Frame-Options: DENY
action: 'deny'
},
xssFilter: true, // X-XSS-Protection: 1; mode=block
referrerPolicy: {
policy: 'strict-origin-when-cross-origin'
}
}));
// رؤوس أمان إضافية
app.use((req, res, next) => {
res.setHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
res.setHeader('X-Powered-By', ''); // إزالة رأس X-Powered-By
next();
});
أمان متغيرات البيئة
حماية بيانات التكوين الحساسة:
// استخدام dotenv للتطوير المحلي فقط
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config();
}
// التحقق من صحة متغيرات البيئة المطلوبة
const requiredEnvVars = [
'DATABASE_URL',
'JWT_SECRET',
'API_KEY'
];
requiredEnvVars.forEach((varName) => {
if (!process.env[varName]) {
throw new Error(`متغير البيئة ${varName} مطلوب`);
}
});
// استخدام أسرار قوية (32 حرفًا كحد أدنى)
const crypto = require('crypto');
// إنشاء سر عشوائي آمن
const generateSecret = () => {
return crypto.randomBytes(32).toString('hex');
};
console.log('السر المولد:', generateSecret());
// تخزين الأسرار في خزائن آمنة (الإنتاج)
// - AWS Secrets Manager
// - Azure Key Vault
// - HashiCorp Vault
// - Google Secret Manager
// مثال مع AWS Secrets Manager
const AWS = require('aws-sdk');
const secretsManager = new AWS.SecretsManager({ region: 'us-east-1' });
async function getSecret(secretName) {
const data = await secretsManager.getSecretValue({ SecretId: secretName }).promise();
return JSON.parse(data.SecretString);
}
// الاستخدام
const dbCredentials = await getSecret('prod/database/credentials');
إعداد HTTPS
استخدم دائمًا HTTPS في الإنتاج لتشفير البيانات أثناء النقل:
const https = require('https');
const fs = require('fs');
// تحميل شهادات SSL
const privateKey = fs.readFileSync('/path/to/private-key.pem', 'utf8');
const certificate = fs.readFileSync('/path/to/certificate.pem', 'utf8');
const ca = fs.readFileSync('/path/to/ca-bundle.pem', 'utf8');
const credentials = {
key: privateKey,
cert: certificate,
ca: ca
};
// إنشاء خادم HTTPS
const httpsServer = https.createServer(credentials, app);
httpsServer.listen(443, () => {
console.log('خادم HTTPS يعمل على المنفذ 443');
});
// إعادة توجيه HTTP إلى HTTPS
const http = require('http');
http.createServer((req, res) => {
res.writeHead(301, { Location: `https://${req.headers.host}${req.url}` });
res.end();
}).listen(80);
// فرض HTTPS في Express
app.use((req, res, next) => {
if (req.secure || req.headers['x-forwarded-proto'] === 'https') {
next();
} else {
res.redirect(301, `https://${req.headers.host}${req.url}`);
}
});
تحديد المعدل
الحماية من هجمات القوة الغاشمة و DDoS:
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
// محدد المعدل العام
const generalLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 دقيقة
max: 100, // حد كل IP إلى 100 طلب لكل windowMs
message: 'طلبات كثيرة جدًا، يرجى المحاولة مرة أخرى لاحقًا',
standardHeaders: true,
legacyHeaders: false,
});
app.use(generalLimiter);
// محدد صارم لنقاط نهاية المصادقة
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
skipSuccessfulRequests: true, // لا تحسب عمليات تسجيل الدخول الناجحة
message: 'محاولات تسجيل دخول كثيرة جدًا، يرجى المحاولة مرة أخرى لاحقًا'
});
app.post('/login', authLimiter, async (req, res) => {
// منطق تسجيل الدخول
});
// استخدام Redis لتحديد المعدل الموزع
const redisClient = require('redis').createClient({
host: 'localhost',
port: 6379
});
const distributedLimiter = rateLimit({
store: new RedisStore({
client: redisClient,
prefix: 'rl:',
}),
windowMs: 15 * 60 * 1000,
max: 100
});
app.use(distributedLimiter);
تجزئة كلمة المرور الآمنة
const bcrypt = require('bcrypt');
// تجزئة كلمة المرور قبل التخزين
app.post('/register', async (req, res) => {
const { username, password } = req.body;
// التحقق من قوة كلمة المرور
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
if (!passwordRegex.test(password)) {
return res.status(400).json({
error: 'يجب أن تتكون كلمة المرور من 8 أحرف على الأقل مع أحرف كبيرة وصغيرة ورقم وحرف خاص'
});
}
// تجزئة كلمة المرور باستخدام bcrypt (عامل التكلفة: 10-12 موصى به)
const saltRounds = 12;
const passwordHash = await bcrypt.hash(password, saltRounds);
// تخزين المستخدم مع كلمة المرور المجزأة
const user = await User.create({
username,
passwordHash
});
res.json({ success: true });
});
// التحقق من كلمة المرور أثناء تسجيل الدخول
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = await User.findOne({ username });
if (!user) {
return res.status(401).json({ error: 'بيانات اعتماد غير صالحة' });
}
const isValid = await bcrypt.compare(password, user.passwordHash);
if (!isValid) {
return res.status(401).json({ error: 'بيانات اعتماد غير صالحة' });
}
// إنشاء رمز JWT
const token = jwt.sign(
{ userId: user.id },
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
res.json({ token });
});
- لا تخزن كلمات المرور النصية أبدًا
- استخدم bcrypt أو argon2 أو scrypt (وليس MD5 أو SHA1)
- استخدم جولات الملح بين 10-12 لـ bcrypt
- فرض سياسات كلمة مرور قوية (8 أحرف على الأقل، متطلبات التعقيد)
- تنفيذ قفل الحساب بعد محاولات تسجيل الدخول الفاشلة
- استخدام المصادقة الثنائية (2FA) للحسابات الحساسة
تمرين عملي
المهمة: تأمين تطبيق Node.js ضعيف:
- قم بتشغيل npm audit وإصلاح جميع الثغرات
- قم بتنفيذ helmet لرؤوس الأمان
- أضف التحقق من صحة الإدخال مع express-validator
- قم بتنفيذ حماية CSRF
- أضف تحديد المعدل لنقاط نهاية المصادقة
- قم بإعداد HTTPS مع Let's Encrypt
- قم بتنفيذ تجزئة كلمة المرور الآمنة مع bcrypt
- أضف تعقيم MongoDB
- قم بتكوين سياسة أمان المحتوى
- اختبر الأمان مع OWASP ZAP أو Burp Suite
- ✓ استخدام HTTPS في كل مكان
- ✓ الحفاظ على التبعيات محدثة (npm audit)
- ✓ تنفيذ تحديد المعدل
- ✓ استخدام رؤوس الأمان (helmet)
- ✓ التحقق من صحة وتعقيم جميع المدخلات
- ✓ استخدام الاستعلامات ذات المعاملات
- ✓ تجزئة كلمات المرور باستخدام bcrypt
- ✓ تنفيذ حماية CSRF
- ✓ إعداد التسجيل والمراقبة
- ✓ استخدام متغيرات البيئة للأسرار
- ✓ تنفيذ المصادقة والتفويض
- ✓ اختبار الأمان المنتظم
- helmet: رؤوس الأمان
- express-rate-limit: تحديد المعدل
- express-validator: التحقق من صحة الإدخال
- bcrypt: تجزئة كلمة المرور
- csurf: حماية CSRF
- express-mongo-sanitize: منع حقن NoSQL
- xss-clean: منع XSS
- hpp: منع تلوث معاملات HTTP
- cors: تكوين CORS
- snyk: فحص الثغرات