محدودسازی ورود ناموفق در وردپرس یکی از مؤثرترین و در عین حال ساده‌ترین لایه‌های دفاعی در برابر حملات Brute Force و Credential Stuffing است. در حمله‌ی Brute Force، مهاجم با ارسال تعداد زیادی ترکیب نام کاربری و رمز عبور، تلاش می‌کند به پنل مدیریت دسترسی پیدا کند. اگر تعداد این تلاش‌ها محدود نشود، حتی یک رمز عبور متوسط نیز می‌تواند در چند ساعت شکسته شود. محدودسازی ورود ناموفق (Failed Login Limiting) با شمارش تلاش‌های ناموفق و اعمال محدودیت، پنجره‌ی بهره‌برداری را به‌شدت باریک می‌کند. در این راهنما، مکانیزم دقیق حمله، الگوهای محدودسازی، پیاده‌سازی در وردپرس، ابزارهای تخصصی و اشتباهات رایج بررسی می‌شود.

در پروژه‌ای که برای بازبینی امنیتی به دست ما رسید، سایت روزانه هزاران تلاش ورود ناموفق را ثبت می‌کرد، اما هیچ محدودیتی اعمال نمی‌شد. پس از فعال‌سازی Rate Limiting، تلاش‌ها به کمتر از صد در روز کاهش یافت. تجربه نشان داد که محدودسازی، نه یک تنظیم لوکس، بلکه یک ضرورت پایه است.

Brute Force چیست و چرا محدودسازی ضروری است

Brute Force یا Brute-force attack نوعی حمله است که در آن مهاجم با آزمون سیستماتیک ترکیب‌های ممکن، تلاش می‌کند اعتبارنامه‌ی صحیح را کشف کند. در بستر وب، این حمله معمولاً علیه فرم ورود انجام می‌شود و هدف آن، دسترسی به پنل مدیریت یا حساب کاربری است.

دلایل ضرورت محدودسازی:

  • سرعت بالا: ابزارهای خودکار می‌توانند هزاران درخواست در دقیقه ارسال کنند.
  • ضعف رمز عبور: بسیاری از کاربران از رمزهای کوتاه یا تکراری استفاده می‌کنند.
  • افشای نام کاربری: نام کاربری پیش‌فرض مانند admin نیمی از کار مهاجم را انجام می‌دهد.
  • عدم محدودیت پیش‌فرض: وردپرس به‌صورت پیش‌فرض هیچ محدودیتی روی تعداد تلاش‌های ورود اعمال نمی‌کند.

بدون محدودسازی، یک حمله‌ی Brute Force ساده می‌تواند در چند ساعت به موفقیت برسد. برای مطالعه‌ی جامع‌تر درباره‌ی این حمله، پست چرا حمله Brute Force هنوز یکی از جدی‌ترین تهدیدهای وردپرس است مرجع کاملی است.

مکانیزم دقیق حمله Brute Force

برای درک ضرورت محدودسازی، باید مکانیزم دقیق حمله را شناخت. فرآیند حمله:

  1. شناسایی فرم ورود: مهاجم آدرس wp-login.php یا xmlrpc.php را شناسایی می‌کند.
  2. شناسایی نام کاربری: با تکنیک‌های Enumeration (مانند پیام خطای متفاوت) نام کاربری معتبر را پیدا می‌کند.
  3. آزمون رمز عبور: با یک لیست رمز عبور (Dictionary) یا ترکیب‌های متوالی، تلاش می‌کند.
  4. تحلیل پاسخ: بر اساس کد وضعیت HTTP و پیام‌ها، موفقیت یا شکست را تشخیص می‌دهد.
  5. تکرار: فرآیند را تا یافتن رمز صحیح ادامه می‌دهد.

حمله‌ی Brute Force سه شکل اصلی دارد:

  • Simple Brute Force: آزمون ترکیب‌های متوالی.
  • Dictionary Attack: استفاده از لیست رمزهای رایج.
  • Credential Stuffing: استفاده از اعتبارنامه‌های افشاشده در سایت‌های دیگر.
  • Password Spraying: آزمون چند رمز رایج روی تعداد زیادی نام کاربری.

محدودسازی ورود ناموفق، نه تنها حمله را متوقف می‌کند، بلکه هزینه‌ی اقتصادی حمله را نیز بالا می‌برد. هرچه زمان حمله طولانی‌تر شود، احتمال شناسایی و انصراف مهاجم بیشتر می‌شود.

برای مطالعه‌ی راه‌های جامع دفاع، پست چگونه حملات brute force را در وردپرس دفع کنیم مرجع عملی کاملی است.

Rate Limiting: قلب محدودسازی

Rate Limiting یا محدودسازی نرخ، تعیین می‌کند که در یک بازه‌ی زمانی مشخص، چه تعداد درخواست مجاز است. در زمینه‌ی ورود، Rate Limiting تعداد تلاش‌های ناموفق در واحد زمان را محدود می‌کند.

معیارهای اصلی Rate Limiting:

معیار مزایا معایب مناسب برای
بر اساس IP ساده، مؤثر دور زدن با پروکسی سایت‌های کوچک و متوسط
بر اساس نام کاربری مقاوم در برابر توزیع قفل حساب کاربر واقعی سایت‌های با کاربران ثبت‌نام‌شده
ترکیب IP و نام کاربری دقت بالا پیچیدگی پیاده‌سازی سایت‌های بزرگ
بر اساس Subnet مقاوم در برابر توزیع IP احتمال قفل کاربران واقعی سازمان‌های بزرگ
Sliding Window دقیق، بدون Burst نیازمند ذخیره‌سازی بیشتر سایت‌های حساس
Token Bucket انعطاف‌پذیر، مناسب Burst پیچیدگی بیشتر سرویس‌های API

پیاده‌سازی Rate Limiting در PHP

// ذخیره تلاش‌های ناموفق در Transient وردپرس
function check_login_attempts($username) {
    $ip = $_SERVER["REMOTE_ADDR"];
    $key = "login_attempts_" . md5($ip . $username);
    $attempts = get_transient($key);

    if ($attempts === false) {
        $attempts = 0;
    }

    if ($attempts >= 5) {
        return new WP_Error(
            "too_many_attempts",
            "تعداد تلاش‌های ورود بیش از حد مجاز. لطفاً ۱۵ دقیقه بعد تلاش کنید."
        );
    }

    return true;
}
add_filter("authenticate", "check_login_attempts", 30, 1);

// ثبت تلاش ناموفق
function record_failed_login($username) {
    $ip = $_SERVER["REMOTE_ADDR"];
    $key = "login_attempts_" . md5($ip . $username);
    $attempts = (int) get_transient($key);
    set_transient($key, $attempts + 1, 15 * MINUTE_IN_SECONDS);
}
add_action("wp_login_failed", "record_failed_login");

// پاک‌سازی در ورود موفق
function clear_login_attempts($username, $user) {
    $ip = $_SERVER["REMOTE_ADDR"];
    $key = "login_attempts_" . md5($ip . $username);
    delete_transient($key);
}
add_action("wp_login", "clear_login_attempts", 10, 2);

این الگو، یک Rate Limiting ساده بر اساس ترکیب IP و نام کاربری پیاده می‌کند. برای مطالعه‌ی بیشتر درباره‌ی Transient، پست ترنزینت وردپرس چیست و چگونه کش هوشمند بدون افزونه بسازیم مفید است.

پیاده‌سازی Sliding Window

Sliding Window دقت بالاتری از Fixed Window دارد، چون پنجره‌ی زمانی به‌صورت پیوسته حرکت می‌کند:

function sliding_window_rate_limit($key, $max_attempts = 5, $window = 900) {
    $now = time();
    $attempts = get_transient($key);

    if (!is_array($attempts)) {
        $attempts = [];
    }

    // حذف تلاش‌های قدیمی‌تر از پنجره
    $attempts = array_filter($attempts, function($timestamp) use ($now, $window) {
        return ($now - $timestamp) < $window;
    });

    if (count($attempts) >= $max_attempts) {
        return false;
    }

    $attempts[] = $now;
    set_transient($key, $attempts, $window);
    return true;
}

قفل موقت حساب

قفل موقت حساب (Account Lockout) یکی از مؤثرترین راه‌های محدودسازی است، اما باید با دقت پیاده‌سازی شود تا کاربران واقعی قربانی نشوند.

انواع قفل:

  • Soft Lockout: قفل کوتاه‌مدت (۵ تا ۱۵ دقیقه) پس از چند تلاش ناموفق.
  • Hard Lockout: قفل طولانی‌مدت پس از تعداد بالای تلاش ناموفق.
  • Progressive Lockout: افزایش تدریجی زمان قفل با هر بار تلاش ناموفق.
  • Adaptive Lockout: قفل بر اساس ترکیب IP، کاربر و الگوی رفتاری.

پیاده‌سازی Progressive Lockout

function progressive_lockout($username) {
    $ip = $_SERVER["REMOTE_ADDR"];
    $key = "lockout_" . md5($ip . $username);
    $data = get_transient($key);

    if ($data === false) {
        $data = ["count" => 0, "until" => 0];
    }

    if ($data["until"] > time()) {
        $remaining = $data["until"] - time();
        return new WP_Error(
            "account_locked",
            sprintf("حساب تا %d ثانیه دیگر قفل است.", $remaining)
        );
    }

    return true;
}

function increase_lockout($username) {
    $ip = $_SERVER["REMOTE_ADDR"];
    $key = "lockout_" . md5($ip . $username);
    $data = get_transient($key);

    if ($data === false) {
        $data = ["count" => 0, "until" => 0];
    }

    $data["count"]++;

    // زمان قفل تصاعدی: 1min, 5min, 15min, 60min
    $delays = [60, 300, 900, 3600];
    $delay = $delays[min($data["count"] - 1, count($delays) - 1)];
    $data["until"] = time() + $delay;

    set_transient($key, $data, 86400);
}
add_action("wp_login_failed", "increase_lockout");

نکته‌ی مهم این است که قفل موقت نباید به‌عنوان یک مکانیزم دائمی استفاده شود، چون می‌تواند به یک حمله‌ی Denial of Service تبدیل شود. اگر مهاجم بتواند حساب‌های واقعی را قفل کند، خودش به یک آسیب‌پذیری تبدیل می‌شود.

CAPTCHA و جایگزین‌های مدرن

CAPTCHA یکی از روش‌های کلاسیک محدودسازی است، اما در سال‌های اخیر با چالش‌های جدی مواجه شده است:

  • CAPTCHA سنتی: تصاویر تحریف‌شده که با OCR و ML قابل حل هستند.
  • reCAPTCHA v2: چک‌باکس ساده، اما با Privacy Concerns.
  • reCAPTCHA v3: بدون تعامل کاربر، اما وابسته به رفتار.
  • hCaptcha: جایگزین با حریم خصوصی بهتر.
  • Cloudflare Turnstile: بدون تعامل، با حریم خصوصی بالا.
  • Proof of Work: چالش محاسباتی سبک، بدون تعامل کاربر.

توصیه‌ی عملی این است که CAPTCHA به‌عنوان لایه‌ی دوم استفاده شود، نه اولین لایه. Rate Limiting باید پیش از CAPTCHA عمل کند. برای مطالعه‌ی بیشتر، پست‌های کپچا و آینده‌ی مقابله با ربات‌ها و جایگزین‌های کپچا برای تجربه کاربری بهتر مفید هستند.

لاگ‌گیری و مانیتورینگ

محدودسازی بدون لاگ‌گیری، ناقص است. لاگ‌ها به شناسایی الگوهای حمله و تحلیل ریشه‌ای کمک می‌کنند.

اطلاعات ضروری برای هر تلاش ناموفق:

  • زمان تلاش (Timestamp).
  • آدرس IP.
  • نام کاربری واردشده.
  • User-Agent.
  • Referer.
  • نتیجه (موفق/ناموفق).

پیاده‌سازی لاگ‌گیری

function log_failed_login($username) {
    global $wpdb;
    $table = $wpdb->prefix . "failed_logins";

    $wpdb->insert($table, [
        "time" => current_time("mysql"),
        "ip" => $_SERVER["REMOTE_ADDR"],
        "username" => $username,
        "user_agent" => $_SERVER["HTTP_USER_AGENT"] ?? "",
        "referer" => $_SERVER["HTTP_REFERER"] ?? "",
    ]);
}
add_action("wp_login_failed", "log_failed_login");

برای مطالعه‌ی روش‌های بررسی لاگ، پست‌های چگونه لاگ حملات سایت را بررسی کنیم و لاگ‌های دیتابیس چگونه بررسی می‌شوند مفید هستند.

افزونه‌های تخصصی محدودسازی

افزونه‌های متعددی برای محدودسازی ورود ناموفق در وردپرس وجود دارد:

افزونه ویژگی‌ها مناسب برای
Wordfence Rate Limiting، Firewall، اسکن بدافزار سایت‌های همه‌منظوره
Limit Login Attempts Reloaded Rate Limiting، قفل موقت، CAPTCHA سایت‌های ساده
Solid Security Rate Limiting، 2FA، مدیریت نشست سایت‌های حساس
WP Cerber Rate Limiting، Anti-Spam، Audit Log سایت‌های متوسط
Shield Security Rate Limiting، 2FA، Anti-Bot سایت‌های حساس
Hide My WP Ghost مخفی‌سازی مسیر ورود، Rate Limiting سایت‌های امنیتی

برای مطالعه‌ی فهرست کامل‌تر، پست‌های بهترین افزونه‌های وردپرس برای افزایش امنیت ورود و بهترین افزونه‌های امنیتی وردپرس مراجع کاملی هستند.

پیاده‌سازی برنامه‌نویسی

علاوه بر افزونه‌ها، می‌توان محدودسازی را به‌صورت برنامه‌نویسی نیز پیاده کرد. مثال کامل با ترکیب Rate Limiting، قفل تصاعدی و لاگ‌گیری:

class WP_Login_Limiter {
    const MAX_ATTEMPTS = 5;
    const WINDOW = 900; // 15 minutes
    const LOCKOUT_STEPS = [60, 300, 900, 3600];

    public function __construct() {
        add_filter("authenticate", [$this, "check"], 30, 3);
        add_action("wp_login_failed", [$this, "record_failure"]);
        add_action("wp_login", [$this, "clear"], 10, 2);
    }

    private function get_key($username) {
        $ip = $_SERVER["REMOTE_ADDR"];
        return "login_limit_" . md5($ip . "|" . $username);
    }

    public function check($user, $username, $password) {
        if (empty($username)) {
            return $user;
        }

        $key = $this->get_key($username);
        $data = get_transient($key);

        if ($data === false) {
            return $user;
        }

        if (!empty($data["lock_until"]) && $data["lock_until"] > time()) {
            $remaining = $data["lock_until"] - time();
            return new WP_Error(
                "login_locked",
                sprintf("حساب تا %d ثانیه دیگر قفل است.", $remaining)
            );
        }

        if ($data["count"] >= self::MAX_ATTEMPTS) {
            $step = min($data["lockout_step"] ?? 0, count(self::LOCKOUT_STEPS) - 1);
            $delay = self::LOCKOUT_STEPS[$step];
            $data["lock_until"] = time() + $delay;
            $data["lockout_step"] = $step + 1;
            set_transient($key, $data, 86400);

            return new WP_Error(
                "login_locked",
                sprintf("حساب تا %d ثانیه دیگر قفل است.", $delay)
            );
        }

        return $user;
    }

    public function record_failure($username) {
        $key = $this->get_key($username);
        $data = get_transient($key);

        if ($data === false) {
            $data = ["count" => 0, "lockout_step" => 0, "lock_until" => 0];
        }

        $data["count"]++;
        set_transient($key, $data, 86400);

        // لاگ‌گیری
        $this->log($username);
    }

    public function clear($username, $user) {
        $key = $this->get_key($username);
        delete_transient($key);
    }

    private function log($username) {
        global $wpdb;
        $table = $wpdb->prefix . "failed_logins";
        $wpdb->insert($table, [
            "time" => current_time("mysql"),
            "ip" => $_SERVER["REMOTE_ADDR"],
            "username" => $username,
        ]);
    }
}

new WP_Login_Limiter();

این الگو، یک محدودسازی تصاعدی کامل را پیاده می‌کند. برای مطالعه‌ی بیشتر درباره‌ی هوک‌های وردپرس، پست نحوه استفاده صحیح از هوک‌های وردپرس مرجع کاملی است.

محدودسازی در XML-RPC و REST API

یکی از نقاطی که در محدودسازی فراموش می‌شود، xmlrpc.php است. این فایل، مسیر ورود جایگزینی فراهم می‌کند که اغلب از دید Rate Limiting پنهان می‌ماند.

اقدامات ضروری:

  • غیرفعال کردن XML-RPC: اگر از آن استفاده نمی‌کنید، غیرفعالش کنید.
  • محدودسازی XML-RPC: اگر لازم است، Rate Limiting جداگانه اعمال کنید.
  • محدودسازی REST API: برای endpointهای احراز هویت، Rate Limiting اعمال کنید.
// غیرفعال کردن XML-RPC
add_filter("xmlrpc_enabled", "__return_false");

// حذف هدر X-Pingback
add_filter("wp_headers", function($headers) {
    unset($headers["X-Pingback"]);
    return $headers;
});

برای مطالعه‌ی بیشتر درباره‌ی REST API، پست REST API در وردپرس راهنمای کامل مفید است.

پرسش‌های پرتکرار درباره محدودسازی ورود ناموفق

چند تلاش ناموفق قبل از قفل مناسب است؟

معمولاً بین ۳ تا ۵ تلاش. تعداد کمتر ممکن است کاربران واقعی را قفل کند، تعداد بیشتر پنجره‌ی حمله را باز می‌گذارد.

آیا قفل حساب می‌تواند به یک آسیب‌پذیری تبدیل شود؟

بله. اگر مهاجم بتواند حساب‌های واقعی را قفل کند، این یک حمله‌ی Denial of Service است. Progressive Lockout و محدودسازی بر اساس IP، این خطر را کاهش می‌دهد.

آیا Rate Limiting بر اساس IP کافی است؟

خیر. مهاجم می‌تواند با پروکسی یا Botnet، IP خود را تغییر دهد. ترکیب Rate Limiting بر اساس IP و نام کاربری، مقاوم‌تر است.

آیا CAPTCHA جایگزین Rate Limiting است؟

خیر. CAPTCHA و Rate Limiting مکمل یکدیگرند. Rate Limiting اولین لایه است و CAPTCHA لایه‌ی دوم.

چگونه از قفل شدن حساب ادمین خود جلوگیری کنیم؟

با استفاده از Whitelist IP، یا با تنظیم Progressive Lockout. همچنین می‌توان از افزونه‌هایی استفاده کرد که امکان Unlock از طریق ایمیل یا پنل مدیریت را فراهم می‌کنند.

آیا محدودسازی ورود ناموفق روی XML-RPC هم اعمال می‌شود؟

در بسیاری از افزونه‌ها، خیر. باید محدودسازی جداگانه برای XML-RPC اعمال شود، یا XML-RPC غیرفعال شود.

آیا Rate Limiting بر سرعت سایت اثر دارد؟

Rate Limiting ساده با Transient وردپرس، بار ناچیزی دارد. Rate Limiting پیچیده با Sliding Window و ذخیره‌سازی خارجی، بار بیشتری دارد.

آیا محدودسازی ورود ناموفق جایگزین 2FA است؟

خیر. محدودسازی ورود ناموفق، لایه‌ی اول است و 2FA لایه‌ی دوم. ترکیب این دو، سطح امنیت را چند برابر می‌کند.

اشتباهات رایج در محدودسازی

نشانه علت ریشه‌ای راه‌حل
قفل دائمی حساب پیاده‌سازی نادرست Progressive Lockout با زمان‌های تصاعدی
نادیده گرفتن XML-RPC تمرکز فقط روی wp-login.php محدودسازی XML-RPC یا غیرفعال‌سازی آن
عدم لاگ‌گیری غفلت از مانیتورینگ لاگ‌گیری از تمام تلاش‌های ناموفق
Rate Limiting فقط بر اساس IP نادیده گرفتن پروکسی و Botnet ترکیب IP و نام کاربری
عدم Whitelist برای ادمین خطر قفل شدن مدیر Whitelist IP ادمین
نادیده گرفتن REST API تمرکز فقط روی فرم ورود Rate Limiting برای endpointهای احراز هویت
عدم اطلاع‌رسانی به کاربر تجربه‌ی کاربری ضعیف پیام واضح و راهنمای بازیابی

برای مطالعه‌ی فهرست کامل‌تری از اشتباهات، پست‌های اشتباهات رایج در احراز هویت کاربران و اشتباهات امنیتی رایج در وردپرس مراجع مفیدی هستند.

ملاحظات معماری پیشرفته

در معماری‌های توزیع‌شده و سایت‌های بزرگ، محدودسازی ورود ناموفق پیچیدگی‌های خاص خود را پیدا می‌کند:

1. ذخیره‌سازی توزیع‌شده: در سایت‌های با چند سرور، Transient وردپرس کافی نیست. باید از Redis یا Memcached برای ذخیره‌سازی متمرکز استفاده شود.

2. IP Reputation: ترکیب Rate Limiting با پایگاه داده‌ی IP Reputation، دقت را افزایش می‌دهد.

3. Behavioral Analysis: استفاده از ML برای شناسایی الگوهای غیرمعمول ورود، مانند سرعت بالا یا User-Agent مشکوک.

4. Adaptive Rate Limiting: تنظیم پویا بر اساس بار سرور و سطح تهدید.

5. Fail2ban و CrowdSec: این ابزارها با تحلیل لاگ، IPهای مخرب را شناسایی و در سطح فایروال بلاک می‌کنند.

6. WAF Integration: ترکیب Rate Limiting با WAF، دفاع چندلایه‌ای ایجاد می‌کند.

7. Zero Trust: در معماری Zero Trust، هر درخواست باید احراز هویت و مجوزدهی شود، حتی اگر از داخل شبکه باشد.

8. Passwordless Authentication: با گسترش Passkey و FIDO2، حمله‌ی Brute Force به‌صورت بنیادین بی‌اثر می‌شود. برای مطالعه‌ی بیشتر، پست ورود بدون رمز عبور چه مزایا و معایبی دارد مفید است.

محدودسازی ورود ناموفق یک تنظیم نیست؛ یک لایه‌ی معماری است که با سایر لایه‌های امنیتی ترکیب می‌شود. هرچه این ترکیب دقیق‌تر باشد، سطح حمله باریک‌تر می‌شود.

در انتها، باید پذیرفت که Brute Force یک تهدید دائمی است و محدودسازی، بخشی جدایی‌ناپذیر از امنیت پایدار است. با ترکیب Rate Limiting، Progressive Lockout، CAPTCHA، لاگ‌گیری و 2FA، می‌توان سطح امنیت را به‌طور معناداری افزایش داد.

برای مطالعه‌ی راهنمای جامع دفاع، پست‌های چگونه سایت وردپرسی را در برابر هک محافظت کنیم، احراز هویت در وردپرس چگونه تقویت می‌شود و فعال‌سازی احراز هویت دو مرحله‌ای 2FA در وردپرس مراجع کاملی هستند.

اگر این تجربه را در یک پروژه‌ی واقعی داشته‌اید، برای ما جالب است بدانیم کدام لایه از محدودسازی بیشترین اثر را داشت: Rate Limiting، Progressive Lockout یا CAPTCHA. تجربه‌ی خودتان را در دیدگاه‌ها بنویسید؛ به‌خصوص اگر راه‌حل متفاوتی برای محدودسازی ورود ناموفق پیدا کرده‌اید که می‌تواند برای خواننده‌ی بعدی مفید باشد.

💡 نکته‌ی پایانی: محدودسازی ورود ناموفق، اولین لایه‌ی دفاع است. هرچه این لایه دقیق‌تر باشد، لایه‌های بعدی فرصت بیشتری برای عمل دارند.