2026年6月26日
2026年6月26日
WordPressのブルートフォース攻撃を防止する方法|ログイン試行制限・2段階認証・サーバー側の遮断
WordPressのブルートフォース攻撃(パスワードの総当たり)は、強いパスワードと2段階認証で「当てられても入れない」状態を作り、ログイン試行の回数制限とXML-RPCの制限で「試行させない」状態を重ねると、現実的な範囲で防げます。この記事では、攻撃がどこに来ているかの確認から、WordPress側とサーバー側の対策、プロキシ配下で起きやすい誤ブロックの防ぎ方、効果の確認方法までを順に解説します。
攻撃の入口はwp-login.phpだけではない
ブルートフォース攻撃の主な入口は次の3つです。
- **
wp-login.php**: 通常のログイン画面です。ボットはユーザー名とパスワードの組み合わせをPOSTで次々に送ります。 - **
xmlrpc.php**: 外部アプリ向けの古い仕組みです。system.multicallを使うと1回のリクエストで多数のパスワードを試せるため、ログイン画面だけ守っていても抜け道になります。 - ユーザー名の収集:
/?author=1のようなURLや、REST APIの/wp-json/wp/v2/usersから投稿者のユーザー名(スラッグ)が分かることがあります。攻撃者はここで得た名前を総当たりに使います。
まずアクセスログで、これらへのPOSTがどれだけ来ているかを確認します。
# wp-login.php と xmlrpc.php へのPOSTを送信元IPごとに集計(Nginxの例)
sudo grep -E '"POST /(wp-login|xmlrpc)\.php' /var/log/nginx/access.log \
| awk '{print $1}' | sort | uniq -c | sort -rn | head -20
特定のIPから数百〜数千件のPOSTが来ている、あるいは多数のIPから少しずつ来ている、といった傾向が分かれば、どの対策を優先するかを判断できます。
対策1: 当てられないパスワードと推測されにくいユーザー名
総当たりは、パスワードが弱いほど早く成功します。最初に全管理者アカウントを見直してください。
- パスワードはパスワード管理ツールで生成した長くランダムなものにし、他のサービスと使い回さない
- ユーザー名に
adminやサイト名など推測しやすいものを使わない。既存の管理者名を変えたい場合は、新しい管理者を作ってログインし直し、古いアカウントを削除するときに投稿を新アカウントへ引き継ぐ - 「ユーザー」→「プロフィール」で「ブログ上の表示名」をユーザー名と異なるニックネームにする
- 使っていない管理者・編集者アカウントは削除するか権限を下げる
ユーザー名が知られること自体は致命的ではありませんが、攻撃者に半分の答えを渡さないことで総当たりの効率は大きく下がります。
対策2: 2段階認証を導入する
2段階認証(2要素認証)を有効にすると、パスワードが当たっても、スマートフォンの認証アプリが表示するワンタイムコードなどがなければログインできません。総当たりに対して最も効果の高い対策です。
WordPress.orgで公開されている「Two Factor」プラグインや、総合セキュリティプラグインの2段階認証機能が使えます。導入時は次の点に注意します。
- 設定後、ログアウトして実際にコードでログインできるかを確かめてから、他の管理者にも展開する
- バックアップコードを発行して安全な場所に保管する。スマートフォンを紛失するとログインできなくなるためです
- 万一ログインできなくなったときは、FTPやファイルマネージャーで該当プラグインのフォルダ名を変えると無効化できる、という復旧手段を把握しておく
対策3: ログイン試行回数を制限する
WordPress本体には、ログイン失敗の回数を制限する機能がありません。「Limit Login Attempts Reloaded」などのプラグインを使うと、一定回数失敗したIPを一定時間ロックアウトできます。許可回数やロックアウト時間はプラグインの設定画面で変更でき、まずは既定値のまま様子を見て、正規の利用者がロックされることがないかを確認しながら調整します(設定項目の名前や既定値は2026年10月現在のものと異なる場合があります)。
ここで最も多いトラブルが、CDNやリバースプロキシ配下での誤ブロックです。CloudflareなどのCDNやロードバランサーを経由していると、WordPressから見た送信元IPがすべてプロキシのIPになり、1人の攻撃者のせいで全員がロックアウトされることがあります。多くのプラグインには、X-Forwarded-For などのヘッダーから実際のIPを読む設定があります。ただし、このヘッダーは送信者が自由に偽装できるため、信頼できるプロキシを経由している場合にだけ有効にしてください。
対策4: XML-RPCを制限する
XML-RPCを使っていないなら、制限しておくと system.multicall による大量試行を防げます。JetpackやWordPressの一部の外部アプリ連携はXML-RPCを使うため、利用状況を確かめてから判断してください。
WordPress側で認証が必要なXML-RPCメソッドを無効にするには、xmlrpc_enabled フィルターを使います。
<?php
// 認証が必要なXML-RPCメソッドを無効化する(ピンバックなど認証不要の機能は残る)
add_filter( 'xmlrpc_enabled', '__return_false' );
このフィルターはファイルへのアクセス自体は止めないため、リクエストは引き続きPHPまで届きます。サーバー負荷も減らしたい場合は、Webサーバーでファイルごと拒否します。
location = /xmlrpc.php {
deny all;
}
Apacheでは .htaccess に次のように書きます。
<Files "xmlrpc.php">
Require all denied
</Files>
対策5: サーバー側で試行を絞る
Nginxのlimit_reqでログイン画面への送信を制限する
ボットの送信をPHPに届く前に絞ると、サーバー負荷の対策にもなります。location を新たに作ると既存のPHP処理の設定が適用されなくなるため、中にPHP-FPMへの受け渡し設定を書く必要があります。設定ファイルは変更前に複製し、sudo nginx -t で構文を確認してから再読み込みしてください。
# http ブロック内: 送信元IPごとに1分あたり10回まで
limit_req_zone $binary_remote_addr zone=wplogin:10m rate=10r/m;
# server ブロック内
location = /wp-login.php {
limit_req zone=wplogin burst=5 nodelay;
limit_req_status 429;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php-fpm.sock; # 既存の設定に合わせる
}
wp-login.php は表示(GET)でも数えられるため、パスワード再設定やログアウトも含めて普通に使う範囲で429が返らないかを確認します。プロキシ配下では、ここでも先に実際の送信元IPを取得する設定(real_ip 関連)が必要です。
fail2banで繰り返し失敗するIPを遮断する
fail2banはログを監視し、条件に一致したIPをファイアウォールで一定時間遮断するツールです。WordPressはログイン失敗を標準でシステムログに書きませんが、ログインに失敗した wp-login.php へのPOSTはステータス200(ログイン画面の再表示)、成功時は302(リダイレクト)になるため、アクセスログから失敗を拾えます。
# /etc/fail2ban/filter.d/wordpress-login.conf
[Definition]
failregex = ^<HOST> .* "POST /wp-login\.php HTTP/[0-9.]+" 200
ignoreregex =
# /etc/fail2ban/jail.d/wordpress-login.conf
[wordpress-login]
enabled = true
port = http,https
filter = wordpress-login
logpath = /var/log/nginx/access.log
maxretry = 5
findtime = 600
bantime = 3600
sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wordpress-login.conf で、フィルターが実際のログに一致するかを反映前に確認できます。反映後は sudo fail2ban-client status wordpress-login で遮断中のIPを確認します。CDN配下ではファイアウォールで遮断してもCDNのIPを止めるだけになるため、この方法は直接アクセスを受けるサーバー向けです。
ログイン画面にアクセス制限をかける
管理者が固定のIPから作業するなら、wp-login.php と /wp-admin/ へのアクセスをそのIPに限定する方法もあります。ただし /wp-admin/admin-ajax.php はフロント側の機能からも呼ばれるため、制限から除外してください。固定IPがない場合は、wp-login.php にBasic認証を重ねる方法もあります。
ユーザー名の収集を減らす
REST APIのユーザー一覧をログインしていない訪問者に返さないようにするには、次のコードが使えます。ブロックエディターなどログイン中の機能はそのまま動きます。
<?php
add_filter( 'rest_endpoints', function ( $endpoints ) {
if ( ! is_user_logged_in() ) {
unset( $endpoints['/wp/v2/users'], $endpoints['/wp/v2/users/(?P<id>[\d]+)'] );
}
return $endpoints;
} );
ログインURLを変更するプラグインもありますが、効果は「見つけにくくする」ことにとどまり、URLが知られれば意味がなくなります。他のプラグインやキャッシュとの相性問題が出ることもあるため、パスワード・2段階認証・試行制限の代わりにはせず、補助として考えてください。
効果を確認する
対策後は、最初に使ったログ集計のコマンドを再度実行し、xmlrpc.php へのPOSTが403になっているか、wp-login.php への大量試行が429や遮断で止まっているかを確認します。試行制限プラグインのログや、2段階認証を求められた回数も手がかりになります。攻撃は対策後も来続けるので、件数がゼロになることよりも「試行が成功しない状態」を保てているかを見るのが大切です。
まとめ
- 入口は
wp-login.php、xmlrpc.php、ユーザー名の収集の3つ。まずログで攻撃の傾向を確かめる - 長くランダムなパスワードと2段階認証で、試行されても入れない状態を作る
- ログイン試行制限はプロキシ配下での誤ブロックに注意し、実際の送信元IPを正しく取得する
- XML-RPCを使っていなければ、フィルターかWebサーバーの設定で制限する
- Nginxの
limit_reqやfail2banで、PHPに届く前に試行を絞る - ログインURLの変更は補助的な対策。効果はログで定期的に確認する