ممارسات البرمجة الآمنة: كتابة كود لا يمكن اختراقه
الأمن ليس ميزة — إنه خاصية. تعلم المبادئ والأنماط والتقنيات التي تجعل كودك مرناً ضد الهجمات الشائعة مع البقاء قابلاً للصيانة.
كل تطبيق لديه ثغرات. الفرق بين التطبيق القابل للاختراق والتطبيق الآمن هو ليس غياب الثغرات — بل الوعي بها وتخفيفها. البرمجة الآمنة هي ممارسة بناء التطبيقات بوعي مستمر بالخلفية الأمنية: كل دالة تكتبها، كل استعلام قاعدة بيانات تصوغه، كل إدخال مستخدم تعالجه هو هجوم محتمل في انتظار الحدوث.
هذا ليس تشاؤماً. إنه اعتراف بأن الأمن ليس خطوة نهائية — إنه ليس مراجعة قبل الإصدار، ولا فحصاً واحداً، وليس مسؤولية فريق أمني مختص. الأمن هو خاصية للتصميم. كودك إما آمن أو غير آمن من أول commit. هذا الفصل يغطي أكثر الثغرات فتكاً — تلك التي تظهر في قائمة OWASP العشرة الأوائل سنة بعد سنة — ويوضح كيفية البرمجة بطريقة تجعلها مستحيلة.
حقن SQL: المغزى الذي لا يموت
حقن SQL هو ثغرة العقد الأول من القرن الحادي والعشرين التي ترفض الاختفاء. تحدث عندما يقوم تطبيقك ببناء استعلام SQL عن طريق ربط إدخال المستخدم مباشرة بالسلسلة. هجوم بسيط — إدخال إغلاق علامة اقتباس وفاصلة منقوطة ثم تعليق — يمكنه تحويل استعلام آمن إلى أمر قاعدة بيانات مدمر.
-- BAD: string concatenation (SQL injection possible)
const query = `SELECT * FROM users WHERE email = '${userInput}'`;
-- If userInput = "admin@example.com' OR '1'='1"
-- Query becomes: SELECT * FROM users WHERE email = 'admin@example.com' OR '1'='1'
-- Returns ALL users
-- GOOD: parameterized query (SQL injection impossible)
const query = "SELECT * FROM users WHERE email = $1";
db.query(query, [userInput]);
-- User input is treated as data, not executable SQLالدفاع الوحيد والفعال الوحيد ضد حقن SQL هو الاستعلامات الم parametrized — تُعرف أيضاً باسم البيانات المعدة. قاعدة البيانات تعامل القيم الم parametrized كبيانات، وليس كأوامر SQL قابلة للتنفيذ. لا كمية من التعقيم أو إفلات السلاسل يمكن أن تطابق أمان البيانات الم parametrized. هذا ينطبق على كل لغة وقاعدة بيانات: في PostgreSQL مع $1، في MySQL مع ?، في SQL Server مع @p، في أدوات ORM مثل Prisma أو TypeORM تستخدم ميزات المعلمات المضمنة فيها.
XSS: عندما يهاجم متصفح المستخدم نفسه
البرمجة عبر المواقع (XSS) هي ثغرة في الواجهة الأمامية حيث يقوم تطبيقك بعرض إدخال مستخدم — تعليق، اسم ملف، رسالة — دون تعقيم مناسب، ويتم تنفيذ كود JavaScript ضار في متصفح ضحية آخر. إنها ثغرة تنتج عن الثقة — تطبيقك يثق بأن المستخدم لن يكتب <script> سيئاً.
-- BAD: rendering user input directly
const comment = '<script>fetch("/api/steal-cookies")</script>';
document.getElementById('comments').innerHTML = comment;
-- Every user who views this comment executes the attacker's script
-- GOOD: treating input as text, not HTML
const comment = '<script>fetch("/api/steal-cookies")</script>';
document.getElementById('comments').textContent = comment;
-- Rendered as literal text, not executable HTMLالمبدأ الأساسي هو: كل إدخال مستخدم يتم عرضه في متصفح هو هجوم محتمل. إطارات العمل الحديثة مثل React وVue وSvelte تتعامل مع هذا نيابة عنك — template expressions (JSX's {}، Vue's {{}}، Svelte's {}) تقوم بعرض النص، وليس HTML، بشكل افتراضي. لا تستخدم dangerouslySetInnerHTML (React)، v-html (Vue)، أو {@html} (Svelte) إلا إذا كنت تفهم بالضبط البيانات التي تمررها وتعقيمها بشكل صحيح.
كسر المصادقة: لماذا لا يجب أن تدير كلمات المرور بنفسك
واحدة من أكثر الثغرات تكلفة تأتي من إدارة المصادقة بشكل سيئ. تخزين كلمات المرور بنص واضح هو أسرع طريقة لظهور اسم شركتك في تغطية إخبارية لاختراق بيانات. حتى التجزئة بدون 'ملح' غير آمنة — جداول قوس قزح تجعل من السهل عكس تجزئات كلمات المرور الشائعة.
-- BAD: storing passwords unsalted or with weak hashing
const hashed = crypto.createHash('sha256').update(password).digest('hex');
-- Fast to compute = fast to brute-force
-- GOOD: bcrypt with cost factor >= 12
import bcrypt from 'bcrypt';
const salt = await bcrypt.genSalt(12); // 2^12 rounds
const hash = await bcrypt.hash(password, salt);
// bcrypt is deliberately slow = expensive to brute-force
-- Also good: Argon2id (OWASP recommended)
import * as argon2 from 'argon2';
const hash = await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 19456,
timeCost: 2,
});المبادئ الحاسمة لتصميم المصادقة: لا تخزن كلمات المرور أبداً — استخدم دوال تجزئة بطيئة ومقاومة للعنف (bcrypt، Argon2id، scrypt). أضف 'ملحاً' فريداً لكل كلمة مرور بحيث لا ينتج عن نفس كلمة المرور نفس التجزئة أبداً. نفذ الحماية من القوة العمياء — تأخير المحاولات، وحدود المعدل، وقفل الحساب بعد N محاولات فاشلة. واستخدم المصادقة متعددة العوامل كلما أمكن، خاصة للحسابات الحساسة.
إعدادات الأمان الضعيفة: ما لا تخبرك به الإعدادات الافتراضية
إطارات العمل وقواعد البيانات والخوادم تأتي بإعدادات افتراضية تعطي الأولوية للتوافق وسهولة الاستخدام على الأمن. من مسؤوليتك تغييرها. من أكثر الأخطاء شيوعاً: ترك تصحيح الأخطاء ممكناً في الإنتاج (تسرب معلومات حساسة عن النظام)، استخدام مفاتيح سرية افتراضية (JWT الموقعة بـ 'secret')، عدم تعيين خيارات HttpOnly وSecure وSameSite للكوكيز، وعدم تقييد CORS إلى الأصول الموثوقة فقط.
-- Secure cookie configuration (Express example)
res.cookie('session', token, {
httpOnly: true, // Cannot be read by JavaScript (prevents XSS token theft)
secure: true, // Only sent over HTTPS
sameSite: 'strict', // Not sent on cross-site requests (prevents CSRF)
maxAge: 3600000 // 1 hour
});
-- CORS: whitelist specific origins, never use '*'
app.use(cors({
origin: ['https://myapp.com', 'https://admin.myapp.com'],
credentials: true
}));قاعدة جيدة: اقرأ دليل الأمان لكل عنصر في مجموعتك التقنية. Express له واحد، React له واحد، PostgreSQL له واحد، Nginx له واحد. تجميع الضبط عبر هذه الإعدادات هو ما يحول مجموعة من الأدوات الآمنة بشكل معتدل إلى حصن.
كشف التعرض للبيانات الحساسة: ما لا يجب أن يغادر خادمك أبداً
أحد أكثر الثغرات دقة هو كشف البيانات الحساسة عن غير قصد — إرجاع كلمة مرور مجزأة في استجابة API، أو تضمين مفتاح خاص في سجل الخطأ، أو تضمين رقم بطاقة ائتمان في عنوان URL. هذه الأخطاء لا تتطلب هجوماً — مجرد متصفح أو سجل أو أداة شبكة.
-- BAD: returning sensitive fields in API response
GET /users/me -> { "id": 1, "name": "Ada", "password_hash": "$2b$12$...", "ssn_last4": "1234" }
-- GOOD: explicit response serialization (whitelist, not blacklist)
class UserResponse {
constructor(user) {
this.id = user.id;
this.name = user.name;
this.email = user.email;
// password_hash, ssn, internal notes are NOT included
}
}
app.get('/users/me', (req, res) => {
const user = db.findUser(req.userId);
res.json(new UserResponse(user));
});قواعد التصميم: القائمة البيضاء للحقول عند إرجاع البيانات، لا تستخدم أبداً الكيان كاملاً أو .toJSON() دون إزالة الحقول الحساسة صراحةً. قم بتعمية أو إخفاء البيانات الحساسة في السجلات (آخر 4 أرقام فقط من بطاقة الائتمان). استخدم التشفير للبيانات الحساسة المخزنة — تشفير على مستوى العمود في قاعدة البيانات للـ PII، واستخدم TLS 1.3 لجميع البيانات أثناء النقل. ولا ترسل أبداً مفاتيح API، أو رموز، أو كلمات مرور في عناوين URL — استخدم رؤوس HTTP بدلاً من ذلك.
إدارة التبعيات: أعظم ناقل هجوم غير مرئي
قاعدة البيانات الحديثة تعتمد على مئات التبعيات. متوسط مشروع Node.js لديه أكثر من 1000 حزمة في node_modules. أنت لا تثق فقط بكودك — بل تثق بكود ألف شخص آخر، كل منهم قد يرتكب خطأ أو يخضع لهجوم. Event-stream في 2018 هو تذكير مشهور: إضافة ضارة تم حقنها في حزمة شرعية استهدفت محافظ البيتكوين.
# Dependency security practices
# 1. Lock files — always commit them
npm ci # uses package-lock.json for deterministic installs
# 2. Regular auditing — automate it in CI
npm audit
snyk test
github: Dependabot
enable: true
opens: auto
# 3. Pin versions, don't use ranges
# BAD: "express": "^4.18.0" (auto-updates to minor/patch)
# GOOD: "express": "4.18.2" (exact version only)
# 4. Review before adopting new dependencies
# - How many maintainers?
# - When was the last release?
# - Does it need unnecessary permissions?ممارسة مهمة: أتمتة فحص التبعيات في CI/CD بحيث يفشل البناء إذا تم سحب حزمة بها ثغرة معروفة. قم بتحديث التبعيات كثيراً — الترقية المنتظمة تحافظ على إصدارات آمنة أكثر من سباقات التصحيح في اللحظة الأخيرة. وفكر في تحجيم التبعيات: هل تحتاج حقاً إلى تلك الحزمة المكونة من 50 سطراً؟ weight الخفيف هو وزن أمني أقل.
تسجيل الدخول ومراقبة الأمان: اعرف ما يحدث
حتى الكود الأكثر أمناً سيواجه هجمات. ما يحدد ما إذا كان الهجوم اختراقاً أو مجرد محاولة فاشلة هو الكشف والاستجابة. التسجيل الجيد يمنحك الرؤية — من حاول ماذا ومتى ومن أين. المراقبة الجيدة تمنحك التنبيه — عندما يحدث شيء غير متوقع، يعرف شخص ما خلال دقائق، ليس أياماً.
// Security events that should ALWAYS be logged
// - Failed login attempts (with IP and timestamp)
// - Successful logins from new locations/devices
// - Password reset requests
// - Permission changes (user promoted to admin)
// - API key creation or revocation
// - Unauthorized access attempts (403 errors)
// - Rate limit violations
// - Unusual data volume access (potential data exfiltration)لا تسجل البيانات الحساسة أبداً — لا كلمات مرور، لا رموز، لا أرقام بطاقات ائتمان، لا محتوى رسائل. تأكد من أن سجلاتك تحتوي على سياق كافٍ للتحقيق (IP، ومعرف المستخدم، وطابع زمني، وإجراء) دون الكشف عن أسرار. خزّن السجلات في نظام منفصل عن تطبيقك — إذا تم اختراق تطبيقك، يجب أن تبقى سجلاته سليمة للتحقيق.
مبدأ الامتياز الأقل: تقليل الضرر عندما يحدث الأسوأ
مبدأ الامتياز الأقل (PoLP) هو فلسفة أمنية أساسية: كل جزء من النظام — مستخدم، عملية، خدمة صغيرة، دالة — يجب أن يكون لديه فقط الأذونات اللازمة لأداء مهمته، لا أكثر. إذا تم اختراق مكون له امتيازات قراءة فقط، الضرر أقل مما لو تم اختراق مكون له امتيازات كتابة.
-- Database user privileges — grant least privilege
-- BAD: application user with full access
GRANT ALL PRIVILEGES ON DATABASE app TO app_user;
-- GOOD: application user has only what it needs
GRANT SELECT, INSERT, UPDATE, DELETE ON orders TO app_user;
GRANT SELECT, INSERT, UPDATE ON products TO app_user;
REVOKE DELETE ON audit_logs FROM app_user; -- app should never delete logs
GRANT USAGE ON SCHEMA public TO app_user;
-- Separate migration user with schema-altering privileges
CREATE USER migration_user WITH PASSWORD '...';
GRANT CREATE, ALTER, DROP ON SCHEMA public TO migration_user;طبق PoLP في كل مكان. كل خدمة صغيرة لها قاعدة بياناتها الخاصة ومفاتيح API الخاصة بها. مستخدم قاعدة بيانات التطبيق لديه صلاحيات CRUD على جداول بياناته فقط، ولا يمكنه تغيير المخطط. المفتاح المستخدم لقراءة من S3 لا يمكنه الكتابة. CI/CD pipeline له رموز مميزة ذات نطاق محدود. فصل الامتيازات هو شبكة أمان: عندما يفشل مكون ما، لا تسقط معه بقية النظام.
الأمن كثقافة، وليس قائمة مراجعة
الأمن ليس مشروعاً — إنه عملية مستمرة. ليست قائمة مراجعة من عشرة عناصر تشطبها قبل الإصدار. إنها عقلية: كل سطر من الكود هو هجوم محتمل، وكل تبعية هي ثقة، وكل إدخال مستخدم هو خطر. المطورون الآمنون لا يعرفون فقط كيفية إصلاح الثغرات — يعرفون كيفية عدم إنشائها في المقام الأول. يتبعون الأنماط التي تجعل الفئات الكاملة من الأخطاء مستحيلة — استعلامات م paramtrized، ومخرجات واجهة أمامية معرفة، ووظائف تجزئة كلمات مرور بطيئة، وأقل امتياز بشكل افتراضي.
عندما يصبح الأمن جزءاً من ثقافة البرمجة لديك — جزء من كيفية كتابة الكود، وكيفية مراجعة الكود، وكيفية تصميم الأنظمة — عندها فقط تنتقل من كونك ضحية للثغرات إلى كونك مطوراً لا يمكن اختراق تطبيقاته.
