CSRF - Cross-Site Request Forgery — الأمن المتقدم
CSRF - Cross-Site Request Forgery
ما هو CSRF؟
خداع المستخدم لتنفيذ إجراءات غير مقصودة على موقع هو مسجّل فيه.
مثال على الهجوم
السيناريو
1. المستخدم مسجّل دخول في موقع البنك
2. المهاجم يرسل رابط ضار
3. الرابط يحوّل أموال للمهاجم
4. المتصفح يرسل session cookie تلقائياً
كود الهجوم
<!-- في موقع المهاجم -->
<img src="https://bank.com/transfer?to=hacker&amount=10000" alt="" width="1" height="1">
<!-- أو نموذج يُرسل تلقائياً -->
<form action="https://bank.com/transfer" method="POST">
<input type="hidden" name="to" value="hacker">
<input type="hidden" name="amount" value="10000">
<button type="submit" hidden></button>
</form>
<script>document.forms[0].submit();</script>لماذا ينجح؟
- المتصفح يرسل cookies تلقائياً
- الموقع لا يتحقق من مصدر الطلب
الوقاية من CSRF
1. CSRF Token ✅
توكن سري في كل نموذج.
<form action="/transfer" method="POST">
<input type="hidden" name="csrf_token" value="abc123xyz">
<input type="text" name="to">
<input type="number" name="amount">
<button type="submit">تحويل</button>
</form>2. SameSite Cookie ✅
Set-Cookie: session=abc; SameSite=Strict- Strict: لا ترسل من مواقع أخرى أبداً
- Lax: ترسل فقط مع GET من روابط
- None: ترسل دائماً (غير آمن)
3. التحقق من Referer Header
from urllib.parse import urlparse
def is_same_origin(request, allowed_origin="https://mysite.com"):
# Origin أولاً ثم Referer، ونقارن الأصل كاملاً (المخطط + النطاق + المنفذ):
# Referer يحتوي المسار، فالمقارنة بالمساواة تفشل، و startswith تقبل mysite.com.evil.com
source = request.headers.get("Origin") or request.headers.get("Referer") or ""
parsed = urlparse(source)
return f"{parsed.scheme}://{parsed.netloc}" == allowed_origin
# في معالج الطلب:
# if not is_same_origin(request):
# return "طلب مشبوه", 4034. طلب إعادة كلمة المرور
للعمليات الحساسة جداً.
CSRF vs XSS
- XSS: حقن كود في الموقع نفسه
- CSRF: تنفيذ إجراءات من موقع آخر
# حماية من CSRF في Flask
from flask import Flask, session, request, render_template_string
import secrets
app = Flask(__name__)
app.secret_key = secrets.token_hex(16)
# ===== توليد CSRF Token =====
def generate_csrf_token():
"""توليد توكن CSRF فريد"""
if '_csrf_token' not in session:
session['_csrf_token'] = secrets.token_hex(16)
return session['_csrf_token']
# جعل الدالة متاحة في templates
app.jinja_env.globals['csrf_token'] = generate_csrf_token
# ===== التحقق من CSRF Token =====
def verify_csrf_token():
"""التحقق من توكن CSRF"""
token = request.form.get('csrf_token')
if not token or token != session.get('_csrf_token'):
return False
return True
# ===== صفحة بنموذج محمي =====
@app.route('/transfer', methods=['GET', 'POST'])
def transfer():
if request.method == 'POST':
# التحقق من CSRF Token
if not verify_csrf_token():
return "⚠️ CSRF Token غير صالح!", 403
# معالجة التحويل
to = request.form.get('to')
amount = request.form.get('amount')
return f"✅ تم تحويل {amount} إلى {to}"
# عرض النموذج
return render_template_string('''
<form method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input name="to" placeholder="المستلم" required>
<input name="amount" placeholder="المبلغ" required>
<button type="submit">تحويل</button>
</form>
''')
# ===== Decorator للحماية التلقائية =====
from functools import wraps
def csrf_protect(f):
"""Decorator للتحقق من CSRF في POST requests"""
@wraps(f)
def decorated_function(*args, **kwargs):
if request.method == 'POST':
if not verify_csrf_token():
return "CSRF Token غير صالح!", 403
return f(*args, **kwargs)
return decorated_function
@app.route('/delete_account', methods=['POST'])
@csrf_protect
def delete_account():
"""عملية حساسة محمية من CSRF"""
return "تم حذف الحساب!"
# ===== تعيين SameSite Cookie =====
@app.after_request
def set_secure_cookies(response):
"""إضافة خصائص أمان للـ cookies"""
# Session cookie
response.set_cookie(
'session',
session.get('_id', ''),
secure=True, # HTTPS فقط
httponly=True, # لا يمكن الوصول من JS
samesite='Strict' # منع CSRF
)
return response
# ===== فحص Referer (طبقة إضافية) =====
@app.before_request
def check_referer():
"""التحقق من Referer للعمليات الحساسة"""
if request.method == 'POST':
referer = request.headers.get('Referer', '')
# تحقق من أن الطلب من نفس الموقع
if not referer.startswith(request.host_url):
return "Referer مشبوه!", 403اختبر فهمك