سیستم Login یکی از حساسترین قسمتهای هر برنامه وب است.
حتی اگر رمزهای عبور کاربران را با الگوریتم مناسبی مانند password_hash() ذخیره کنیم، هنوز یک مشکل مهم وجود دارد:
مهاجم میتواند به جای حمله مستقیم به دیتابیس، بارها فرم Login را امتحان کند.
برای مثال:
admin@example.com
123456
اگر اشتباه بود:
admin@example.com
12345678
و سپس:
admin@example.com
password
و همین روند میتواند هزاران یا میلیونها بار تکرار شود.
به این نوع حمله، Brute Force Attack گفته میشود.
در این مقاله بررسی میکنیم Brute Force چیست، چه تفاوتی با Password Spraying و Credential Stuffing دارد و چگونه میتوانیم در PHP از سیستم Login در برابر این حملات محافظت کنیم.
# Brute Force چیست؟
Brute Force یا حمله جستوجوی فراگیر، روشی است که در آن مهاجم تعداد زیادی ترکیب احتمالی را امتحان میکند تا اطلاعات صحیح احراز هویت را پیدا کند.
در سادهترین حالت:
Username
+
Password
بارها امتحان میشود.
مثلاً:
123456
12345678
password
qwerty
admin123
...
هدف این است که یکی از ترکیبها درست باشد.
# یک مثال ساده از Brute Force
فرض کنید Endpoint زیر داریم:
POST /login
و اطلاعات زیر ارسال میشود:
email
password
مهاجم ممکن است درخواستهای متوالی ارسال کند:
Request 1 → password123
Request 2 → 12345678
Request 3 → qwerty123
Request 4 → admin123
Request 5 → password
اگر سیستم هیچ محدودیتی نداشته باشد، مهاجم میتواند تعداد بسیار زیادی تلاش انجام دهد.
اینجاست که Rate Limiting اهمیت پیدا میکند.
# آیا Brute Force فقط برای Password است؟
خیر.
همین مفهوم میتواند روی موارد دیگری نیز استفاده شود:
- OTP
- PIN
- Recovery Code
- Security Code
- API Key
- بعضی Tokenها
مثلاً اگر OTP شش رقمی باشد، تعداد حالتها محدود است:
000000
000001
000002
...
999999
بنابراین OTP نیز باید محدودیت تعداد تلاش داشته باشد.
# Brute Force با Password Spraying چه تفاوتی دارد؟
این دو حمله شبیه یکدیگر هستند اما دقیقاً یکسان نیستند.
Brute Force
مهاجم معمولاً روی یک حساب، تعداد زیادی رمز مختلف را امتحان میکند.
مثلاً:
user@example.com
password1
password2
password3
password4
...
Password Spraying
در Password Spraying مهاجم معمولاً یک یا چند رمز رایج را روی تعداد زیادی حساب امتحان میکند.
مثلاً:
123456
روی:
user1@example.com
user2@example.com
user3@example.com
user4@example.com
این روش میتواند از بعضی محدودیتهای ساده Login عبور کند؛ مثلاً سیستمی که فقط تعداد تلاش برای هر حساب را محدود کرده است.
# Credential Stuffing چیست؟
Credential Stuffing با Brute Force تفاوت مهمی دارد.
در Credential Stuffing مهاجم از Username/Passwordهایی استفاده میکند که قبلاً از یک سرویس دیگر به دست آمدهاند.
مثلاً:
user@example.com
password123
ممکن است در یک سایت دیگر افشا شده باشد.
مهاجم همان اطلاعات را روی سایت شما امتحان میکند.
بنابراین حتی اگر کاربران رمز عبور نسبتاً خوبی داشته باشند، استفاده مجدد از رمز عبور در سرویسهای مختلف میتواند خطرناک باشد.
# مقایسه سه نوع حمله
| نوع حمله | روش |
|---|---|
| Brute Force | امتحان تعداد زیادی رمز روی یک حساب |
| Password Spraying | امتحان یک/چند رمز روی تعداد زیادی حساب |
| Credential Stuffing | استفاده از اطلاعات افشاشده قبلی |
در نتیجه فقط یک نوع Rate Limit برای محافظت از Login کافی نیست.
# چرا Brute Force در PHP خطرناک است؟
اگر Login شما چنین ساختاری داشته باشد:
if ($password === $user['password']) {
login();
}
مشکلات زیادی دارد.
اما حتی اگر از:
password_verify()
استفاده کنید، هنوز مهاجم میتواند تعداد زیادی درخواست ارسال کند.
password_verify() برای مقایسه امن Password Hash طراحی شده است، اما وظیفه آن جلوگیری از ارسال تعداد زیاد Login نیست.
بنابراین باید دو موضوع را جدا ببینیم:
Password Hashing
+
Login Protection
# Password Hashing در برابر Brute Force
رمز عبور کاربران نباید به صورت Plain Text ذخیره شود.
روش صحیح:
$hash = password_hash(
$password,
PASSWORD_DEFAULT
);
و هنگام Login:
if (password_verify($password, $hash)) {
// Login successful
}
این کار امنیت ذخیرهسازی Password را افزایش میدهد.
اما اگر مهاجم بتواند هزاران Login واقعی به سیستم ارسال کند، همچنان باید Rate Limiting داشته باشیم.
# یک Login ساده و امنتر با PDO
فرض کنیم جدول کاربران به شکل زیر است:
CREATE TABLE users (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
created_at DATETIME NOT NULL
);
برای پیدا کردن کاربر:
$stmt = $pdo->prepare(
'SELECT id, email, password
FROM users
WHERE email = ?
LIMIT 1'
);
$stmt->execute([$email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
سپس:
if (
$user &&
password_verify($password, $user['password'])
) {
// Login successful
}
اما این هنوز یک Login کامل و مقاوم در برابر Brute Force نیست.
# اضافه کردن Rate Limiting
فرض کنیم از مقاله قبلی یک Rate Limiter داریم.
میتوانیم قبل از بررسی Password این محدودیت را اعمال کنیم:
$ip = $_SERVER['REMOTE_ADDR'];
$key = 'login:ip:' . $ip;
if (!checkRateLimit($pdo, $key, 20, 900)) {
http_response_code(429);
exit('Too many login attempts.');
}
این یعنی:
20 attempts
در 15 دقیقه
برای هر IP
مقدار واقعی باید بر اساس رفتار سیستم تعیین شود.
# چرا Rate Limiting فقط بر اساس IP کافی نیست؟
فرض کنید مهاجم از چندین IP استفاده کند:
IP 1 → 5 requests
IP 2 → 5 requests
IP 3 → 5 requests
IP 4 → 5 requests
اگر فقط IP را محدود کرده باشیم، مهاجم میتواند محدودیت را تا حدی دور بزند.
از طرف دیگر، اگر فقط حساب کاربر را محدود کنیم، مهاجم میتواند باعث قفل شدن حساب قربانی شود.
بنابراین بهتر است چند لایه محدودیت داشته باشیم.
# Rate Limiting ترکیبی
برای Login میتوانیم چنین ساختاری داشته باشیم:
IP
+
Email
+
IP + Email
مثلاً:
20 attempts / IP / 15 minutes
5 attempts / Email / 15 minutes
5 attempts / IP + Email / 15 minutes
این اعداد فقط نمونه هستند.
# یک نکته مهم درباره User Enumeration
فرض کنید Login چنین پاسخهایی داشته باشد:
Email does not exist
یا:
Wrong password
این تفاوت میتواند به مهاجم اطلاعات بدهد.
برای مثال اگر پاسخ اول برای Email نامعتبر و پاسخ دوم برای Email معتبر باشد، مهاجم میتواند Emailهای موجود در سیستم را شناسایی کند.
بهتر است پاسخ عمومی داشته باشیم:
ایمیل یا رمز عبور صحیح نیست.
# Login امن در PHP
یک ساختار ساده:
<?php
session_start();
$email = strtolower(trim($_POST['email'] ?? ''));
$password = $_POST['password'] ?? '';
if ($email === '' || $password === '') {
exit('Invalid credentials.');
}
$ip = $_SERVER['REMOTE_ADDR'];
$ipKey = 'login:ip:' . $ip;
if (!checkRateLimit($pdo, $ipKey, 20, 900)) {
http_response_code(429);
exit('Too many requests.');
}
$emailKey = 'login:email:' . hash(
'sha256',
$email
);
if (!checkRateLimit($pdo, $emailKey, 5, 900)) {
http_response_code(429);
exit('Too many requests.');
}
$stmt = $pdo->prepare(
'SELECT id, email, password
FROM users
WHERE email = ?
LIMIT 1'
);
$stmt->execute([$email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if (
!$user ||
!password_verify($password, $user['password'])
) {
exit('Email or password is incorrect.');
}
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
echo 'Login successful.';
در این مثال چند لایه دفاعی داریم:
Input Validation
↓
IP Rate Limit
↓
Email Rate Limit
↓
Database Query
↓
password_verify()
↓
Session Regeneration
# چرا Session را بعد از Login تغییر دهیم؟
بعد از احراز هویت موفق، بهتر است Session ID تغییر کند:
session_regenerate_id(true);
این کار در برابر Session Fixation اهمیت دارد.
بنابراین Login امن فقط مربوط به Password نیست.
باید موارد زیر نیز در نظر گرفته شوند:
- Password Hashing
- Rate Limiting
- Session Security
- CSRF Protection
- 2FA
# Progressive Delay در برابر Brute Force
یکی از روشهای دیگر برای کاهش سرعت حملات، افزایش تدریجی تأخیر است.
مثلاً:
تلاش اول → 0 ثانیه
تلاش دوم → 1 ثانیه
تلاش سوم → 2 ثانیه
تلاش چهارم → 5 ثانیه
تلاش پنجم → 10 ثانیه
این کار سرعت Automation را کاهش میدهد.
البته Delay نباید به شکلی طراحی شود که منابع سرور را به صورت خطرناک مصرف کند.
# آیا باید حساب کاربر را Lock کنیم؟
قفل کردن حساب بعد از تعداد مشخصی Login ناموفق ساده به نظر میرسد:
5 failed attempts
↓
Account Locked
اما مشکل مهمی دارد.
مهاجم میتواند عمداً Login اشتباه برای حساب قربانی ارسال کند و باعث Lock شدن آن شود.
بنابراین:
Account Lockout
باید با احتیاط طراحی شود.
در بسیاری از سیستمها Temporary Cooldown یا Rate Limiting تدریجی میتواند از Lockout دائمی مناسبتر باشد.
# Temporary Lockout
به جای:
Account permanently locked
میتوان از:
Account temporarily blocked
استفاده کرد.
مثلاً:
5 failed attempts
↓
2 minutes cooldown
و در صورت تکرار:
10 failed attempts
↓
10 minutes cooldown
این مقادیر فقط مثال هستند.
# محافظت در برابر Password Spraying
در Password Spraying مهاجم ممکن است یک Password را روی تعداد زیادی حساب امتحان کند.
اگر Rate Limit فقط روی هر حساب باشد، ممکن است این حمله بهخوبی شناسایی نشود.
به همین دلیل میتوان علاوه بر Limit حساب، رفتار IP را نیز کنترل کرد.
مثلاً:
Per Account Limit
+
Per IP Limit
در سیستمهای بزرگتر حتی میتوان رفتارهای مشکوک را با سیستم Monitoring تحلیل کرد.
# محافظت در برابر Credential Stuffing
برای Credential Stuffing، موارد زیر اهمیت دارند:
۱. Passwordهای قوی
کاربر نباید از رمزهای قابل حدس استفاده کند.
۲. جلوگیری از Reuse
استفاده از یک Password در چند سرویس ریسک را افزایش میدهد.
۳. MFA
حتی اگر Password لو رفته باشد، عامل دوم میتواند یک لایه دفاعی اضافه ایجاد کند.
۴. Rate Limiting
تعداد تلاشها باید محدود شود.
۵. Monitoring
الگوهای غیرعادی Login باید بررسی شوند.
# استفاده از 2FA در برابر Brute Force
2FA میتواند یک لایه دفاعی مهم باشد.
مثلاً:
Email + Password
↓
OTP
↓
Login Success
در این حالت مهاجم علاوه بر Password به عامل دوم نیز نیاز دارد.
اما OTP خودش باید Rate Limited باشد.
برای مثال:
OTP Verify
↓
Maximum attempts
↓
Temporary block
پس Rate Limiting و 2FA مکمل یکدیگر هستند.
# Rate Limiting برای OTP
فرض کنیم OTP شش رقمی است.
نباید اجازه دهیم کاربر یا مهاجم بدون محدودیت آن را امتحان کند.
مثلاً:
$key = 'otp:verify:' . $userId;
if (!checkRateLimit($pdo, $key, 5, 300)) {
http_response_code(429);
exit('Too many attempts.');
}
بعد:
if (!hash_equals($otpHash, hash('sha256', $otp))) {
exit('Invalid code.');
}
در یک سیستم کامل، OTP باید:
- Expiration داشته باشد.
- پس از استفاده باطل شود.
- تعداد تلاش داشته باشد.
- Rate Limit داشته باشد.
- به User/Session مناسب متصل باشد.
# Brute Force روی Password Reset
Password Reset نیز میتواند هدف حمله قرار بگیرد.
مثلاً مهاجم مرتب درخواست:
POST /forgot-password
ارسال کند.
این کار میتواند باعث:
- ارسال ایمیل زیاد
- مصرف منابع
- Spam شدن Inbox
- سوءاستفاده از سرویس Email
شود.
بنابراین Password Reset نیز باید Rate Limited باشد.
# پاسخ مناسب Password Reset
نباید بگوییم:
این Email در سیستم وجود ندارد.
بهتر است:
اگر حسابی با این اطلاعات وجود داشته باشد،
دستورالعمل بازیابی رمز عبور ارسال خواهد شد.
این موضوع علاوه بر امنیت Login، در جلوگیری از User Enumeration نیز اهمیت دارد.
# Brute Force روی API
APIها نیز ممکن است هدف حمله باشند.
مثلاً:
POST /api/login
باید محدودیت مخصوص API داشته باشد.
میتوان بر اساس:
API Key
+
User
+
IP
محدودیت اعمال کرد.
در صورت عبور از Limit:
http_response_code(429);
header('Retry-After: 60');
echo json_encode([
'message' => 'Too Many Requests'
]);
# استفاده از Redis برای سیستمهای بزرگتر
در سیستم چندسروره:
Load Balancer
↓
Server 1
Server 2
Server 3
↓
Redis
Rate Limit باید بین سرورها مشترک باشد.
Redis برای Counterهای سریع و دارای TTL گزینه مناسبی است.
مثلاً:
$key = 'brute-force:login:' . $ip;
$count = $redis->incr($key);
if ($count === 1) {
$redis->expire($key, 900);
}
if ($count > 20) {
http_response_code(429);
exit('Too many requests.');
}
در Production باید Atomic بودن عملیات، TTL و مدیریت خطاهای Redis نیز بررسی شود.
# اگر Redis از دسترس خارج شود چه کنیم؟
این تصمیم به معماری پروژه بستگی دارد.
نباید بدون طراحی قبلی بگوییم:
Redis unavailable → allow everything
چون در Login حساس ممکن است باعث شود سیستم بدون Rate Limit کار کند.
از طرف دیگر:
Redis unavailable → block all users
هم میتواند باعث از کار افتادن Login شود.
بنابراین باید یک سیاست مشخص برای Fail Open / Fail Closed بر اساس حساسیت Endpoint و معماری سیستم تعریف شود.
# CAPTCHA چه نقشی دارد؟
CAPTCHA میتواند در شرایط مشکوک به عنوان یک لایه اضافه استفاده شود.
مثلاً:
Normal Login
↓
Rate Limit
↓
Risk Detection
↓
CAPTCHA
اما CAPTCHA نباید جایگزین:
Password Hashing
Rate Limiting
2FA
Session Security
شود.
# Logging در مقابله با Brute Force
سیستم باید تلاشهای غیرعادی را قابل مشاهده کند.
برای مثال میتوان اطلاعات زیر را ثبت کرد:
timestamp
IP
endpoint
user identifier
success/failure
response status
اما نباید اطلاعات حساس مانند:
password
OTP
reset token
access token
را در Log ذخیره کنیم.
# چه زمانی یک IP مشکوک است؟
صرفاً تعداد Login ناموفق همیشه به معنی حمله نیست.
مثلاً:
کاربر رمز خود را فراموش کرده
ممکن است چند بار تلاش کند.
اما الگوهایی مانند:
صدها درخواست در مدت کوتاه
تلاش برای تعداد زیادی حساب
تغییر مداوم IP
تعداد زیادی Username مختلف
میتوانند برای سیستم Monitoring مهم باشند.
تشخیص حمله بهتر است بر اساس الگوی رفتار انجام شود، نه فقط یک شرط ساده.
# دفاع چندلایه در برابر Brute Force
یک سیستم Login امن بهتر است چند لایه دفاع داشته باشد:
Login
↓
Input Validation
↓
Rate Limiting
↓
Password Verification
↓
Session Security
↓
2FA
↓
Monitoring
هیچکدام از این لایهها به تنهایی کافی نیستند.
# اشتباهات رایج در مقابله با Brute Force
فقط به Password قوی اعتماد کردن
Password قوی مهم است اما تعداد درخواستها نیز باید کنترل شود.
فقط IP را محدود کردن
IP میتواند بین چند کاربر مشترک باشد یا مهاجم از چند IP استفاده کند.
Lock کردن دائمی حساب
این روش میتواند برای مهاجم امکان ایجاد DoS روی حساب قربانی فراهم کند.
نداشتن Rate Limit برای OTP
OTP نیز میتواند هدف Brute Force قرار بگیرد.
تفاوت پاسخ برای Username موجود و ناموجود
این کار میتواند User Enumeration ایجاد کند.
اعتماد مستقیم به X-Forwarded-For
Headerهای Proxy باید فقط در معماری Trusted Proxy استفاده شوند.
ذخیره رمز عبور به صورت Plain Text
هرگز Password را به صورت متن ساده ذخیره نکنید.
از:
password_hash()
و:
password_verify()
استفاده کنید.
# یک معماری پیشنهادی برای Login امن
برای یک پروژه PHP میتوان چنین ساختاری داشت:
Client
↓
HTTPS Request
↓
CSRF Check
↓
Rate Limiter
↓
Generic Login Response
↓
Password Verification
↓
Session Regeneration
↓
2FA
↓
Login Success
و در کنار آن:
Monitoring
↑
|
Login Events
# چکلیست مقابله با Brute Force در PHP
قبل از انتشار سیستم Login این موارد را بررسی کنید:
- [ ] Passwordها با
password_hash()ذخیره میشوند. - [ ] Login از
password_verify()استفاده میکند. - [ ] Rate Limiting روی Login وجود دارد.
- [ ] Rate Limit فقط بر اساس IP نیست.
- [ ] تعداد تلاشهای یک حساب نیز کنترل میشود.
- [ ] Password Spraying در طراحی در نظر گرفته شده است.
- [ ] Credential Stuffing در نظر گرفته شده است.
- [ ] OTP دارای Rate Limit است.
- [ ] Password Reset دارای Rate Limit است.
- [ ] پاسخ Login باعث User Enumeration نمیشود.
- [ ] Session بعد از Login Regenerate میشود.
- [ ] HTTPS فعال است.
- [ ] 2FA برای حسابهای حساس قابل استفاده است.
- [ ] Lockout باعث DoS روی حساب نمیشود.
- [ ] Loginهای مشکوک Log و Monitor میشوند.
- [ ] Password و OTP در Log ذخیره نمیشوند.
- [ ] IP واقعی فقط از Proxyهای مورد اعتماد استخراج میشود.
- [ ] برای APIها پاسخ 429 در نظر گرفته شده است.
# پرسشهای متداول
Brute Force در PHP چیست؟
Brute Force حملهای است که در آن مهاجم تعداد زیادی ترکیب احتمالی Username و Password را امتحان میکند تا اطلاعات صحیح Login را پیدا کند.
آیا password_hash() جلوی Brute Force را میگیرد؟
خیر. password_hash() برای ذخیره امن Password طراحی شده است. جلوگیری از تلاشهای متعدد Login نیازمند Rate Limiting و سایر کنترلهای امنیتی است.
آیا Rate Limiting به تنهایی کافی است؟
خیر. Rate Limiting یکی از لایههای دفاعی است و باید در کنار Password Hashing، Session Security، 2FA و Monitoring استفاده شود.
Password Spraying چه تفاوتی با Brute Force دارد؟
در Brute Force معمولاً تعداد زیادی Password روی یک حساب امتحان میشود، اما در Password Spraying یک یا چند Password روی تعداد زیادی حساب امتحان میشود.
Credential Stuffing چیست؟
Credential Stuffing استفاده از Username و Passwordهایی است که قبلاً در یک سرویس دیگر افشا شدهاند و امتحان کردن آنها در سرویس فعلی.
آیا Account Lockout بهترین راه مقابله با Brute Force است؟
Lockout میتواند مفید باشد، اما اگر بدون طراحی مناسب استفاده شود، مهاجم میتواند عمداً حساب کاربران را قفل کند. Rate Limiting و Cooldown موقت میتوانند در کنار یا به جای Lockout دائمی استفاده شوند.
# جمعبندی
Brute Force یکی از حملات مهم علیه سیستمهای Login است، اما دفاع در برابر آن فقط با انتخاب Password قوی انجام نمیشود.
یک سیستم PHP امن باید چند لایه داشته باشد:
Password Hashing
+
Rate Limiting
+
Session Security
+
2FA
+
CSRF Protection
+
Monitoring
همچنین باید بین سه مفهوم مهم تفاوت قائل شویم:
Brute Force
Password Spraying
Credential Stuffing
چون هرکدام الگوی متفاوتی دارند و اگر Rate Limiting فقط بر اساس یک معیار مانند IP طراحی شود، ممکن است تمام سناریوها را پوشش ندهد.
برای Login، ترکیب محدودیتهای IP، حساب کاربری و در صورت نیاز IP + شناسه کاربر میتواند کنترل دقیقتری ایجاد کند. در کنار آن، پاسخهای عمومی برای جلوگیری از User Enumeration و Session Regeneration بعد از Login نیز اهمیت دارند.
در نهایت باید به یاد داشته باشیم که هیچ مکانیزم منفردی امنیت Login را تضمین نمیکند. امنیت واقعی حاصل ترکیب چند لایه دفاعی و طراحی صحیح کل فرآیند احراز هویت است.
مقاله پیشنهادی بعدی
بعد از Brute Force، موضوع مناسبی برای ادامه کلاستر Security Headers در PHP است؛ در آن مقاله میتوانیم Headerهایی مانند Content-Security-Policy، Strict-Transport-Security، X-Content-Type-Options، Referrer-Policy و Permissions-Policy را با مثال عملی PHP بررسی کنیم.





