WordPress のログインを破るには「ユーザー名」と「パスワード」の2つが要ります。ところが WordPress は初期状態のまま公開すると、ユーザー名(ログイン ID)のほうを外部から簡単に教えてしまう設計になっています。
この記事は、自社サイトや制作したサイトを WordPress で運用している Web 担当者・制作会社向けに、ユーザー名が漏れる代表的な3つの経路と、それぞれをコピペで塞ぐ設定を1本にまとめたものです。攻撃のやり方ではなく、同じ穴を塞ぐための再発防止の設定だけを扱います。
「パスワードは長くて強いから大丈夫」と思っていても、ユーザー名が既知になると総当たり(ブルートフォース)や、流出パスワードの使い回し(パスワードリスト攻撃)の成功率は上がります。攻撃者に「宿題の半分」を渡さないことが目的です。
なぜユーザー名が漏れると問題なのか
ログイン試行は「ユーザー名 × パスワード」の総当たりです。ユーザー名が分からなければ、攻撃者は無数のユーザー名も同時に試す必要があります。しかしユーザー名(特に管理者アカウント)が特定できると、あとはパスワードだけに攻撃を集中できます。
さらに WordPress では、記事の投稿者名(表示名)とログイン ID が同じになっているサイトが非常に多く、投稿者アーカイブの URL からログイン ID がそのまま推測できるケースが少なくありません。まずは「どこから漏れているか」を1つずつ確認していきます。
経路1: ?author=N によるユーザー名の露出
WordPress は https://example.com/?author=1 のような URL にアクセスすると、その番号のユーザーの投稿者アーカイブ /author/ユーザースラッグ/ へリダイレクトします。この「ユーザースラッグ」が初期設定ではログイン ID と一致していることが多く、?author=1(多くの場合サイト最初の管理者)を見るだけでログイン ID の当たりがついてしまいます。
対策は、author=数字 を含むクエリを弾くことです。
nginx の場合(server ブロック内):
# ?author=N によるユーザー名列挙を遮断する
if ($args ~* "author=\d") {
return 403;
}
Apache(.htaccess の先頭付近)の場合:
# ?author=N によるユーザー名列挙を遮断する
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{QUERY_STRING} (^|&)author=\d [NC]
RewriteRule ^ - [F,L]
</IfModule>
Web サーバの設定を触れない共有レンタルサーバなどでは、テーマの functions.php(子テーマ推奨)でリダイレクトそのものを止める方法もあります。
// 投稿者アーカイブへのアクセス・?author リダイレクトを無効化する
add_action('template_redirect', function () {
if (is_author() || (isset($_GET['author']) && ! is_admin())) {
wp_safe_redirect(home_url('/'), 301);
exit;
}
});
投稿者アーカイブページを SEO などで意図的に使っている場合は、この functions.php 版は使わず、あとで触れる「投稿者スラッグをログイン ID と別にする」対策と組み合わせてください。
経路2: REST API のユーザー一覧エンドポイント
WordPress 4.7 以降は REST API が標準で有効です。https://example.com/wp-json/wp/v2/users にアクセスすると、未認証でもサイトの投稿者一覧(表示名・スラッグ)が JSON で返ってきます。?author=N を塞いでもこちらが開いていれば意味がないため、必ずセットで塞ぎます。
functions.php で、ログインしていない(=ユーザー一覧を見る権限がない)アクセスに対してだけ users エンドポイントを無効化します。管理画面やプラグインが使う分は残るので、副作用が小さいのがポイントです。
// 権限のない相手には REST API の users エンドポイントを返さない
add_filter('rest_endpoints', function ($endpoints) {
if (! current_user_can('list_users')) {
if (isset($endpoints['/wp/v2/users'])) {
unset($endpoints['/wp/v2/users']);
}
if (isset($endpoints['/wp/v2/users/(?P<id>[\d]+)'])) {
unset($endpoints['/wp/v2/users/(?P<id>[\d]+)']);
}
}
return $endpoints;
});
current_user_can('list_users') で判定しているので、管理者としてログイン中の操作やユーザー管理系プラグインには影響しません。第三者(未認証)だけが 404 相当になります。
Web サーバ側でも念のため URL 単位で塞いでおくと多層防御になります。
# /wp-json/wp/v2/users への外部アクセスを遮断する(多層防御)
location ~* ^/wp-json/wp/v2/users {
return 403;
}
なお、REST API そのものを丸ごと無効化するとブロックエディタや一部プラグインが動かなくなることがあります。**「REST API を止める」のではなく「users エンドポイントだけを未認証から隠す」**のが安全です。
経路3: ログインエラーの文言差分
wp-login.php は初期状態だと、入力したユーザー名が存在するかどうかでエラー文言が変わります。存在しないときと、存在するがパスワードが違うときで表示が異なるため、総当たりの前に「有効なユーザー名」だけを絞り込めてしまうのです。
対策は、エラー文言を1種類に統一して、ユーザー名が存在するかどうかを推測させないことです。functions.php に追記します。
// ログイン失敗時の文言を統一し、ユーザー名の存在有無を隠す
add_filter('login_errors', function () {
return 'ユーザー名またはパスワードが正しくありません。';
});
あわせて、ログイン試行そのものを絞る回数制限(レートリミット)や wp-login.php の保護も入れておくと、ユーザー名を隠す対策が一段と効きます(下記の関連記事を参照)。
仕上げ: 投稿者スラッグをログイン ID と別にする
ここまでの経路を塞いでも、コメントや著者表示からログイン ID を表示名として使っていると推測されることがあります。ログイン ID と、公開される投稿者スラッグ・表示名は分けておきましょう。
wp-cli が使えるサーバなら、投稿者スラッグ(user_nicename)だけを別の値に変更できます(ログイン ID 自体は変わりません)。
# ユーザーID 1 の公開スラッグをログインIDと別の値に変更する
wp user update 1 --user_nicename="editor-team"
管理画面から行う場合は、プロフィール画面で「ブログ上の表示名」をログイン ID 以外に設定します。
設定後の確認方法
設定を入れたら、外部からどう見えるかを curl で確認します。自分の管理するサイトに対してのみ実行してください。
# 1) ?author=1 でユーザー名(スラッグ)が漏れないか
# Location に /author/xxxx/ が出なければ遮断できている
curl -sI "https://example.com/?author=1" | grep -i "^location"
# 2) REST API でユーザー一覧が取れないか
# JSON のユーザー配列が返らず、403/404 相当になっていれば成功
curl -s -o /dev/null -w "%{http_code}\n" "https://example.com/wp-json/wp/v2/users"
# 3) 存在するユーザー名でログイン失敗した時の文言が
# 「ユーザー名またはパスワードが正しくありません。」に統一されているか(ブラウザで確認)
- で
Locationが出ない、2) が403または404を返せば、外部からのユーザー名列挙はひとまず塞げています。
まとめ
- WordPress は初期状態だと
?author=N/ REST API の users / ログインエラー差分の3経路でユーザー名(ログイン ID)が漏れる。 -
?author=Nは nginx/Apache のリライトで遮断、REST API はcurrent_user_can('list_users')で未認証だけ無効化、ログイン文言は統一する。 - さらにログイン ID と公開スラッグを分けると、経路を塞いだ効果が長持ちする。
- ユーザー名を隠すのは総当たり対策の「入口」。ログイン試行回数の制限とセットで初めて効く。
3つとも「一度入れておけば効き続ける」設定です。既存サイトの棚卸しのタイミングで、まとめて入れておくことをおすすめします。
関連記事
本記事の設定が効いているか(ユーザー名の露出や各種設定漏れ)は、Webサイトを9つの守りでまるごと守る「サイトドック」の月次の定期健診でまとめて確認できます → https://sitedock.jp