CSRF

Cross-Site Request Forgery (CSRF) হলো ওয়েব সিকিউরিটির অন্যতম একটি গুরুতর দুর্বলতা। এটি এমন এক ধরনের আক্রমণ যেখানে একজন লগইন করা ইউজারের ব্রাউজারকে ম্যানিপুলেট করে তার অজান্তেই ম্যালিসিয়াস রিকোয়েস্ট পাঠানো হয়। ওয়েব অ্যাপ্লিকেশন যেহেতু ইউজারের সেশন কুকি (Session Cookie) দিয়ে রিকোয়েস্ট ভ্যালিডেট করে, তাই এই আক্রমণটি বেশ ভয়ংকর হতে পারে।

CSRF আক্রমণের প্রক্রিয়া কীভাবে কাজ করে?

ধরা যাক, আপনি একটি ব্যাংকিং অ্যাপ্লিকেশন ব্যবহার করছেন:

১. আপনি আপনার ব্রাউজার থেকে ব্যাংকিং সাইটে লগইন করলেন। সার্ভার আপনার ব্রাউজারে একটি সেশন কুকি (Session Cookie) সেভ করে দিল। ২. লগইন থাকা অবস্থাতেই আপনি অন্য একটি ট্যাবে আক্রমণকারীর তৈরি করা কোনো ওয়েবসাইটে (যেমন: কোনো লোভনীয় অফারের সাইট) প্রবেশ করলেন। ৩. ওই ম্যালিসিয়াস সাইটে একটি লুকানো স্ক্রিপ্ট বা ফর্ম আছে, যা ব্যাকগ্রাউন্ডে আপনার ব্যাংকিং সাইটের ফান্ড ট্রান্সফার এন্ডপয়েন্টে একটি POST রিকোয়েস্ট পাঠায়। ৪. ব্রাউজারের ডিফল্ট আচরণ হলো, কোনো ডোমেইনে রিকোয়েস্ট পাঠানোর সময় ওই ডোমেইনের সংরক্ষিত কুকি স্বয়ংক্রিয়ভাবে রিকোয়েস্টের সাথে যুক্ত করে দেওয়া। ৫. ব্যাংকিং সার্ভার রিকোয়েস্টটি পেয়ে কুকি ভ্যালিডেট করে এবং ভাবে আপনি নিজেই রিকোয়েস্টটি পাঠিয়েছেন। ফলে আপনার অজান্তেই টাকা ট্রান্সফার হয়ে যায়।

কীভাবে CSRF প্রতিরোধ করা যায়?

CSRF প্রতিরোধের জন্য একাধিক সিকিউরিটি লেয়ার ব্যবহার করা উচিত। নিচে সবচেয়ে কার্যকরী কৌশলগুলো দেওয়া হলো:

১. Anti-CSRF Token (Synchronizer Token Pattern)

এটি সবচেয়ে জনপ্রিয় পদ্ধতি। সার্ভার ইউজারের সেশনের জন্য একটি ইউনিক, র‍্যান্ডম এবং প্রেডিক্ট করা যায় না এমন টোকেন জেনারেট করে। ইউজার যখন কোনো ফর্ম সাবমিট করে বা API কল করে, তখন রিকোয়েস্ট বডি বা হেডারে এই টোকেনটি পাঠাতে হয়। অ্যাটাকার অন্য ডোমেইন থেকে রিকোয়েস্ট পাঠাতে পারলেও, ব্রাউজারের Same-Origin Policy (SOP) এর কারণে সে ইউজারের পেজ থেকে ওই টোকেনটি রিড করতে পারে না। ফলে টোকেন ছাড়া পাঠানো রিকোয়েস্ট সার্ভার রিজেক্ট করে দেয়।

২. SameSite Cookies

কুকি সেট করার সময় SameSite অ্যাট্রিবিউট ব্যবহার করা আধুনিক ব্রাউজারগুলোতে CSRF প্রতিরোধের অন্যতম সেরা উপায়।

  • SameSite=Strict: ব্রাউজার অন্য কোনো ডোমেইন থেকে আসা কোনো রিকোয়েস্টের সাথেই কুকি পাঠাবে না।

  • SameSite=Lax: টপ-লেভেল নেভিগেশন (যেমন: লিংকে ক্লিক করে সাইটে আসা) ছাড়া অন্য কোনো থার্ড-পার্টি রিকোয়েস্টে (যেমন: লুকানো ফর্ম সাবমিট বা AJAX কল) কুকি পাঠানো হবে না।

৩. Double Submit Cookie Pattern

এই পদ্ধতিতে সার্ভার একটি র‍্যান্ডম ভ্যালু কুকিতে সেট করে দেয় এবং ইউজারকে একই ভ্যালু ফর্ম প্যারামিটার বা কাস্টম হেডার হিসেবে পাঠাতে হয়। সার্ভারে সেশন স্টোর করার প্রয়োজন হয় না, বরং রিকোয়েস্ট আসার পর সার্ভার শুধু চেক করে যে কুকির ভ্যালু এবং ফর্মের ভ্যালু এক কি না।

৪. Origin ও Referer Header Check

রিকোয়েস্ট কোন ডোমেইন থেকে আসছে, তা HTTP-এর Origin বা Referer হেডার চেক করে নিশ্চিত হওয়া যায়। রিকোয়েস্ট যদি নিজস্ব ডোমেইন ছাড়া অন্য কোনো আননোন ডোমেইন থেকে আসে, তবে তা ব্লক করে দেওয়া হয়।

Python কোড ইমপ্লিমেন্টেশন

Django এর মতো FastAPI তে ডিফল্টভাবে CSRF প্রোটেকশন থাকে না। API বেইজড আর্কিটেকচারে (যেখানে ফ্রন্টএন্ড React বা Next.js থাকে) CSRF প্রতিরোধের জন্য সবচেয়ে জনপ্রিয় পদ্ধতি হলো Double Submit Cookie Pattern।

এই পদ্ধতিতে সার্ভার একটি টোকেন জেনারেট করে কুকিতে সেট করে দেয়। ফ্রন্টএন্ড সেই কুকিটি রিড করে এবং পরবর্তীতে যেকোনো POST/PUT রিকোয়েস্ট পাঠানোর সময় একটি কাস্টম হেডারে (যেমন: X-CSRF-Token) টোকেনটি যুক্ত করে দেয়। সার্ভার তখন কুকির টোকেন এবং হেডারের টোকেন মিলিয়ে দেখে।

নিচে FastAPI-এর Dependency Injection ব্যবহার করে এর একটি ক্লিন ইমপ্লিমেন্টেশন দেওয়া হলো:

import secrets
from fastapi import FastAPI, Depends, HTTPException, Request, Response, status

app = FastAPI()

CSRF_HEADER_NAME = "X-CSRF-Token"
CSRF_COOKIE_NAME = "csrf_token"

def generate_csrf_token() -> str:
    # Generates a secure URL-safe random string
    return secrets.token_urlsafe(32)

def verify_csrf_token(request: Request):
    # Retrieve tokens from cookie and header
    cookie_token = request.cookies.get(CSRF_COOKIE_NAME)
    header_token = request.headers.get(CSRF_HEADER_NAME)

    if not cookie_token or not header_token:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="CSRF token is missing in cookie or header."
        )

    # Use secrets.compare_digest to prevent timing attacks
    if not secrets.compare_digest(cookie_token, header_token):
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="CSRF token mismatch. Request blocked."
        )

@app.get("/api/v1/get-csrf-token")
def get_csrf_token(response: Response):
    token = generate_csrf_token()
    
    # httponly=False is required so that frontend JS (React/Next.js) can read the cookie
    # and send it back via the custom header (X-CSRF-Token)
    response.set_cookie(
        key=CSRF_COOKIE_NAME,
        value=token,
        httponly=False, 
        samesite="lax",
        secure=True # Set to False in local development if not using HTTPS
    )
    return {"message": "CSRF token generated and set in cookie."}

# The dependency verify_csrf_token will run before executing the route logic
@app.post("/api/v1/transfer", dependencies=[Depends(verify_csrf_token)])
def transfer_funds(amount: float):
    # If the execution reaches this point, it means CSRF validation has passed
    return {
        "status": "success",
        "message": f"Transfer of ${amount} initiated securely."
    }

এই ইমপ্লিমেন্টেশনের গুরুত্বপূর্ণ দিকগুলো:

১. Dependency Injection: FastAPI-এর Depends ব্যবহার করায় রাউটের মূল লজিকটি পরিষ্কার থাকে এবং ভ্যালিডেশনের কাজ ব্যাকগ্রাউন্ডে সম্পন্ন হয়। তুমি চাইলে এটি গ্লোবাল মিডেলওয়্যার হিসেবেও যুক্ত করতে পারো। ২. Timing Attack Prevention: দুটি স্ট্রিং মেলানোর জন্য সাধারণ == অপারেটর ব্যবহার না করে secrets.compare_digest() ব্যবহার করা হয়েছে, যা সিকিউরিটির জন্য একটি বেস্ট প্র্যাকটিস। এটি টাইমিং অ্যাটাক (Timing Attack) থেকে রক্ষা করে। ৩. HttpOnly=False: কুকি সেট করার সময় এটি False রাখা হয়েছে, কারণ ডাবল সাবমিট কুকি প্যাটার্নে ক্লায়েন্ট-সাইড জাভাস্ক্রিপ্টের কুকিটি রিড করে হেডারে যুক্ত করার প্রয়োজন হয়। তবে কুকিটি অবশ্যই SameSite="lax" বা strict হতে হবে।

সিস্টেম ডিজাইনে শুধুমাত্র একটি কৌশলের ওপর নির্ভর না করে Defense-in-Depth অ্যাপ্রোচ ফলো করা ভালো। অর্থাৎ, Anti-CSRF Token-এর পাশাপাশি কুকিতে SameSite=Lax বা Strict ব্যবহার করলে অ্যাপ্লিকেশনটি CSRF-এর বিরুদ্ধে পুরোপুরি সুরক্ষিত থাকবে।