API غير المؤمنة هي بوابة مفتوحة للهاكرز. في هذا الدليل العملي، سنفكك طبقات الأمان من المصادقة إلى الحد من الطلبات، مع أكواد حقيقية وحيل من تجارب شركات مثل تويتر وأمازون.
في أحد أيام الجمعة الماضية، تلقى أحد عملائي رسالة من مزود الاستضافة: "تم إيقاف سيرفرك بسبب هجوم DDoS قادم من 12 دولة مختلفة". السبب؟ API خاصته كان يسمح بـ 10,000 طلب في الثانية بدون أي حماية. لم يكن هناك Rate Limiting، ولا حتى مصادقة أساسية. النتيجة؟ خسارة 48 ساعة من التوقف، و32 ألف دولار من الإيرادات المفقودة. هذه ليست قصة درامية، بل واقع يومي يواجهه المطورون الذين يعتقدون أن "لن يستهدفني أحد". الحقيقة هي أن الروبوتات لا تنتظر دعوة - فهي تفحص الإنترنت بحثاً عن أي نقطة ضعف كل 39 ثانية وفقاً لدراسة من جامعة ميريلاند. إذا كنت تبني API اليوم، فالأمان ليس خياراً إضافياً، بل هو الطبقة الأولى من الكود الذي تكتبه.
في هذا الدليل، لن نتحدث عن الأمان النظري. سنذهب إلى ما وراء الكواليس: كيف تعمل الـ Tokens في الذاكرة؟ لماذا الـ JWT يمكن أن يكون سلاحاً ذا حدين؟ وكيف يمكن لـ Rate Limiting أن ينقذ سيرفرك من الانهيار تحت ضغط الطلبات؟ سنستخدم أمثلة واقعية من شركات مثل تويتر (الذي عانى من هجمات على API الخاص به في 2021) وأمازون (الذي يفرض حدود صارمة على API الخاص به). الكود الذي ستراه هنا ليس مجرد أمثلة تعليمية - إنه كود جاهز للإنتاج، مع ملاحظات حول الفخاخ التي يقع فيها حتى المطورون ذوو الخبرة.
عندما نتحدث عن مصادقة API، فإن أول ما يتبادر إلى الذهن هو الـ Basic Authentication. ببساطة، ترسل اسم المستخدم وكلمة المرور مشفرة بـ Base64 في الـ Header لكل طلب. يبدو سهلاً، أليس كذلك؟ المشكلة أن Base64 ليس تشفيراً حقيقياً - إنه مجرد ترميز. أي شخص يستطيع اعتراض الطلب (مثلاً عبر شبكة Wi-Fi عامة) يمكنه فك تشفير الـ Credentials في أقل من ثانية. في عام 2019، اكتشف باحثون في شركة Imperva أن 42% من هجمات API كانت تستهدف الـ Basic Auth بسبب سهولة اختراقه.
البديل؟ الـ Bearer Tokens، وخاصة الـ JWT (JSON Web Tokens). لكن حتى هنا، هناك فخاخ. الكثير من المطورين يعتقدون أن مجرد استخدام JWT يعني أنهم آمنون. الحقيقة أن JWT يمكن أن يكون خطيراً إذا لم يتم إعداده بشكل صحيح. مثلاً، إذا لم تقم بتفعيل التوقيع الرقمي (Digital Signature)، فإن الـ Token يصبح قابلاً للتعديل من قبل المهاجمين. في عام 2020، تعرضت شركة Zoom لاختراق بسبب استخدام JWT بدون توقيع قوي، مما سمح للمهاجمين بإنشاء tokens مزيفة للوصول إلى اجتماعات خاصة.
// مثال على JWT غير آمن (خطير جداً)
const jwt = require('jsonwebtoken');
// لا تستخدم هذا أبداً في الإنتاج!
const token = jwt.sign(
{ userId: 123 },
'', // سر فارغ = لا توقيع!
{ expiresIn: '1h' }
);
// هذا الكود يسمح لأي شخص بتعديل الـ Payload
// المهاجم يمكن أن يغير userId إلى أي قيمة يريدها
// الطريقة الصحيحة:
const secureToken = jwt.sign(
{ userId: 123 },
process.env.JWT_SECRET, // سر قوي ومخزن في بيئة آمنة
{ expiresIn: '1h', algorithm: 'HS256' } // خوارزمية توقيع محددة
);لكن حتى مع JWT آمن، هناك مشكلة أخرى: التخزين. أين تخزن الـ Token؟ في الـ LocalStorage؟ خطأ شائع. الـ LocalStorage يمكن الوصول إليه عبر JavaScript، مما يعني أن أي هجوم XSS (Cross-Site Scripting) يمكن أن يسرق الـ Token الخاص بالمستخدم. الحل؟ استخدم الـ HttpOnly Cookies. هذه الكوكيز لا يمكن الوصول إليها عبر JavaScript، مما يجعلها أكثر أماناً. لكن حتى هنا، يجب تفعيل الـ Secure Flag و SameSite Attribute لمنع هجمات CSRF (Cross-Site Request Forgery).
// إعداد كوكيز آمن في Express.js
const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
app.use(cookieParser());
app.post('/login', (req, res) => {
// بعد التحقق من بيانات المستخدم
const token = generateSecureJWT(); // دالة افتراضية لإنشاء JWT آمن
res.cookie('token', token, {
httpOnly: true, // لا يمكن الوصول عبر JS
secure: true, // فقط عبر HTTPS
sameSite: 'strict', // منع CSRF
maxAge: 3600000 // ساعة واحدة
});
res.send('تم تسجيل الدخول بنجاح');
});المصادقة تخبرك من هو المستخدم، لكن التفويض يحدد ما يمكنه فعله. الكثير من APIs تعتمد على نظام بسيط مثل "admin" و "user"، لكن هذا لا يكفي في التطبيقات الحقيقية. تخيل أن لديك API لإدارة الملفات السحابية. المستخدم A يملك ملفاً خاصاً، والمستخدم B يملك ملفاً آخر. إذا كان الـ API يعتمد فقط على دور "user"، فإن المستخدم B يمكن أن يصل إلى ملف المستخدم A إذا عرف الـ ID الخاص به. هذا ما يسمى بـ Insecure Direct Object Reference (IDOR)، وهو ثغرة شائعة جداً - لدرجة أنها احتلت المرتبة الرابعة في قائمة OWASP Top 10 لعام 2021.
الحل؟ استخدام نظام تفويض يعتمد على الـ Ownership. بدلاً من التحقق فقط من الدور، يجب التحقق من أن المستخدم يمتلك المورد الذي يحاول الوصول إليه. هذا يعني أن كل طلب يجب أن يمر عبر طبقة تحقق من الملكية قبل السماح بأي عملية. في قواعد البيانات، هذا غالباً ما يعني إضافة عمود مثل userId إلى الجداول، ثم استخدامه في الاستعلامات. لكن حتى هنا، هناك فخ: SQL Injection. إذا كنت تستخدم الاستعلامات المباشرة بدون Prepared Statements، فإن المهاجم يمكن أن يعدل الاستعلام للحصول على بيانات لا يملكها.
# مثال على تفويض آمن باستخدام Flask-SQLAlchemy
from flask import Flask, request, abort
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
db = SQLAlchemy(app)
class Document(db.Model):
id = db.Column(db.Integer, primary_key=True)
c db.Column(db.Text)
owner_id = db.Column(db.Integer, db.ForeignKey('user.id'))
@app.route('/documents/<int:doc_id>', methods=['GET'])
def get_document(doc_id):
# الحصول على المستخدم الحالي من الـ Token
current_user_id = get_current_user_id() # دالة افتراضية
# التحقق من الملكية باستخدام Prepared Statement
document = db.session.execute(
db.select(Document).where(
(Document.id == doc_id) & (Document.owner_id == current_user_id)
)
).scalar()
if not document:
abort(403, description="ليس لديك إذن للوصول إلى هذا المستند")
return {"content": document.content}
# هذا الكود يمنع IDOR لأنه يتحقق من owner_id قبل إرجاع البياناتفي بعض التطبيقات، لا يكفي التحقق من الدور أو الملكية. مثلاً، قد تريد السماح للمستخدمين بالوصول إلى البيانات فقط في ساعات العمل الرسمية، أو فقط إذا كانوا في بلد معين. هنا يأتي دور الـ ABAC، الذي يسمح لك بتعريف سياسات تفويض معقدة تعتمد على عدة عوامل. على سبيل المثال، في نظام إدارة المستشفيات، قد تريد السماح للأطباء بالوصول إلى سجلات المرضى فقط إذا كانوا في نفس القسم، وخلال نوبة عملهم الحالية.
// مثال على ABAC باستخدام مكتبة CASL
const { defineAbility } = require('@casl/ability');
const ability = defineAbility((can) => {
can('read', 'Document', {
ownerId: 123, // المستخدم الحالي
department: 'engineering', // قسم المستخدم
status: 'published', // حالة المستند
'$or': [
{ accessLevel: 'public' },
{ sharedWith: 123 } // تم المشاركة مع المستخدم
]
});
// يمكن للمستخدم تعديل المستند فقط إذا كان:
// - مالكه
// - والمستند ليس مؤرشفاً
// - والتعديل خلال ساعات العمل (9-5)
can('update', 'Document', {
ownerId: 123,
archived: false,
'$expr': {
'$and': [
{ '$gte': [new Date().getHours(), 9] },
{ '$lte': [new Date().getHours(), 17] }
]
}
});
});
// استخدام الـ Ability في الـ API
app.get('/documents/:id', (req, res) => {
const document = getDocument(req.params.id); // دالة افتراضية
if (!ability.can('read', document)) {
return res.status(403).send('غير مسموح بالوصول');
}
res.json(document);
});في عام 2020، تعرضت شركة Cloudflare لهجوم DDoS بلغ 17.2 مليون طلب في الثانية. كيف نجوا؟ بفضل نظام Rate Limiting متطور. الـ Rate Limiting ليس مجرد ميزة إضافية - إنه خط الدفاع الأول ضد هجمات الحرمان من الخدمة (DoS) والروبوتات الضارة. الفكرة بسيطة: تحديد عدد الطلبات التي يمكن للمستخدم إرسالها في فترة زمنية معينة. لكن التنفيذ الصحيح يتطلب فهماً عميقاً لكيفية عمل الـ Event Loop في Node.js أو الـ GIL في بايثون.
المشكلة أن الكثير من المطورين يعتمدون على حلول بسيطة مثل تخزين عدد الطلبات في الذاكرة. هذا يعمل بشكل جيد للتطبيقات الصغيرة، لكنه يفشل في البيئات الموزعة. إذا كان لديك عدة سيرفرات خلف Load Balancer، فإن تخزين الـ Counters في الذاكرة يعني أن كل سيرفر سيكون له عداد مستقل، مما يسمح للمهاجم بإرسال عدد أكبر من الطلبات عبر توزيعها على السيرفرات المختلفة. الحل؟ استخدام قاعدة بيانات موزعة مثل Redis لتخزين الـ Counters.
// Rate Limiting باستخدام Redis في Express.js
const express = require('express');
const redis = require('redis');
const { RateLimiterRedis } = require('rate-limiter-flexible');
const app = express();
const redisClient = redis.createClient({
host: 'localhost',
port: 6379,
enable_offline_queue: false,
});
// إعداد الـ Rate Limiter
const rateLimiter = new RateLimiterRedis({
storeClient: redisClient,
keyPrefix: 'rate_limit',
points: 100, // 100 طلب
duration: 60, // في 60 ثانية
blockDuration: 60, // حظر لمدة 60 ثانية إذا تجاوز الحد
});
// Middleware للـ Rate Limiting
const rateLimiterMiddleware = (req, res, next) => {
const userId = req.user.id; // افترض أننا حصلنا على المستخدم من الـ Token
rateLimiter.consume(userId)
.then(() => {
next();
})
.catch(() => {
res.status(429).send('عدد الطلبات كبير جداً. حاول لاحقاً.');
});
};
app.use(rateLimiterMiddleware);
// تطبيق الـ Rate Limiting على الـ API
app.get('/api/data', (req, res) => {
res.json({ data: 'معلومات سرية' });
});هناك طريقتان رئيسيتان لتنفيذ الـ Rate Limiting: Fixed Window و Sliding Window. الـ Fixed Window أسهل في التنفيذ: مثلاً، 100 طلب في الدقيقة. لكن المشكلة أنه يسمح بـ "Burst" من الطلبات في نهاية النافذة. مثلاً، إذا كان الحد 100 طلب في الدقيقة، يمكن للمستخدم إرسال 100 طلب في الثانية الأخيرة من الدقيقة، ثم 100 طلب أخرى في الثانية الأولى من الدقيقة التالية، مما يعني 200 طلب في ثانيتين. هذا قد يكون كافياً لإسقاط سيرفرك.
الـ Sliding Window يحل هذه المشكلة عن طريق تتبع الطلبات في نافذة زمنية متحركة. بدلاً من تقسيم الوقت إلى دقائق ثابتة، فإنه يحسب عدد الطلبات في آخر 60 ثانية. هذا يمنع الـ Burst ويوفر حماية أكثر دقة. لكن التنفيذ أكثر تعقيداً، خاصة في البيئات الموزعة. على سبيل المثال، إذا كان لديك عدة سيرفرات، فإن تتبع الـ Sliding Window يتطلب مزامنة دقيقة بين جميع السيرفرات، مما قد يؤدي إلى تأخير في الاستجابة.
# Sliding Window Rate Limiting باستخدام Redis
import time
import redis
r = redis.Redis(host='localhost', port=6379, db=0)
class SlidingWindowRateLimiter:
def __init__(self, key, max_requests, window_seconds):
self.key = key
self.max_requests = max_requests
self.window_sec window_seconds
def allow_request(self):
now = time.time()
window_start = now - self.window_seconds
# إزالة الطلبات القديمة
r.zremrangebyscore(self.key, 0, window_start)
# حساب عدد الطلبات الحالية
request_count = r.zcard(self.key)
if request_count >= self.max_requests:
return False
# إضافة الطلب الحالي
r.zadd(self.key, {now: now})
r.expire(self.key, self.window_seconds)
return True
# استخدام الـ Rate Limiter
limiter = SlidingWindowRateLimiter('user:123', 100, 60)
if not limiter.allow_request():
print("عدد الطلبات كبير جداً")
else:
print("الطلب مسموح به")في عام 2017، تعرضت شركة Equifax لاختراق ضخم بسبب ثغرة في API الخاص بها. السبب؟ عدم التحقق من مدخلات المستخدم بشكل صحيح. المهاجمون استطاعوا حقن SQL عبر حقل بحث لم يكن متوقعاً أن يحتوي على كود ضار. هذه ليست حالة نادرة - وفقاً لتقرير من شركة Akamai، فإن 83% من هجمات API تستهدف الـ Input Validation الضعيف.
الكثير من المطورين يعتمدون على الـ Frontend للتحقق من المدخلات، معتقدين أن هذا يكفي. لكن الـ Frontend يمكن تجاوزه بسهولة باستخدام أدوات مثل Postman أو حتى cURL. التحقق الحقيقي يجب أن يتم في الـ Backend، وعلى عدة مستويات: أولاً، التحقق من نوع البيانات (String, Number, Boolean)، ثم التحقق من التنسيق (مثلاً، البريد الإلكتروني يجب أن يحتوي على @)، وأخيراً التحقق من القيم المسموح بها (مثلاً، العمر يجب أن يكون بين 18 و 100).
// Input Validation باستخدام مكتبة Joi في Node.js
const Joi = require('joi');
// تعريف الـ Schema
const userSchema = Joi.object({
name: Joi.string()
.min(3)
.max(30)
.required()
.pattern(/^[a-zA-Z\s]+$/), // فقط حروف وأ espacios
email: Joi.string()
.email()
.required(),
age: Joi.number()
.integer()
.min(18)
.max(100)
.required(),
password: Joi.string()
.min(8)
.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/)
.required()
.messages({
'string.pattern.base': 'كلمة المرور يجب أن تحتوي على حرف كبير وحرف صغير ورقم ورمز خاص'
}),
role: Joi.string()
.valid('user', 'admin') // فقط القيم المسموح بها
.default('user')
});
// استخدام الـ Schema في الـ API
app.post('/register', async (req, res) => {
try {
const value = await userSchema.validateAsync(req.body);
// إذا وصلنا هنا، فالبيانات صحيحة
res.send('تم التسجيل بنجاح');
} catch (err) {
res.status(400).send(err.details[0].message);
}
});التحقق من المدخلات هو الخطوة الأولى، لكن ليس كافية. حتى إذا تأكدت من أن المدخلات تتبع التنسيق الصحيح، فإنها قد تحتوي على كود ضار. مثلاً، حقل الاسم قد يحتوي على >alert('hacked')</script>. إذا قمت بعرض هذا الاسم في صفحة ويب بدون معالجة، فإن الكود سيتم تنفيذه، مما يؤدي إلى هجوم XSS. الحل؟ الـ Sanitization، وهي عملية إزالة أو تحويل الأحرف الخطيرة إلى شكل آمن.
في عام 2021، تعرضت شركة Colonial Pipeline لهجوم فدية تسبب في إغلاق خط أنابيب نفط رئيسي في الولايات المتحدة. التحقيقات كشفت أن المهاجمين كانوا داخل الشبكة لمدة أسبوعين قبل تنفيذ الهجوم. لماذا لم يتم اكتشافهم؟ لأن الشركة لم تكن تراقب نشاطات الـ API بشكل فعال. الـ Logging والمراقبة ليسا مجرد أدوات لتصحيح الأخطاء - هما خط الدفاع الأخير ضد الهجمات.
المشكلة أن الكثير من المطورين يسجلون فقط الأخطاء، معتقدين أن هذا يكفي. لكن في الأمن، تحتاج إلى تسجيل كل شيء: من قام بالطلب؟ متى؟ من أي IP؟ ما هي الـ Headers التي أرسلها؟ ما هي البيانات التي طلبها؟ ثم تحتاج إلى تحليل هذه السجلات للبحث عن أنماط مشبوهة. مثلاً، إذا لاحظت أن مستخدماً واحداً يرسل 1000 طلب في الدقيقة، فقد يكون هذا روبوتاً ضاراً. إذا رأيت طلبات من دول غير متوقعة، فقد يكون هذا اختراقاً.
// Logging متقدم باستخدام Winston و Morgan في Express.js
const express = require('express');
const morgan = require('morgan');
const winston = require('winston');
const { combine, timestamp, json } = winston.format;
const app = express();
// إعداد Winston للـ Logging
const logger = winston.createLogger({
level: 'info',
format: combine(
timestamp(),
json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
// تسجيل الأخطاء غير المعالجة
process.on('uncaughtException', (err) => {
logger.error('uncaughtException', { error: err.message, stack: err.stack });
process.exit(1);
});
process.on('unhandledRejection', (err) => {
logger.error('unhandledRejection', { error: err.message, stack: err.stack });
});
// استخدام Morgan لتسجيل الطلبات
app.use(morgan('combined', {
stream: {
write: (message) => logger.info(message.trim())
}
}));
// تسجيل أحداث أمنية محددة
app.use((req, res, next) => {
// تسجيل محاولات الوصول غير المصرح بها
if (req.path === '/admin' && !req.user?.isAdmin) {
logger.warn('محاولة وصول غير مصرح بها إلى لوحة التحكم', {
ip: req.ip,
userId: req.user?.id,
path: req.path
});
}
// تسجيل الطلبات الكبيرة
if (JSON.stringify(req.body).length > 10000) {
logger.warn('طلب كبير الحجم', {
ip: req.ip,
size: JSON.stringify(req.body).length,
path: req.path
});
}
next();
});
// استخدام الـ Logger في الـ API
app.get('/api/data', (req, res) => {
logger.info('تم طلب البيانات', {
userId: req.user.id,
ip: req.ip,
userAgent: req.get('User-Agent')
});
res.json({ data: 'معلومات سرية' });
});الـ Logging التقليدي يعتمد على قواعد محددة مسبقاً، مثل "إذا كان هناك أكثر من 100 طلب في الدقيقة، أرسل تنبيهاً". لكن الهجمات الحديثة أصبحت أكثر ذكاءً، وغالباً ما تتجنب هذه القواعد. الحل؟ استخدام الذكاء الاصطناعي لاكتشاف الأنماط غير الطبيعية. مثلاً، إذا كان المستخدم عادة يرسل 10 طلبات في الدقيقة، ثم فجأة بدأ يرسل 100 طلب، فقد يكون هذا مؤشراً على اختراق. شركات مثل Darktrace و Vectra تستخدم هذه التقنية لحماية شبكات الشركات الكبيرة.
في الـ API، يمكنك استخدام مكتبات مثل TensorFlow.js أو Scikit-learn لبناء نماذج بسيطة للكشف عن الشذوذ. الفكرة هي تدريب النموذج على البيانات الطبيعية، ثم استخدامه للكشف عن أي سلوك غير طبيعي. مثلاً، يمكنك تدريب النموذج على عدد الطلبات لكل مستخدم، ثم استخدامه للكشف عن أي زيادة مفاجئة في الطلبات. بالطبع، هذا يتطلب كمية كبيرة من البيانات التاريخية، لكنه فعال جداً في الكشف عن الهجمات التي لا يمكن اكتشافها بالقواعد التقليدية.
# Anomaly Detection باستخدام Isolation Forest في بايثون
import numpy as np
from sklearn.ensemble import IsolationForest
# بيانات التدريب: عدد الطلبات لكل مستخدم في الدقيقة (طبيعي)
X_train = np.array([
[5], [6], [4], [7], [5], [6], [4], [8], [5], [6], # مستخدم 1
[10], [12], [9], [11], [10], [13], [8], [12], [11], [9], # مستخدم 2
# ... المزيد من البيانات
])
# تدريب النموذج
model = IsolationForest(c0.01) # 1% من البيانات تعتبر شاذة
model.fit(X_train)
# الكشف عن الشذوذ في البيانات الجديدة
def detect_anomaly(requests_per_minute):
prediction = model.predict([[requests_per_minute]])
return prediction[0] == -1 # -1 يعني شاذ
# مثال على الاستخدام
if detect_anomaly(50): # 50 طلب في الدقيقة لشخص عادة يرسل 5
print("⚠ تنبيه: نشاط مشبوه تم اكتشافه!")
else:
print("النشاط طبيعي")بعد أكثر من عشر سنوات في بناء وحماية APIs لمشاريع تتراوح من الشركات الناشئة إلى الشركات الكبرى، هذه هي النصائح التي أتمنى لو ها في بداية مسيرتي:
الأمان ليس مشروعاً جانبياً - إنه جزء لا يتجزأ من تطوير API. كلما بدأت مبكراً في التفكير في الأمان، كلما كان ذلك أسهل وأقل تكلفة. تذكر: المهاجمون لا ينتظرون حتى تنتهي من تطوير الميزات الجديدة. هم يفحصون الإنترنت بحثاً عن أي نقطة ضعف، كل ثانية من كل يوم. لا تجعل API الخاص بك هدفاً سهلاً.