API غير المؤمنة هي بوابة مفتوحة للهاكرز. في هذا الدليل العملي، سنفكك كل طبقة أمنية من المصادقة إلى تحديد المعدل، مع أكواد حقيقية وحيل لا يعرفها إلا المحترفون الذين يتعاملون مع ملايين الطلبات يومياً.
في عام ٢٠٢٣ وحده، تعرضت أكثر من ٧٠٪ من الشركات لهجمات على واجهات برمجة التطبيقات الخاصة بها، وفقاً لتقرير سولارويندز. المشكلة ليست في ضعف الأدوات، بل في أننا نتعامل مع الأمان كإضافة أخيرة وليس كطبقة أساسية. تخيل أن لديك واجهة برمجة تطبيقات تعرض بيانات حساسة للعملاء، وفي يوم من الأيام تجد أن سيرفرك قد أصبح بطيئاً بشكل غريب. بعد التحقيق، تكتشف أن أحدهم يرسل ١٠٠٠ طلب في الثانية الواحدة، ليس لسرقة البيانات، بل فقط لإسقاط الخدمة. هذا ليس سيناريو خيالياً، بل حدث حقيقي لشركة ناشئة في مجال الفنتك كانت تعتمد على API واحد لربط جميع خدماتها. الخطأ؟ لم يكن لديهم Rate Limiting فعال، وكانوا يستخدمون مفاتيح API ثابتة بدون تحديث دوري. في هذا المقال، سنذهب خطوة بخطوة لبناء طبقات أمان قوية لواجهة برمجة التطبيقات الخاصة بك، بدءاً من المصادقة وصولاً إلى حماية السيرفر من الهجمات، مع التركيز على ما يحدث خلف الكواليس في الذاكرة والمعالج.
عندما نتحدث عن أمان API، فإننا لا نتحدث فقط عن منع الوصول غير المصرح به، بل أيضاً عن ضمان أن النظام قادر على التعامل مع الحمل دون أن ينهار. الكثير من المطورين يركزون على الجانب الوظيفي ويهملون الجانب الأمني، معتقدين أن الهجمات لن تستهدفهم لأنهم ليسوا شركة كبيرة. لكن الحقيقة هي أن الهجمات الآلية لا تفرق بين شركة ناشئة وسيرفر شخصي. في تجربتي، رأيت مشاريع صغيرة تتعرض لهجمات بسيطة مثل Brute Force على نقاط النهاية الخاصة بها، فقط لأن المطورين استخدموا كلمات مرور ضعيفة للمصادقة الأساسية. المشكلة الأكبر هي أن الكثير من الأدوات الأمنية تأتي مع إعدادات افتراضية غير آمنة، والمطورون لا يكلفون أنفسهم عناء تغييرها.
المصادقة الأساسية (Basic Auth) هي أسهل طريقة لتأمين API، لكنها أيضاً الأكثر ضعفاً. عندما ترسل طلباً باستخدام Basic Auth، فإنك ترسل اسم المستخدم وكلمة المرور مشفرين بتشفير Base64 فقط، وهذا يعني أنه يمكن فك تشفيرهما بسهولة إذا تم اعتراض الطلب. المشكلة ليست فقط في سهولة فك التشفير، بل أيضاً في أن الكثير من المطورين يستخدمون كلمات مرور ضعيفة أو لا يغيرونها أبداً. في إحدى المشاريع التي عملت عليها، كان لدينا API داخلي يستخدم Basic Auth، وكنا نستخدم كلمة مرور واحدة لجميع المطورين. بعد فترة، اكتشفنا أن أحد المطورين السابقين كان قد نسخ كلمة المرور واستخدمها للوصول إلى البيانات بعد مغادرته الشركة. الحل؟ الانتقال إلى نظام أكثر أماناً مثل JWT أو OAuth 2.0.
JWT (JSON Web Tokens) هو خيار شائع لأنه يسمح بتخزين معلومات المصادقة في الـ Payload ويمكن التحقق منه بسهولة دون الحاجة إلى استعلام قاعدة البيانات في كل طلب. لكن حتى JWT له مشاكله. على سبيل المثال، إذا لم تقم بتعيين مدة صلاحية قصيرة للـ Token، فقد يظل صالحاً لفترة طويلة بعد أن يغادر المستخدم النظام. أيضاً، إذا لم تستخدم HTTPS، يمكن اعتراض الـ Token بسهولة. في أحد المشاريع، استخدمنا JWT مع مدة صلاحية طويلة جداً، وفي إحدى المرات، تم سرقة Token من جهاز مستخدم، واستخدمه المهاجم للوصول إلى البيانات الحساسة. الحل كان تقصير مدة الصلاحية وإضافة طبقة إضافية من التحقق باستخدام Refresh Tokens.
// مثال على توليد JWT باستخدام Node.js و jsonwebtoken
const jwt = require('jsonwebtoken');
const secretKey = process.env.JWT_SECRET; // يجب أن تكون سرية وقوية
function generateToken(user) {
const payload = {
id: user.id,
email: user.email,
role: user.role
};
const opti {
expiresIn: '15m', // مدة صلاحية قصيرة
issuer: 'your-api-service'
};
return jwt.sign(payload, secretKey, options);
}
// مثال على التحقق من JWT
function verifyToken(token) {
try {
const decoded = jwt.verify(token, secretKey);
return decoded;
} catch (err) {
throw new Error('Invalid or expired token');
}
}
// استخدام Refresh Token لتجديد JWT
function generateRefreshToken(user) {
const payload = { id: user.id };
const options = {
expiresIn: '7d', // مدة صلاحية أطول
issuer: 'your-api-service'
};
return jwt.sign(payload, process.env.REFRESH_SECRET, options);
}OAuth 2.0 هو بروتوكول تفويض يسمح للتطبيقات بالوصول إلى الموارد نيابة عن المستخدم دون الحاجة إلى مشاركة كلمات المرور. على سبيل المثال، عندما تسمح لتطبيق ما بالوصول إلى حسابك على جوجل أو فيسبوك، فأنت تستخدم OAuth 2.0. لكن OAuth ليس حلاً سحرياً، فهو يأتي مع تعقيداته الخاصة. أحد الأخطاء الشائعة هو استخدام نوع Grant غير المناسب. على سبيل المثال، استخدام Implicit Grant في التطبيقات التي تعمل من جانب الخادم يمكن أن يعرض الـ Access Token للخطر، حيث يتم إرساله عبر عنوان URL. في إحدى المرات، رأيت تطبيقاً يستخدم Implicit Grant لإرسال الـ Token عبر الـ URL، وتم اعتراضه بسهولة لأنه كان مرئياً في سجلات الخادم.
الحل هو استخدام Authorization Code Grant مع PKCE (Proof Key for Code Exchange)، وهو مصمم خصيصاً للتطبيقات التي تعمل من جانب العميل. PKCE يضيف طبقة أمان إضافية عن طريق توليد رمز مؤقت يتم استخدامه للتحقق من أن الطلب يأتي من التطبيق الأصلي وليس من مهاجم. أيضاً، يجب دائماً استخدام HTTPS لضمان أن البيانات تنتقل بشكل آمن. في مشروع آخر، استخدمنا OAuth 2.0 مع PKCE لتأمين تطبيق جوال، وكان الفرق ملحوظاً في الأمان مقارنة باستخدام Implicit Grant.
# مثال على تنفيذ OAuth 2.0 مع PKCE باستخدام Flask و Authlib
from authlib.integrations.flask_client import OAuth
from flask import Flask, request, redirect, session, url_for
import secrets
import base64
import hashlib
app = Flask(__name__)
app.secret_key = 'your-secret-key-here'
oauth = OAuth(app)
google = oauth.register(
name='google',
client_id='your-client-id',
client_secret='your-client-secret',
access_token_url='https://oauth2.googleapis.com/token',
authorize_url='https://accounts.google.com/o/oauth2/auth',
api_base_url='https://www.googleapis.com/oauth2/v1/',
client_kwargs={'scope': 'email profile'}
)
@app.route('/login')
def login():
# توليد رمز مؤقت لـ PKCE
code_verifier = secrets.token_urlsafe(64)
session['code_verifier'] = code_verifier
# توليد code_challenge
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode()).digest()
).decode().rstrip('=')
redirect_uri = url_for('authorize', _external=True)
return google.authorize_redirect(
redirect_uri,
code_challenge=code_challenge,
code_challenge_method='S256'
)
@app.route('/authorize')
def authorize():
token = google.authorize_access_token()
code_verifier = session.pop('code_verifier', None)
if not code_verifier:
return 'Invalid request', 400
# هنا يمكنك التحقق من الـ token والحصول على بيانات المستخدم
user_info = google.get('userinfo').json()
return f"Hello, {user_info['name']}!"
if __name__ == '__main__':
app.run(debug=True)الكثير من المطورين يعتقدون أن التشفير ضروري فقط للبيانات التي تنتقل عبر الإنترنت، لكنهم ينسون أن البيانات يمكن أن تتعرض للخطر حتى داخل شبكتهم الداخلية. على سبيل المثال، إذا كنت تخزن بيانات حساسة في قاعدة البيانات بدون تشفير، فقد يتمكن أحد المهاجمين الذي يحصل على وصول إلى قاعدة البيانات من قراءتها بسهولة. في إحدى الشركات التي عملت معها، كانت قاعدة البيانات تحتوي على معلومات بطاقات الائتمان للعملاء بدون تشفير، وعندما تعرضت الشركة لاختراق، تم سرقة ملايين البطاقات. الحل؟ استخدام التشفير من طرف إلى طرف، بحيث تكون البيانات مشفرة دائماً سواء أثناء النقل أو التخزين.
أحد الأدوات القوية للتشفير هي مكتبة OpenSSL، التي تسمح بتشفير البيانات باستخدام خوارزميات قوية مثل AES-256. لكن حتى مع استخدام التشفير، هناك أخطاء شائعة يمكن أن تعرض البيانات للخطر. على سبيل المثال، استخدام نفس مفتاح التشفير لجميع السجلات في قاعدة البيانات يمكن أن يؤدي إلى تسريب البيانات إذا تم كشف المفتاح. الحل هو استخدام مفتاح تشفير فريد لكل سجل، ويمكن تحقيق ذلك باستخدام تقنيات مثل envelope encryption، حيث يتم تشفير البيانات بمفتاح بيانات فريد، ثم يتم تشفير مفتاح البيانات باستخدام مفتاح رئيسي مخزن بأمان.
# مثال على تشفير البيانات باستخدام AES-256 مع Python و cryptography
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.backends import default_backend
import base64
import os
# توليد مفتاح تشفير من كلمة مرور
password = b'my-secret-password'
salt = os.urandom(16)
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterati100000,
backend=default_backend()
)
key = base64.urlsafe_b64encode(kdf.derive(password))
cipher_suite = Fernet(key)
# تشفير البيانات
data = b"Sensitive data that needs encryption"
encrypted_data = cipher_suite.encrypt(data)
print(f"Encrypted: {encrypted_data}")
# فك التشفير
decrypted_data = cipher_suite.decrypt(encrypted_data)
print(f"Decrypted: {decrypted_data.decode()}")
# مثال على envelope encryption
# توليد مفتاح بيانات فريد لكل سجل
import secrets
data_key = secrets.token_bytes(32)
cipher_suite_data = Fernet(base64.urlsafe_b64encode(data_key))
# تشفير البيانات بمفتاح البيانات
encrypted_data = cipher_suite_data.encrypt(data)
# تشفير مفتاح البيانات بالمفتاح الرئيسي
encrypted_data_key = cipher_suite.encrypt(data_key)
# لفك التشفير، أولاً فك تشفير مفتاح البيانات بالمفتاح الرئيسي
# ثم استخدام مفتاح البيانات لفك تشفير البيانات
print(f"Encrypted Data Key: {encrypted_data_key}")حتى إذا كنت تستخدم أقوى خوارزميات التشفير، إذا لم تستخدم HTTPS، فستكون بياناتك مكشوفة أثناء النقل. HTTPS ليس مجرد طبقة إضافية من الأمان، بل هو الأساس الذي يبنى عليه كل شيء آخر. في عام ٢٠١٤، تعرضت شركة ياهو لهجوم ضخم أدى إلى سرقة بيانات ٥٠٠ مليون حساب، وكان أحد الأسباب الرئيسية هو عدم استخدام HTTPS بشكل كافٍ. المشكلة ليست فقط في اعتراض البيانات، بل أيضاً في إمكانية تعديلها أثناء النقل. على سبيل المثال، إذا كان لديك API يعرض أسعار المنتجات، فقد يتمكن مهاجم من تعديل الاستجابة لتغيير الأسعار قبل أن تصل إلى العميل.
لحسن الحظ، أصبح الحصول على شهادة SSL/TLS مجانياً وسهلاً بفضل مبادرات مثل Let's Encrypt. لكن حتى مع استخدام HTTPS، هناك أخطاء شائعة يمكن أن تعرض البيانات للخطر. على سبيل المثال، استخدام بروتوكولات قديمة مثل TLS 1.0 أو 1.1 يمكن أن يجعل الاتصال عرضة للهجمات مثل POODLE و BEAST. الحل هو تحديث السيرفر لاستخدام أحدث إصدار من TLS، وهو TLS 1.3 حالياً. أيضاً، يجب تعطيل دعم البروتوكولات القديمة لضمان أن جميع الاتصالات تستخدم أقوى معايير التشفير.
Rate Limiting هو أحد الأدوات الأساسية لحماية API من الهجمات والإساءة. الفكرة بسيطة: تحديد عدد الطلبات التي يمكن للمستخدم إرسالها في فترة زمنية معينة. بدون Rate Limiting، يمكن لأي مستخدم أو مهاجم إرسال آلاف الطلبات في الثانية الواحدة، مما يؤدي إلى إبطاء السيرفر أو إسقاطه تماماً. في إحدى المرات، تعرضت واجهة برمجة تطبيقات كنت أعمل عليها لهجوم DDoS بسيط، حيث أرسل المهاجم ١٠٠٠ طلب في الثانية الواحدة. النتيجة؟ السيرفر أصبح بطيئاً جداً لدرجة أن المستخدمين الحقيقيين لم يتمكنوا من الوصول إلى الخدمة. الحل كان تطبيق Rate Limiting باستخدام Redis لتحديد عدد الطلبات لكل عنوان IP.
هناك عدة طرق لتطبيق Rate Limiting، لكن الأكثر شيوعاً هي استخدام خوارزمية Token Bucket أو Leaky Bucket. في خوارزمية Token Bucket، يتم إضافة عدد محدد من الـ Tokens إلى دلو افتراضي في فترات زمنية محددة. كل طلب يستهلك Token، وإذا لم يكن هناك Tokens متاحة، يتم رفض الطلب. هذه الخوارزمية تسمح بتحديد معدل ثابت للطلبات مع السماح ببعض الانفجارات (Bursts) في عدد الطلبات. في مشروع آخر، استخدمنا Token Bucket مع Redis لتطبيق Rate Limiting على مستوى المستخدم، حيث سمحنا لكل مستخدم بإرسال ١٠٠ طلب في الدقيقة الواحدة، مع السماح بانفجار يصل إلى ٢٠ طلب في الثانية الواحدة.
// مثال على تطبيق Rate Limiting باستخدام Token Bucket و Redis في Node.js
const redis = require('redis');
const { promisify } = require('util');
const client = redis.createClient({
host: 'localhost',
port: 6379
});
const getAsync = promisify(client.get).bind(client);
const setAsync = promisify(client.set).bind(client);
const incrAsync = promisify(client.incr).bind(client);
const expireAsync = promisify(client.expire).bind(client);
async function rateLimit(userId, maxRequests, windowSizeInSeconds) {
const key = `rate_limit:${userId}`;
const current = await getAsync(key);
if (current && parseInt(current) >= maxRequests) {
return false; // Rate limit exceeded
}
if (!current) {
await setAsync(key, 1, 'EX', windowSizeInSeconds);
} else {
await incrAsync(key);
}
return true; // Within rate limit
}
// مثال على استخدام Rate Limiting في Express middleware
const express = require('express');
const app = express();
app.use(async (req, res, next) => {
const userId = req.ip; // أو استخدم معرف المستخدم إذا كان مسجلاً دخول
const allowed = await rateLimit(userId, 100, 60); // 100 طلب في الدقيقة
if (!allowed) {
return res.status(429).json({ error: 'Too many requests' });
}
next();
});
app.get('/api/data', (req, res) => {
res.json({ message: 'Data retrieved successfully' });
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});أحد التحديات في تطبيق Rate Limiting هو التمييز بين المستخدمين الحقيقيين والهجمات الآلية. على سبيل المثال، إذا قمت بتطبيق Rate Limiting بناءً على عنوان IP فقط، فقد يؤثر ذلك على المستخدمين الحقيقيين الذين يتشاركون نفس عنوان IP، مثل المستخدمين في شبكة شركة أو جامعة. الحل هو استخدام مزيج من العوامل لتحديد الهوية، مثل عنوان IP، ومعرف المستخدم، ورؤوس الطلبات مثل User-Agent. في أحد المشاريع، استخدمنا مكتبة تسمى FingerprintJS لتوليد بصمة فريدة لكل مستخدم بناءً على متصفحه وجهازه، ثم استخدمنا هذه البصمة بالإضافة إلى عنوان IP لتحديد عدد الطلبات المسموح بها.
أيضاً، يمكن استخدام CAPTCHA كطبقة إضافية لحماية نقاط النهاية الحساسة. على سبيل المثال، إذا كان المستخدم يرسل عدداً كبيراً من الطلبات في فترة زمنية قصيرة، يمكنك عرض CAPTCHA للتحقق من أنه إنسان وليس برنامجاً آلياً. لكن يجب استخدام CAPTCHA بحذر، حيث يمكن أن يؤثر سلباً على تجربة المستخدم. في أحد التطبيقات، استخدمنا CAPTCHA فقط عندما يتجاوز المستخدم حداً معيناً من الطلبات، ولم نظهره للمستخدمين العاديين الذين يلتزمون بالحدود الطبيعية.
حتى مع تطبيق جميع طبقات الأمان، لا يمكنك ضمان أن نظامك محمي تماماً. الهجمات تتطور باستمرار، والمهاجمون يجدون طرقاً جديدة لاختراق الأنظمة. لهذا السبب، فإن المراقبة والتسجيل هما جزء أساسي من استراتيجية الأمان. بدون مراقبة فعالة، قد لا تكتشف الهجوم إلا بعد فوات الأوان. في إحدى الشركات، تعرضت واجهة برمجة التطبيقات لهجوم Brute Force على نقطة النهاية الخاصة بتسجيل الدخول. بفضل نظام المراقبة الذي قمنا بتطبيقه، تمكنا من اكتشاف الهجوم في وقت مبكر وإيقافه قبل أن ينجح المهاجم في اختراق أي حساب.
أدوات المراقبة مثل Prometheus و Grafana يمكن أن تساعد في تتبع أداء API واكتشاف الأنماط غير الطبيعية. على سبيل المثال، إذا لاحظت زيادة مفاجئة في عدد الطلبات على نقطة نهاية معينة، فقد يكون ذلك علامة على هجوم DDoS. أيضاً، يمكن استخدام أدوات مثل ELK Stack (Elasticsearch, Logstash, Kibana) لتحليل سجلات الطلبات والبحث عن أنماط مشبوهة. في مشروع آخر، استخدمنا ELK Stack لتحليل سجلات الطلبات، واكتشفنا أن أحد المهاجمين كان يحاول استغلال ثغرة SQL Injection من خلال إرسال طلبات تحتوي على رموز خاصة في معلمات URL.
# مثال على استخدام Logstash لتصفية السجلات المشبوهة
input {
file {
path => "/var/log/api/requests.log"
start_position => "beginning"
}
}
filter {
grok {
match => { "message" => "%{TIMESTAMP_ISO8601:timestamp} %{IP:client_ip} %{WORD:method} %{URIPATHPARAM:request} %{NUMBER:response_code} %{NUMBER:response_time}" }
}
# فلترة الطلبات التي تحتوي على رموز خاصة (محاولة SQL Injection)
if [request] =~ /(\%27|\'|\-\-|\%23|\#)/ {
mutate {
add_tag => ["sql_injection_attempt"]
}
}
# فلترة الطلبات التي تأتي من نفس IP بشكل متكرر (Brute Force)
if [client_ip] {
aggregate {
task_id => "%{client_ip}"
code => "map['count'] ||= 0; map['count'] += 1;"
push_previous_map_as_event => true
timeout => 60
}
if [count] > 100 {
mutate {
add_tag => ["brute_force_attempt"]
}
}
}
}
output {
elasticsearch {
hosts => ["localhost:9200"]
index => "api-logs-%{+YYYY.MM.dd}"
}
# إرسال تنبيهات عند اكتشاف هجمات
if "sql_injection_attempt" in [tags] or "brute_force_attempt" in [tags] {
email {
from => "alerts@your-api.com"
subject => "Security Alert: Suspicious Activity Detected"
to => "security-team@your-api.com"
body => "Suspicious activity detected: %{message}"
via => "smtp"
opti> {
address => "smtp.your-api.com"
port => 587
user_name => "alerts@your-api.com"
password => "your-email-password"
authentication => "plain"
enable_starttls_auto => true
}
}
}
}الأمان ليس شيئاً تقوم به مرة واحدة وتنساه. التهديدات تتطور باستمرار، والأدوات التي تستخدمها اليوم قد تصبح غير آمنة غداً. لهذا السبب، يجب أن تكون عملية الأمان مستمرة. على سبيل المثال، في عام ٢٠١٧، تم اكتشاف ثغرة في بروتوكول WPA2 تسمى KRACK، والتي سمحت للمهاجمين باختراق شبكات الواي فاي. الشركات التي لم تقم بتحديث أنظمتها لتصحيح هذه الثغرة تعرضت لهجمات خطيرة. نفس الشيء ينطبق على واجهات برمجة التطبيقات. إذا كنت تستخدم مكتبة معينة لتشفير البيانات، يجب أن تتأكد من تحديثها بانتظام لتصحيح أي ثغرات جديدة.
أيضاً، يجب إجراء اختبارات أمان منتظمة لواجهة برمجة التطبيقات الخاصة بك. أدوات مثل OWASP ZAP و Burp Suite يمكن أن تساعد في اكتشاف الثغرات قبل أن يستغلها المهاجمون. في إحدى المرات، استخدمنا OWASP ZAP لفحص واجهة برمجة تطبيقات كنا نعمل عليها، واكتشفنا ثغرة تسمح بحقن البيانات في الاستجابات، مما قد يؤدي إلى هجمات XSS. بفضل الاكتشاف المبكر، تمكنا من إصلاح الثغرة قبل أن تصل إلى بيئة الإنتاج.
الأمان ليس شيئاً تضيفه في النهاية، بل هو جزء أساسي من تصميم النظام منذ البداية. إذا انتظرت حتى تتعرض للهجوم لتأمين واجهة برمجة التطبيقات الخاصة بك، فقد يكون الأوان قد فات. ابدأ بتطبيق المصادقة القوية مثل JWT مع Refresh Tokens، واستخدم HTTPS دائماً، وقم بتشفير البيانات سواء أثناء النقل أو التخزين. لا تنسَ تطبيق Rate Limiting لحماية السيرفر من الهجمات والإساءة، وراقب النظام باستمرار لاكتشاف أي نشاط مشبوه. وأخيراً، كن دائماً على اطلاع بأحدث التهديدات والتقنيات الأمنية، لأن الأمان هو عملية مستمرة وليست مهمة واحدة.
في المرة القادمة التي تعمل فيها على واجهة برمجة تطبيقات جديدة، اسأل نفسك: هل هذا النظام آمن بما يكفي لتحمل هجوم من هكر محترف؟ إذا كانت الإجابة غير واضحة، فقد حان الوقت لإعادة النظر في استراتيجيتك الأمنية.