المتصفّح يرسل كعكاتك مع أي طلب إلى نطاقك، أيًّا كان مَن تسبّب في الطلب. فصفحةٌ على موقع آخر قد تحوي نموذجًا يُرسِل إلى موقعك، وإن كان زائرك مسجَّل الدخول وصل الطلب موثَّقًا — وبدا في سجلّاتك مشروعًا تمامًا.
ماذا يستطيع أن يفعل
- يغيّر عنوان بريد، ثم يطلب إعادة تعيين كلمة المرور إلى العنوان الجديد.
- أو يضع طلبًا، أو يحوّل شيئًا، أو يحذف شيئًا.
- وكل ما يفعله موقعك بـPOST وجلسة.
الرمز
قيمة عشوائية تُحفَظ في الجلسة وتتكرّر في النموذج. وصفحةٌ على موقع آخر لا تقدر على قراءة جلستك، فلا تقدر على معرفة القيمة.
// when rendering the form
$_SESSION["csrf"] = $_SESSION["csrf"] ?? bin2hex(random_bytes(32));
?>
<input type="hidden" name="csrf" value="<?= htmlspecialchars($_SESSION['csrf']) ?>">
// when handling it
if (!hash_equals($_SESSION["csrf"] ?? "", $_POST["csrf"] ?? "")) {
http_response_code(400); exit;
}
وقارن بـhash_equals لا بـ==. فالمقارنة العادية تتوقّف عند أوّل حرف مختلف، والزمن الذي تستغرقه يُسرّب كم كان من الرمز صحيحًا.
وخاصّية الكعكة
session.cookie_samesite = Lax
القيمة SameSite=Lax تقول للمتصفّح ألّا يرسل الكعكة إطلاقًا مع POST عابر للمواقع، فيتوقّف هذا الصنف قبل أن تراه شفرتك. وهي طبقة ثانية لا بديلٌ: تحمي حيث يكون المتصفّح حديثًا ومنضبطًا فقط.
وGET لا يجوز أن يغيّر شيئًا
رابطٌ يحذف شيئًا هو CSRF ينتظر أن يقع — والأسوأ أن زاحفًا أو معاينة رابط ستُشغّله من تلقاء نفسها. وكل ما يغيّر حالةً هو POST برمز.
وأيّ إطار عمل يحمل هذا مدمجًا ومُفعَّلًا. فإن كنت أطفأته لمسار واحد كي يعمل شيء، فذلك المسار هو الذي تنظر فيه.