最近我的网站后台频繁遭到爆破,日志里充斥着各种弱密码尝试。

为降低安全风险,避免恶意请求反复占用服务器资源,我决定在 WordPress 登录页加入一个简单的验证码。

这样能在用户输入密码的同时,多一道校验,拦截绝大多数自动化爆破工具。

首先进入后台,在侧边栏——外观——主题文件编辑器中,选择functions.php,在文件内容底部添加代码:

function start_session_for_captcha() {
    if ( in_array($GLOBALS['pagenow'], ['wp-login.php','wp-register.php']) && !session_id() ) {
        @ob_start();
        session_start();
        // 不要立即关闭 session,让登录验证能读取
    }
}
add_action('init', 'start_session_for_captcha', 1);

// 生成验证码表达式和答案
function generate_captcha() {
    $num1 = rand(10, 99);
    $num2 = rand(10, 99);
    $operator = ['+', '-'][rand(0,1)];
    if ($operator === '-' && $num1 < $num2) {
        list($num1, $num2) = [$num2, $num1];
    }
    $_SESSION['captcha_answer'] = ($operator === '+') ? $num1 + $num2 : $num1 - $num2;
    $_SESSION['captcha_expression'] = "$num1 $operator $num2";
    return $_SESSION['captcha_expression'];
}

// 登录表单显示验证码
function add_captcha_to_login_form() {
    if (!isset($_SESSION['captcha_expression'])) {
        generate_captcha();
    }
    $expression = $_SESSION['captcha_expression'];

    echo "
    <p id='login-captcha' style='display:flex; align-items:center; gap:8px;'>
        <label for='captcha' style='margin:0;'>验证码:$expression = ?</label>
        <input type='text' name='captcha' id='captcha' autocomplete='off' required style='width:80px;'>
        <span id='refresh-captcha' title='刷新验证码' style='cursor:pointer;'>&#x21bb;</span>
    </p>
    <script>
    document.getElementById('refresh-captcha').addEventListener('click', function() {
        const btn = this;
        btn.style.transition='transform 0.3s';
        btn.style.transform='rotate(360deg)';
        setTimeout(()=>{btn.style.transform='rotate(0deg)';},300);

        fetch('" . admin_url('admin-ajax.php') . "?action=refresh_captcha&_=' + new Date().getTime())
        .then(r=>r.json())
        .then(data=>{
            if(data.success){
                document.querySelector('#login-captcha label').innerText='验证码:'+data.data.expression+' = ?';
                document.querySelector('#captcha').value='';
            }else{
                alert(data.data || '刷新失败,请重试');
            }
        }).catch(e=>{
            console.error('刷新验证码失败:',e);
            alert('刷新验证码失败,请重试');
        });
    });
    </script>";
}
add_action('login_form', 'add_captcha_to_login_form');

// Ajax 刷新验证码
function ajax_refresh_captcha() {
    if (!session_id()) session_start();
    $expression = generate_captcha();
    session_write_close(); // 仅在 Ajax 刷新后释放锁
    wp_send_json_success(['expression'=>$expression]);
}
add_action('wp_ajax_refresh_captcha', 'ajax_refresh_captcha');
add_action('wp_ajax_nopriv_refresh_captcha', 'ajax_refresh_captcha');

// 登录验证
function validate_captcha_on_login($user, $username, $password){
    if(!session_id()) session_start();
    if(!isset($_POST['captcha']) || !is_numeric($_POST['captcha'])){
        return new WP_Error('captcha_error','<strong>错误</strong>: 验证码必须为数字.');
    }
    $input = intval($_POST['captcha']);
    if(!isset($_SESSION['captcha_answer']) || $input !== $_SESSION['captcha_answer']){
        return new WP_Error('captcha_error','<strong>错误</strong>: 验证码错误,请重试.');
    }
    // 验证通过,清理 session
    unset($_SESSION['captcha_answer'], $_SESSION['captcha_expression']);
    return $user;
}
add_filter('authenticate','validate_captcha_on_login',30,3);

👆🏻🤓建议:保存后先在浏览器无痕模式下测试,确认验证码功能正常后再投入使用。


值得注意的是validate_captcha_on_login部分:

function validate_captcha_on_login($user, $username, $password){
    if(!session_id()) session_start();
    if(!isset($_POST['captcha']) || !is_numeric($_POST['captcha'])){
        return new WP_Error('captcha_error','<strong>错误</strong>: 验证码必须为数字.');
    }
    $input = intval($_POST['captcha']);
    if(!isset($_SESSION['captcha_answer']) || $input !== $_SESSION['captcha_answer']){
        return new WP_Error('captcha_error','<strong>错误</strong>: 验证码错误,请重试.');
    }
    // 验证通过,清理 session
    unset($_SESSION['captcha_answer'], $_SESSION['captcha_expression']);
    return $user;
}

中的关键位置:

 if(!isset($_SESSION['captcha_answer']) 
 || $input !==
 $_SESSION['captcha_answer'])

这里不区分用户名是否存在,直接判断验证码是否正确。

也就是说:

  • 不管用户名存在与否,只要验证码不对(或者会话里没有答案),就统一返回 “验证码错误”。

  • 即使用户名不存在也返回验证码错误,以达到迷惑攻击者的目的。

各位站长在部署时稍加注意,别被自己的门闩挡在外面了


旧代码,因干扰了 REST API 及环回请求被弃置(拿来做警示展出,勿用)

// 启动 Session
function start_session_for_captcha() {
    if (!session_id()) {
        @ob_start();
        session_start();
    }
}
add_action('init', 'start_session_for_captcha', 1);

// 生成验证码
function generate_captcha() {
    $num1 = rand(10, 99);
    $num2 = rand(10, 99);
    $operator = ['+', '-'][rand(0,1)];
    if ($operator === '-' && $num1 < $num2) {
        list($num1, $num2) = [$num2, $num1];
    }
    $_SESSION['captcha_answer'] = ($operator === '+') ? $num1 + $num2 : $num1 - $num2;
    return "$num1 $operator $num2";
}

// 登录表单显示验证码
function add_captcha_to_login_form() {
    // 只有当 Session 没有答案时生成一次
    if (!isset($_SESSION['captcha_answer'])) {
        $expression = generate_captcha();
    } else {
        // 如果已有答案,显示当前的公式
        $answer = $_SESSION['captcha_answer'];
        $expression = ''; // 为了安全,这里可以保留空,或者生成表达式存储在 $_SESSION['captcha_expression']
    }

    echo "
    <p id='login-captcha' style='display:flex; align-items:center; gap:8px;'>
        <label for='captcha' style='margin:0;'>验证码:" . (isset($_SESSION['captcha_expression']) ? $_SESSION['captcha_expression'] : generate_captcha()) . " = ?</label>
        <input type='text' name='captcha' id='captcha' autocomplete='off' required style='width:80px;'>
        <span id='refresh-captcha' title='刷新验证码' style='cursor:pointer;'>&#x21bb;</span>
    </p>
    <script>
    document.getElementById('refresh-captcha').addEventListener('click', function() {
        const btn = this;
        btn.style.transition='transform 0.3s';
        btn.style.transform='rotate(360deg)';
        setTimeout(()=>{btn.style.transform='rotate(0deg)';},300);

        fetch('" . admin_url('admin-ajax.php') . "?action=refresh_captcha&_=' + new Date().getTime())
        .then(r=>r.json())
        .then(data=>{
            if(data.success){
                document.querySelector('#login-captcha label').innerText='验证码:'+data.data.expression+' = ?';
                document.querySelector('#captcha').value='';
            }else{
                alert(data.data || '刷新失败,请重试');
            }
        }).catch(e=>{
            console.error('刷新验证码失败:',e);
            alert('刷新验证码失败,请重试');
        });
    });
    </script>";
}
add_action('login_form', 'add_captcha_to_login_form');

// AJAX 刷新验证码
function ajax_refresh_captcha() {
    if (!session_id()) session_start();
    $expression = generate_captcha();
    $_SESSION['captcha_expression'] = $expression; // 存储公式
    wp_send_json_success(['expression'=>$expression]);
}
add_action('wp_ajax_refresh_captcha', 'ajax_refresh_captcha');
add_action('wp_ajax_nopriv_refresh_captcha', 'ajax_refresh_captcha');

// 登录验证
function validate_captcha_on_login($user,$username,$password){
    if(!isset($_POST['captcha']) || !is_numeric($_POST['captcha'])){
        return new WP_Error('captcha_error','<strong>错误</strong>: 验证码必须为数字.');
    }
    $input=intval($_POST['captcha']);
    if(!isset($_SESSION['captcha_answer']) || $input!==$_SESSION['captcha_answer']){
        return new WP_Error('captcha_error','<strong>错误</strong>: 验证码错误,请重试.');
    }
    unset($_SESSION['captcha_answer'], $_SESSION['captcha_expression']);
    return $user;
}
add_filter('authenticate','validate_captcha_on_login',30,3);

转载请标明出处

由ChatGPT协助完成