CSRF یکی از آسیبپذیریهای مهم برنامههای تحت وب است که میتواند باعث شود یک درخواست ناخواسته با استفاده از اعتبار احراز هویت کاربر ارسال شود.
نام CSRF مخفف:
Cross-Site Request Forgery
است.
در یک حمله CSRF، مشکل لزوماً این نیست که مهاجم رمز عبور کاربر را میداند یا Session کاربر را مستقیماً سرقت کرده است؛ بلکه ممکن است مرورگر کاربرِ واردشده به سایت، در شرایط خاص، یک درخواست را به سایت ارسال کند و مرورگر نیز اطلاعات احراز هویت مربوط به آن سایت را همراه درخواست بفرستد.
برای مثال تصور کنید کاربر در یک سایت بانکی یا پنل مدیریتی وارد حساب خود شده است. اگر یک عملیات حساس فقط با یک درخواست ساده انجام شود و سایت مکانیزم مناسبی برای تشخیص درخواست واقعی از درخواست جعلی نداشته باشد، امکان سوءاستفاده وجود دارد.
در این مقاله یاد میگیریم CSRF چیست، چرا Session و Cookie در این حمله اهمیت دارند، CSRF Token چگونه کار میکند و چگونه در PHP یک سیستم محافظت در برابر CSRF طراحی کنیم.
# CSRF چیست؟
CSRF یا Cross-Site Request Forgery به حملهای گفته میشود که در آن مهاجم تلاش میکند مرورگر یک کاربر احراز هویتشده را وادار به ارسال یک درخواست ناخواسته به یک سایت دیگر کند.
برای درک بهتر، این سناریو را در نظر بگیرید:
کاربر وارد سایت میشود
↓
Session ایجاد میشود
↓
کاربر احراز هویت شده است
↓
یک سایت یا صفحه دیگر درخواست خاصی ایجاد میکند
↓
مرورگر درخواست را به سایت اصلی ارسال میکند
اگر سایت اصلی صرفاً به Cookie یا Session موجود برای تشخیص کاربر اعتماد کند، ممکن است نتواند متوجه شود این درخواست واقعاً توسط خود کاربر از داخل سایت ارسال شده یا از یک منبع خارجی ایجاد شده است.
# یک مثال ساده از CSRF
فرض کنید در یک پنل مدیریت، حذف کاربر با چنین URLای انجام میشود:
/admin/delete-user.php?id=15
و PHP نیز چیزی شبیه این دارد:
$id = $_GET['id'];
$stmt = $pdo->prepare(
"DELETE FROM users WHERE id = :id"
);
$stmt->execute([
'id' => $id
]);
فرض کنیم کاربر مدیر قبلاً وارد پنل شده و Session معتبر دارد.
اگر عملیات حساس فقط بر اساس همین درخواست انجام شود، طراحی مناسبی برای یک عملیات مدیریتی نیست.
مشکل SQL Injection در اینجا با Prepared Statement برطرف شده است، اما مسئله دیگری وجود دارد:
آیا این درخواست واقعاً از داخل پنل مدیریت ارسال شده است؟
این دقیقاً جایی است که CSRF اهمیت پیدا میکند.
# چرا Session در CSRF مهم است؟
بسیاری از برنامههای PHP از Session برای احراز هویت استفاده میکنند.
مثلاً:
session_start();
$_SESSION['user_id'] = 15;
بعد در درخواستهای بعدی، مرورگر Cookie مربوط به Session را ارسال میکند.
بنابراین سرور میتواند تشخیص دهد درخواست مربوط به کدام کاربر است.
اما صرفاً اینکه درخواست متعلق به یک کاربر احراز هویتشده باشد، به این معنی نیست که خود کاربر آن درخواست خاص را عمداً ایجاد کرده است.
این تفاوت مهم است:
Authentication
=
چه کسی کاربر است؟
CSRF Protection
=
آیا این درخواست واقعاً از مسیر مورد انتظار برنامه ایجاد شده است؟
# CSRF چگونه اتفاق میافتد؟
یک سناریوی ساده را در نظر بگیرید.
کاربر در سایت:
example.com
وارد حساب خود شده است.
Session او فعال است.
سپس در یک صفحه دیگر قرار میگیرد که باعث ایجاد درخواست به example.com میشود.
اگر endpoint حساس سایت فقط Session را بررسی کند:
if (!isset($_SESSION['user_id'])) {
exit('Unauthorized');
}
درخواست ممکن است همچنان از دید برنامه متعلق به همان کاربر باشد.
بنابراین احراز هویت بهتنهایی دفاع کامل در برابر CSRF نیست.
# تفاوت CSRF و XSS چیست؟
CSRF و XSS هر دو از آسیبپذیریهای مهم برنامههای وب هستند، اما یکسان نیستند.
XSS
تمرکز XSS روی اجرای محتوای ناخواسته در Context مرورگر است.
مثلاً:
Input
↓
HTML/DOM
↓
Browser
CSRF
تمرکز CSRF روی ارسال درخواست ناخواسته با اعتبار کاربر است.
Attacker-controlled page
↓
Browser
↓
Authenticated Request
↓
Target Website
به همین دلیل روشهای دفاعی این دو نیز متفاوت هستند.
# آیا استفاده از HTTPS جلوی CSRF را میگیرد؟
خیر.
HTTPS برای رمزنگاری ارتباط و محافظت از داده در مسیر ارتباط بسیار مهم است، اما بهتنهایی مکانیزم تشخیص درخواست جعلی CSRF نیست.
ممکن است یک درخواست CSRF نیز از طریق HTTPS ارسال شود.
بنابراین:
HTTPS
+
CSRF Protection
دو موضوع متفاوت هستند.
# CSRF Token چیست؟
یکی از روشهای رایج جلوگیری از CSRF استفاده از CSRF Token است.
CSRF Token یک مقدار تصادفی و غیرقابل پیشبینی است که سرور ایجاد میکند و در Session کاربر نگه میدارد.
سپس هنگام ارسال فرم، Token نیز همراه درخواست ارسال میشود.
سرور مقدار دریافتشده را با مقدار ذخیرهشده در Session مقایسه میکند.
اگر Token صحیح نباشد، درخواست رد میشود.
ساختار کلی:
Server
↓
Generate Token
↓
Store in Session
↓
Send Token to Form
↓
User submits form
↓
Token sent to Server
↓
Compare Tokens
↓
Accept / Reject
# ساخت CSRF Token در PHP
ابتدا Session را فعال میکنیم:
session_start();
سپس اگر Token وجود نداشت، آن را ایجاد میکنیم:
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(
random_bytes(32)
);
}
در اینجا:
random_bytes(32)
یک مقدار تصادفی رمزنگاریشده تولید میکند.
سپس:
bin2hex()
آن را به یک رشته hexadecimal تبدیل میکند.
# نمایش CSRF Token در فرم
فرض کنید فرم حذف یا تغییر اطلاعات داریم.
میتوانیم Token را به شکل Hidden Input قرار دهیم:
<form method="POST" action="/profile/update.php">
<input
type="hidden"
name="csrf_token"
value="<?= htmlspecialchars(
$_SESSION['csrf_token'],
ENT_QUOTES,
'UTF-8'
) ?>"
>
<input
type="text"
name="name"
>
<button type="submit">
ذخیره
</button>
</form>
کاربر معمولاً Token را نمیبیند، اما هنگام Submit شدن فرم همراه درخواست ارسال میشود.
# بررسی CSRF Token در PHP
در فایل پردازش فرم:
session_start();
$token = $_POST['csrf_token'] ?? '';
if (
empty($_SESSION['csrf_token']) ||
!hash_equals($_SESSION['csrf_token'], $token)
) {
http_response_code(403);
exit('درخواست نامعتبر است.');
}
تابع مهم اینجا:
hash_equals()
است.
این تابع برای مقایسه امن رشتهها طراحی شده و برای مقایسه Token انتخاب مناسبی است.
# چرا از == استفاده نکنیم؟
ممکن است کدهایی مانند این ببینید:
if ($_SESSION['csrf_token'] == $_POST['csrf_token']) {
// ...
}
برای مقایسه Token بهتر است از:
hash_equals()
استفاده کنیم:
if (
hash_equals(
$_SESSION['csrf_token'],
$_POST['csrf_token']
)
) {
// Token valid
}
به این ترتیب مقایسه Token به شکل مناسبتری انجام میشود.
# ساخت یک CSRF Helper در PHP
اگر در پروژه چندین فرم داریم، بهتر است منطق CSRF را در یک Helper قرار دهیم.
مثلاً:
function csrf_token(): string
{
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(
random_bytes(32)
);
}
return $_SESSION['csrf_token'];
}
سپس:
function verify_csrf_token(?string $token): bool
{
if (
empty($_SESSION['csrf_token']) ||
empty($token)
) {
return false;
}
return hash_equals(
$_SESSION['csrf_token'],
$token
);
}
حالا در فرم:
<input
type="hidden"
name="csrf_token"
value="<?= htmlspecialchars(
csrf_token(),
ENT_QUOTES,
'UTF-8'
) ?>"
>
و در Endpoint:
if (!verify_csrf_token($_POST['csrf_token'] ?? null)) {
http_response_code(403);
exit('درخواست نامعتبر است.');
}
# یک مثال کامل از فرم امن
فایل:
profile.php
میتواند شامل این باشد:
<?php
session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(
random_bytes(32)
);
}
?>
<form method="POST" action="update-profile.php">
<input
type="hidden"
name="csrf_token"
value="<?= htmlspecialchars(
$_SESSION['csrf_token'],
ENT_QUOTES,
'UTF-8'
) ?>"
>
<label>
نام:
</label>
<input
type="text"
name="name"
>
<button type="submit">
ذخیره
</button>
</form>
در فایل:
update-profile.php
میتوانیم Token را بررسی کنیم:
<?php
session_start();
$token = $_POST['csrf_token'] ?? '';
if (
empty($_SESSION['csrf_token']) ||
!hash_equals(
$_SESSION['csrf_token'],
$token
)
) {
http_response_code(403);
exit('درخواست نامعتبر است.');
}
$name = $_POST['name'] ?? '';
// ادامه پردازش اطلاعات...
# آیا CSRF Token باید در دیتابیس ذخیره شود؟
معمولاً نیازی نیست.
در یک پیادهسازی ساده میتوان Token را در Session ذخیره کرد:
$_SESSION['csrf_token']
و هنگام دریافت درخواست آن را با Token ارسالشده مقایسه کرد.
در نتیجه:
CSRF Token
↓
Session
↓
Form
↓
POST Request
↓
Comparison
کافی است.
# آیا برای هر فرم باید Token متفاوت داشته باشیم؟
الزاماً نه.
یک روش رایج این است که برای هر Session یک Token ایجاد کنیم و فرمهای مختلف همان Token را ارسال کنند.
اما بسته به معماری و سطح امنیت موردنیاز، میتوان Tokenهای متفاوت یا مکانیزمهای دقیقتر نیز طراحی کرد.
برای اکثر پروژههای معمول PHP، یک Token امن در سطح Session میتواند الگوی ساده و قابل استفادهای باشد.
# آیا بعد از هر درخواست باید CSRF Token را تغییر دهیم؟
الزاماً خیر.
میتوان یک Token را در طول عمر Session نگه داشت.
مثلاً:
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(
random_bytes(32)
);
}
این Token تا زمانی که Session ادامه دارد استفاده میشود.
در بعضی معماریها Tokenها بهصورت چرخشی یا وابسته به درخواست مدیریت میشوند، اما پیچیدهتر شدن مکانیزم همیشه به معنی امنیت بهتر یا طراحی مناسبتر نیست.
# CSRF در درخواستهای AJAX
اگر Frontend شما با JavaScript به PHP درخواست میفرستد، میتوانید Token را در Header ارسال کنید.
مثلاً:
fetch('/api/profile/update.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({
name: 'Saeed'
})
});
در PHP:
$token = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
سپس:
if (
empty($_SESSION['csrf_token']) ||
!hash_equals(
$_SESSION['csrf_token'],
$token
)
) {
http_response_code(403);
exit('Invalid CSRF token.');
}
این الگو در برنامههای SPA و APIهای مبتنی بر Session کاربرد دارد.
# CSRF در REST API
اینجا باید یک نکته مهم را در نظر بگیریم.
CSRF بیشتر زمانی اهمیت پیدا میکند که احراز هویت مرورگر بهصورت خودکار مانند Cookie یا Session همراه درخواست ارسال شود.
اگر API از یک Token در:
Authorization: Bearer ...
استفاده کند و این Token بهصورت خودکار توسط مرورگر برای درخواستهای Cross-Site اضافه نشود، مدل تهدید متفاوت است.
بنابراین نمیتوان گفت:
> هر API حتماً به CSRF Token نیاز دارد.
باید نوع Authentication و نحوه ارسال Credentialها را بررسی کرد.
# SameSite Cookie چیست؟
یکی دیگر از مکانیزمهای مهم در کاهش ریسک CSRF، ویژگی:
SameSite
در Cookie است.
در PHP میتوان هنگام ایجاد Cookie آن را تنظیم کرد:
setcookie(
'session_token',
$token,
[
'expires' => time() + 3600,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax'
]
);
مقادیر رایج SameSite عبارتاند از:
Strict
Lax
None
# SameSite=Strict
در حالت Strict مرورگر محدودیت بیشتری برای ارسال Cookie در درخواستهای Cross-Site اعمال میکند.
مثلاً:
'samesite' => 'Strict'
این گزینه میتواند محافظت بیشتری در برابر برخی سناریوهای Cross-Site ایجاد کند، اما ممکن است روی بعضی جریانهای طبیعی ورود یا Navigation سایت اثر بگذارد.
# SameSite=Lax
در بسیاری از سناریوهای وب، Lax انتخاب متداولی است:
'samesite' => 'Lax'
این حالت تعادل متفاوتی میان امنیت و سازگاری ایجاد میکند.
اما باید معماری واقعی سایت و نحوه استفاده از Cookie را در نظر گرفت.
# SameSite=None
برای:
'samesite' => 'None'
Cookie میتواند در Contextهای Cross-Site ارسال شود.
در مرورگرهای مدرن، استفاده از SameSite=None نیازمند:
'secure' => true
است.
بنابراین:
[
'secure' => true,
'samesite' => 'None'
]
معمولاً فقط در سناریوهایی استفاده میشود که واقعاً به Cross-Site Cookie نیاز دارید.
# آیا SameSite جایگزین CSRF Token است؟
بهتر است آن را یک لایه دفاعی در نظر بگیریم، نه اینکه بدون بررسی معماری، همیشه آن را جایگزین Token کنیم.
برای عملیات حساس میتوان از ترکیب چند مکانیزم استفاده کرد:
CSRF Token
+
SameSite Cookie
+
Origin/Referer Validation
+
Authentication
البته انتخاب دقیق این موارد به معماری برنامه بستگی دارد.
# بررسی Origin و Referer
برای بعضی درخواستهای حساس میتوان Headerهایی مانند:
Origin
و:
Referer
را نیز بررسی کرد.
مثلاً:
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if ($origin !== 'https://example.com') {
http_response_code(403);
exit('Invalid origin.');
}
اما نباید بدون درنظر گرفتن Proxy، CDN، معماری چنددامنهای و رفتار مرورگرها، یک مقدار ثابت را کورکورانه اعمال کنیم.
این روش بهتر است بهعنوان یک لایه دفاعی تکمیلی استفاده شود.
# CSRF برای چه درخواستهایی اهمیت بیشتری دارد؟
CSRF بیشتر برای عملیاتهایی اهمیت دارد که باعث تغییر وضعیت یا ایجاد اثر جانبی میشوند.
مثلاً:
تغییر رمز عبور
تغییر ایمیل
حذف حساب
حذف کاربر
تغییر تنظیمات
ثبت سفارش
انتقال وجه
تغییر سطح دسترسی
ایجاد یا حذف اطلاعات
در مقابل، درخواستهای صرفاً خواندنی معمولاً مدل تهدید متفاوتی دارند.
بهطور کلی، عملیات تغییر وضعیت بهتر است با روشهای مناسب محافظت شوند.
# آیا GET برای حذف اطلاعات مناسب است؟
بهتر است عملیات تغییر وضعیت مانند:
DELETE
UPDATE
CREATE
را با GET انجام ندهیم.
مثلاً این طراحی:
/delete-user.php?id=15
برای حذف کاربر مناسب نیست.
بهتر است عملیات تغییر وضعیت با روش HTTP مناسب و کنترل CSRF انجام شود.
مثلاً:
<form method="POST" action="/delete-user.php">
و داخل فرم:
<input
type="hidden"
name="csrf_token"
value="..."
>
سپس سمت سرور Token بررسی شود.
# چرا استفاده از GET برای عملیات حساس مشکلساز است؟
GET باید تا حد امکان برای دریافت منابع و اطلاعات استفاده شود.
وقتی یک عملیات حساس را به GET متصل میکنیم، مشکلات مختلفی ایجاد میشود.
برای مثال ممکن است URL توسط:
- مرورگر
- لینک
- Crawler
- Prefetch
- سیستمهای مختلف
دیده یا درخواست شود.
به همین دلیل عملیات تغییر وضعیت را نباید صرفاً به بازدید از یک URL GET وابسته کنیم.
# CSRF و Session Regeneration
در سیستمهای احراز هویت بهتر است هنگام Login موفق، Session ID نیز طبق طراحی امن برنامه Regenerate شود.
مثلاً:
session_regenerate_id(true);
سپس اطلاعات کاربر را در Session قرار میدهیم:
$_SESSION['user_id'] = $user['id'];
این موضوع بیشتر به امنیت Session مربوط است، اما در طراحی کلی Authentication و CSRF باید در کنار یکدیگر در نظر گرفته شوند.
# CSRF و Logout
حتی عملیات Logout نیز میتواند نیازمند بررسی طراحی باشد.
اگر Logout صرفاً یک عملیات ساده و کمخطر باشد، معماریهای مختلفی وجود دارد؛ اما اگر endpointهای تغییر وضعیت شما به CSRF Token نیاز دارند، Logout نیز باید بر اساس همان سیاست امنیتی بررسی شود.
اصل مهم این است که برای تمام Endpointهای تغییر وضعیت، یک سیاست مشخص داشته باشیم.
# CSRF در Laravel
اگر از PHP خام به Laravel بروید، معمولاً نیازی نیست سیستم CSRF را از ابتدا خودتان پیادهسازی کنید.
Laravel برای فرمهای Web خود مکانیزم CSRF دارد.
مثلاً در Blade:
<form method="POST" action="/profile">
@csrf
<input
type="text"
name="name"
>
<button type="submit">
ذخیره
</button>
</form>
دستور:
@csrf
Token موردنیاز را در فرم قرار میدهد.
در نتیجه لازم نیست در هر پروژه Laravel دوباره Helper مربوط به تولید و بررسی Token را از صفر بنویسید.
# CSRF در WordPress
WordPress نیز برای بسیاری از عملیات مدیریتی و فرمها از مکانیزمی به نام Nonce استفاده میکند.
Nonce در WordPress بخشی از سیستم امنیتی درخواستها است و برای بررسی Intent و جلوگیری از درخواستهای ناخواسته در بسیاری از سناریوها استفاده میشود.
اما نباید آن را دقیقاً معادل یک Session CSRF Token سفارشی در PHP خام بدانیم؛ پیادهسازی و چرخه عمر آن در WordPress متفاوت است.
برای توسعه افزونه WordPress باید از APIهای امنیتی خود WordPress استفاده کرد، نه اینکه سیستم امنیتی جداگانهای بدون دلیل ایجاد کنیم.
# اشتباهات رایج در جلوگیری از CSRF
اشتباه اول: اعتماد به Session بهتنهایی
این:
if (isset($_SESSION['user_id'])) {
// Request accepted
}
بهتنهایی محافظت CSRF نیست.
Session فقط نشان میدهد کاربر احراز هویت شده است.
اشتباه دوم: استفاده از یک Token ثابت برای تمام کاربران
نباید یک Token ثابت را در سورس پروژه قرار دهیم:
$csrf_token = '123456';
Token باید غیرقابل پیشبینی و مرتبط با Session یا درخواست باشد.
اشتباه سوم: تولید Token با rand()
استفاده از روشهای ضعیف برای تولید Token مناسب نیست.
برای Token امنیتی از:
random_bytes()
استفاده کنید.
اشتباه چهارم: مقایسه Token با روش نامناسب
بهجای:
if ($token == $_SESSION['csrf_token']) {
از:
hash_equals(
$_SESSION['csrf_token'],
$token
);
استفاده کنید.
اشتباه پنجم: استفاده از CSRF Token در URL
بهتر است Tokenهای CSRF را در Query String قرار ندهیم:
?action=delete&csrf_token=...
برای فرمها استفاده از POST و قرار دادن Token در Body معمولاً مناسبتر است.
اشتباه ششم: تصور اینکه HTTPS بهتنهایی کافی است
HTTPS بسیار مهم است، اما جایگزین CSRF Protection نیست.
# یک ساختار پیشنهادی برای پروژه PHP
اگر پروژه PHP شما چندین فرم دارد، میتوانید ساختار سادهای مانند این داشته باشید:
project/
│
├── public/
│ ├── index.php
│ ├── login.php
│ └── profile.php
│
├── app/
│ ├── helpers/
│ │ └── csrf.php
│ │
│ └── controllers/
│
└── config/
در:
app/helpers/csrf.php
منطق مربوط به Token قرار بگیرد:
<?php
function csrf_token(): string
{
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(
random_bytes(32)
);
}
return $_SESSION['csrf_token'];
}
function verify_csrf_token(?string $token): bool
{
if (
empty($_SESSION['csrf_token']) ||
empty($token)
) {
return false;
}
return hash_equals(
$_SESSION['csrf_token'],
$token
);
}
و در فرم:
<input
type="hidden"
name="csrf_token"
value="<?= htmlspecialchars(
csrf_token(),
ENT_QUOTES,
'UTF-8'
) ?>"
>
و در Controller:
if (
!verify_csrf_token(
$_POST['csrf_token'] ?? null
)
) {
http_response_code(403);
exit('درخواست نامعتبر است.');
}
این ساختار را میتوان بعداً با Middleware و معماری حرفهایتر توسعه داد.
# چکلیست جلوگیری از CSRF در PHP
قبل از انتشار یک پروژه PHP این موارد را بررسی کنید:
- [ ] تمام عملیات حساس شناسایی شدهاند.
- [ ] عملیات تغییر وضعیت با GET انجام نمیشوند.
- [ ] برای فرمهای حساس CSRF Token استفاده شده است.
- [ ] Token با
random_bytes()تولید میشود. - [ ] Token در Session نگهداری میشود.
- [ ] Token هنگام دریافت درخواست با
hash_equals()بررسی میشود. - [ ] درخواست نامعتبر با HTTP Status مناسب مانند
403رد میشود. - [ ] Cookieهای Session تنظیمات امنیتی مناسبی دارند.
- [ ] ویژگی
SameSiteمتناسب با معماری سایت تنظیم شده است. - [ ] Cookieهای حساس در HTTPS با
Secureاستفاده میشوند. - [ ] Cookieهای حساس در صورت نیاز
HttpOnlyهستند. - [ ] برای APIها نوع Authentication بررسی شده است.
- [ ] در صورت نیاز Origin یا Referer نیز بهعنوان لایه دفاعی بررسی شده است.
- [ ] Session ID هنگام Login به شکل مناسب Regenerate میشود.
- [ ] سیستم CSRF در Frameworkهایی مانند Laravel و WordPress بهصورت صحیح استفاده میشود.
# جمعبندی
CSRF یکی از آسیبپذیریهای مهم برنامههای وب است که میتواند باعث شود مرورگر کاربر احراز هویتشده، درخواست ناخواستهای را به یک سایت ارسال کند.
نکته مهم این است که:
Authentication با CSRF Protection یکسان نیست.
Session میتواند مشخص کند کاربر چه کسی است، اما CSRF Token میتواند کمک کند سرور بررسی کند درخواست از مسیر مورد انتظار برنامه ایجاد شده است.
یکی از روشهای رایج در PHP استفاده از Token تصادفی است:
$_SESSION['csrf_token'] = bin2hex(
random_bytes(32)
);
سپس Token در فرم قرار میگیرد:
<input
type="hidden"
name="csrf_token"
value="<?= htmlspecialchars(
$_SESSION['csrf_token'],
ENT_QUOTES,
'UTF-8'
) ?>"
>
و هنگام دریافت درخواست:
if (
!hash_equals(
$_SESSION['csrf_token'],
$_POST['csrf_token'] ?? ''
)
) {
http_response_code(403);
exit('درخواست نامعتبر است.');
}
در کنار CSRF Token میتوان از مکانیزمهایی مانند SameSite Cookie، Secure Cookie، HttpOnly، بررسی Origin و طراحی صحیح HTTP Methodها نیز استفاده کرد.
در Frameworkهایی مانند Laravel نیز بهتر است از مکانیزم داخلی Framework استفاده کنیم و سیستم CSRF را بدون نیاز دوباره پیادهسازی نکنیم.
در نهایت، CSRF تنها یکی از بخشهای امنیت برنامه است و باید در کنار موضوعاتی مانند XSS، SQL Injection، امنیت Session، احراز هویت، مدیریت رمز عبور و امنیت آپلود فایل بررسی شود.
# سوالات متداول
CSRF در PHP چیست؟
CSRF نوعی حمله است که در آن مهاجم تلاش میکند مرورگر یک کاربر احراز هویتشده را وادار به ارسال درخواست ناخواسته به یک سایت کند.
CSRF Token چیست؟
CSRF Token یک مقدار تصادفی و غیرقابل پیشبینی است که معمولاً به Session کاربر مرتبط میشود و همراه درخواست ارسال و در سمت سرور بررسی میشود.
آیا Session بهتنهایی از CSRF جلوگیری میکند؟
خیر. Session برای احراز هویت کاربر استفاده میشود، اما بهتنهایی ثابت نمیکند که درخواست توسط خود کاربر و از مسیر مورد انتظار برنامه ایجاد شده است.
آیا HTTPS جلوی CSRF را میگیرد؟
خیر. HTTPS امنیت ارتباط را افزایش میدهد، اما جایگزین مکانیزمهای CSRF نیست.
آیا SameSite جایگزین CSRF Token است؟
SameSite میتواند ریسک برخی حملات Cross-Site را کاهش دهد، اما اینکه آیا بهتنهایی کافی است یا خیر به معماری و نحوه Authentication برنامه بستگی دارد.
آیا GET میتواند باعث CSRF شود؟
بله، اگر یک عملیات حساس و تغییردهنده وضعیت به GET متصل شده باشد، طراحی میتواند در برابر درخواستهای ناخواسته آسیبپذیرتر شود. عملیات تغییر وضعیت بهتر است با HTTP Method مناسب و محافظت CSRF انجام شوند.
آیا REST API به CSRF Token نیاز دارد؟
بستگی به روش Authentication دارد. APIهایی که Credential آنها بهصورت خودکار توسط مرورگر مانند Cookie ارسال میشود، مدل تهدید متفاوتی نسبت به APIهایی دارند که از Bearer Token در Authorization Header استفاده میکنند.
آیا Laravel از CSRF پشتیبانی میکند؟
بله. Laravel برای درخواستهای Web مکانیزم داخلی CSRF دارد و در فرمهای Blade میتوان از @csrf استفاده کرد.
تفاوت CSRF و XSS چیست؟
XSS مربوط به اجرای یا تفسیر ناامن محتوای کنترلشده توسط کاربر در مرورگر است، در حالی که CSRF روی ارسال درخواست ناخواسته با اعتبار کاربر تمرکز دارد.
آیا CSRF Token باید در دیتابیس ذخیره شود؟
در یک پیادهسازی معمول PHP، میتوان Token را در Session نگه داشت و نیازی به ذخیره آن در دیتابیس نیست.





