Kodax

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 "طلب مشبوه", 403

4. طلب إعادة كلمة المرور

للعمليات الحساسة جداً.

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
اختبر فهمك