2026年5月29日
2026年5月29日
WordPressのログイン試行回数を制限してブルートフォース攻撃を防ぐ方法
はじめに
WordPressのwp-login.phpはデフォルトで無制限のログイン試行を許可しています。これを悪用したブルートフォース攻撃(パスワード総当たり)はWordPressへの最も一般的な攻撃手法のひとつです。ログイン試行を制限することで大幅に防御力を高められます。
症状・原因
- アクセスログに
POST /wp-login.phpが毎秒数件記録されている - 管理者パスワードが変更されていて管理画面にログインできない
- サーバーのCPU負荷が異常に高くなっている
- 見知らぬIPアドレスからの大量アクセスがある
解決手順
ステップ1:コードでログイン試行回数を制限する
// functions.php: ログイン試行回数制限
class Login_Attempt_Limiter {
private const MAX_ATTEMPTS = 5;
private const LOCKOUT_TIME = 15 * MINUTE_IN_SECONDS;
private const LOG_GROUP = 'login_security';
public function __construct() {
add_filter('authenticate', [$this, 'check_attempts'], 30, 3);
add_action('wp_login_failed', [$this, 'record_failed_attempt']);
add_filter('login_errors', [$this, 'generic_error_message']);
add_action('wp_login', [$this, 'clear_attempts'], 10, 2);
}
public function check_attempts(WP_User|WP_Error|null $user, string $username, string $password): mixed {
$ip = $this->get_client_ip();
$locked = get_transient('login_locked_' . md5($ip));
if ($locked) {
$remaining = human_time_diff(time(), $locked);
return new WP_Error(
'too_many_retries',
sprintf('ログイン試行回数が多すぎます。%s後に再試行してください。', $remaining)
);
}
return $user;
}
public function record_failed_attempt(string $username): void {
$ip = $this->get_client_ip();
$key = 'login_attempts_' . md5($ip);
$attempts = (int) get_transient($key) + 1;
set_transient($key, $attempts, self::LOCKOUT_TIME);
if ($attempts >= self::MAX_ATTEMPTS) {
$unlock_time = time() + self::LOCKOUT_TIME;
set_transient('login_locked_' . md5($ip), $unlock_time, self::LOCKOUT_TIME);
// ログに記録
error_log(sprintf(
'[Login Lock] IP: %s, Username: %s, Attempts: %d',
$ip, $username, $attempts
));
}
}
public function clear_attempts(string $user_login, WP_User $user): void {
$ip = $this->get_client_ip();
delete_transient('login_attempts_' . md5($ip));
delete_transient('login_locked_' . md5($ip));
}
// エラーメッセージを汎用化(ユーザー名/パスワードのどちらが間違いか分からなくする)
public function generic_error_message(): string {
return 'ユーザー名またはパスワードが正しくありません。';
}
private function get_client_ip(): string {
return sanitize_text_field(
$_SERVER['HTTP_CF_CONNECTING_IP']
?? $_SERVER['HTTP_X_FORWARDED_FOR']
?? $_SERVER['REMOTE_ADDR']
?? 'unknown'
);
}
}
new Login_Attempt_Limiter();
ステップ2:reCAPTCHA v3をログインページに追加する
// Google reCAPTCHA v3 をログインページに組み込む
class WP_Login_Recaptcha {
private const SITE_KEY = 'YOUR_SITE_KEY';
private const SECRET_KEY = 'YOUR_SECRET_KEY';
private const MIN_SCORE = 0.5;
public function __construct() {
add_action('login_enqueue_scripts', [$this, 'enqueue_script']);
add_action('login_form', [$this, 'add_hidden_field']);
add_filter('authenticate', [$this, 'verify_token'], 20, 3);
}
public function enqueue_script(): void {
wp_enqueue_script(
'google-recaptcha',
'https://www.google.com/recaptcha/api.js?render=' . self::SITE_KEY,
[],
null,
true
);
wp_add_inline_script('google-recaptcha', sprintf('
grecaptcha.ready(function() {
grecaptcha.execute("%s", {action: "login"}).then(function(token) {
document.getElementById("g-recaptcha-response").value = token;
});
});
', self::SITE_KEY));
}
public function add_hidden_field(): void {
echo '<input type="hidden" id="g-recaptcha-response" name="g-recaptcha-response" value="">';
}
public function verify_token(WP_User|WP_Error|null $user, string $username, string $password): mixed {
if (empty($username)) return $user;
$token = sanitize_text_field($_POST['g-recaptcha-response'] ?? '');
if (!$token) {
return new WP_Error('recaptcha_failed', 'reCAPTCHA の確認に失敗しました。もう一度お試しください。');
}
$response = wp_remote_post('https://www.google.com/recaptcha/api/siteverify', [
'body' => [
'secret' => self::SECRET_KEY,
'response' => $token,
'remoteip' => $_SERVER['REMOTE_ADDR'] ?? '',
],
]);
if (is_wp_error($response)) return $user;
$result = json_decode(wp_remote_retrieve_body($response), true);
if (!($result['success'] ?? false) || ($result['score'] ?? 0) < self::MIN_SCORE) {
return new WP_Error('recaptcha_failed', 'reCAPTCHA スコアが低すぎます。ブラウザを更新してお試しください。');
}
return $user;
}
}
new WP_Login_Recaptcha();
ステップ3:wp-login.phpを特定IPのみに制限する
# .htaccess: wp-login.phpのアクセスをIP制限
<Files "wp-login.php">
Order Deny,Allow
Deny from all
Allow from 203.0.113.1 # 自宅IP
Allow from 198.51.100.0 # 会社IP
</Files>
# Nginx: wp-login.phpをIP制限
location = /wp-login.php {
allow 203.0.113.1;
allow 198.51.100.0/24;
deny all;
# 許可されたIPでもレート制限
limit_req zone=wp_login burst=3 nodelay;
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
ステップ4:ログインURLを変更する
// wp-login.phpのURLを変更する(セキュリティ by obscurity)
// WP Hide Login プラグインの動作を手動で実装
add_action('init', function(): void {
$new_login = 'my-secret-login'; // 変更後のログインURL
// カスタムログインURLをルーティング
if ($_SERVER['REQUEST_URI'] === "/{$new_login}") {
$_SERVER['REQUEST_URI'] = '/wp-login.php';
require ABSPATH . 'wp-login.php';
exit;
}
// 元のwp-login.phpへのアクセスをブロック
if (str_ends_with($_SERVER['REQUEST_URI'], 'wp-login.php')
&& !is_admin()) {
wp_redirect(home_url('/'), 301);
exit;
}
});
ステップ5:ログインアクティビティを監視する
// ログイン/ログアウトを記録
add_action('wp_login', function(string $user_login, WP_User $user): void {
error_log(sprintf(
'[Login] User: %s | IP: %s | Time: %s',
$user_login,
sanitize_text_field($_SERVER['REMOTE_ADDR'] ?? 'unknown'),
current_time('Y-m-d H:i:s')
));
}, 10, 2);
add_action('wp_login_failed', function(string $username): void {
error_log(sprintf(
'[Login Failed] Username: %s | IP: %s | Time: %s',
$username,
sanitize_text_field($_SERVER['REMOTE_ADDR'] ?? 'unknown'),
current_time('Y-m-d H:i:s')
));
});
// ロックされたIPリストを管理画面に表示
add_action('admin_notices', function(): void {
if (!current_user_can('manage_options')) return;
global $wpdb;
$locked = $wpdb->get_col("
SELECT option_name FROM {$wpdb->options}
WHERE option_name LIKE '_transient_login_locked_%'
");
if ($locked) {
echo '<div class="notice notice-warning"><p>';
echo count($locked) . ' 件のIPがロックされています。';
echo '</p></div>';
}
});
注意事項
- 自分のIPを必ず確認: IP制限を設定する前に自分のIPアドレスを確認し、ロックアウトされないようにしてください
- 動的IP: 動的IPアドレスを使用している場合はIP制限より試行回数制限の方が適切です
- 二要素認証: ログイン試行制限に加えて二要素認証(2FA)を組み合わせると防御力が大幅に向上します
- reCAPTCHAのキー: Google reCAPTCHA v3のサイトキー・シークレットキーはGoogle reCAPTCHAの管理コンソールで取得してください
まとめ
ログイン保護の最適な組み合わせは「試行回数制限(5回でロック)・reCAPTCHA v3・Nginxレート制限・ログインURL変更」の4層です。これらを組み合わせることでブルートフォース攻撃をほぼ完全にブロックできます。関連記事:WordPressのWAF設定方法