WordPress Logo WordPress のユーザー・一覧表示対策

WordPress の場合、「サイトのURL/?author=1」などでアクセスするとユーザー名(ログインID)が閲覧者に見えてしまったり、WP REST API がデフォルトで有効になっているので、認証無しでもユーザー情報などのデータを外部から取得する事ができるようになっているので、そのための対策につて。

更新日:2019年02月19日

作成日:2019年01月18日

ユーザーの一覧表示対策

WordPress のいくつかの機能により、ユーザーのログイン ID などを外部から取得することができるようになっています。そのため、ブルートフォースアタックなどが懸念されますが、ユーザー名を適切に設定し、複雑なパスワードを使い定期的に変更していれば問題はないとされているようです。また、以下の対策で完全に問題を回避できるかは、定かではありません。

author 情報を非表示に

WordPress では投稿者ごとに投稿者アーカイブが作成されるめ、「サイトのURL/?author=1」などでアクセスすると、「サイトのURL/author/ログインID/」にリダイレクトされ、WordPress のユーザー名(ログインID)が閲覧者にわかってしまいます。これを防ぐには、以下のような方法があります。

functions.php を利用

以下(アクションフック)を functions.php に記述することで、?author=n(n は数値)を使ってアクセスされた場合、ホームにリダイレクトすることができます。 $_SERVER['QUERY_STRING']) でクエリ文字を取得して、それに ?author=n が含まれているかを判定しています。

wp_redirect( home_url('/404.php') ) とすれば、404ページにリダイレクトすることができます。

function disable_author_archive_query() {
  if( preg_match('/author=([0-9]*)/i', $_SERVER['QUERY_STRING']) ){
    wp_redirect( home_url() );
    exit;
  }
}
add_action('init', 'disable_author_archive_query');

以下は、検索するとよく紹介れている方法です。$_GET['author'] で ?author=値 のクエリがリクエストされたか、または、$_SERVER['REQUEST_URI']) でリクエストされた URL に /author/ が含まれているかを判定しています。

function disable_author_archive() {
  if( $_GET['author'] || preg_match('#/author/.+#', $_SERVER['REQUEST_URI']) ){
    wp_redirect( home_url('/404.php') );
    exit;
  }
}
add_action('init', 'disable_author_archive');

以下は、「Stop User Enumeration in WordPress」で紹介されている方法で、フィルターフックを使っています。また、リダイレクトではなく、 die(); で終了させています。

if (!is_admin()) {
  // default URL format
  if (preg_match('/author=([0-9]*)/i', $_SERVER['QUERY_STRING'])) die();
  add_filter('redirect_canonical', 'shapeSpace_check_enum', 10, 2);
}
function shapeSpace_check_enum($redirect, $request) {
  // permalink URL format
  if (preg_match('/\?author=([0-9]*)(\/*)/i', $request)) die();
  else return $redirect;
}

is_admin() は、「ダッシュボードまたは管理画面の表示中かどうかをチェックする」条件分岐タグです。

.htaccess を利用

以下を WordPress のインストールディレクトリにある .htaccess に記述します。http://example.com/ の部分は自分の URL に変更します。記述する場所は、「# BEGIN WordPress」の前に記述します。但し、サーバーの設定や .htaccess の配置場所等によりうまく機能しない場合もあるようです。(引用元:Block User ID Phishing Requests

<IfModule mod_rewrite.c>
  RewriteCond %{QUERY_STRING} ^author=([0-9]*)
  RewriteRule .* http://example.com/? [L,R=302]
</IfModule>  
author.php を利用(NG)

検索すると、author.php を作成して、以下のように記述してホームなどにリダイレクトする方法が紹介されていますが、この方法の場合、URL 等にユーザー名は表示されなくなりますが、ネットワーク上にユーザー名が流れてしまいます。

<?php
  wp_redirect(home_url());
  exit();

応答ヘッダの「Location」にユーザー名が表示されているのがわかります。

「サイトのURL/?author=1」に対する応答ヘッダ

WP REST API を無効にする

WordPress 4.7 から「WP REST API」がデフォルトで有効になっています。そのため認証無しでも投稿記事やユーザー情報などのデータを外部から取得する事ができるようになっています。

例えば、以下のアドレスにアクセスしてみると、json 形式のユーザーのデータが表示されます。

「サイトのURL/wp-json/wp/v2/users」や「サイトのURL/?rest_route=/wp/v2/users」

REST API を使用しない場合は、以下のような方法で REST API を無効にすることができます。

※ WordPress の REST API Handbook によると、管理画面の機能が REST API に依存しているため無効にするのは推奨されていません。無効にする場合は、以下のように rest_authentication_errors フィルタを使ってログインしていない場合にのみ無効にする方法が推奨されてます。(以下を functions.php に記述)

add_filter( 'rest_authentication_errors', function( $result ) {
  if ( ! empty( $result ) ) {
    return $result;
  }
  if ( ! is_user_logged_in() ) {
    return new WP_Error( 'rest_not_logged_in', 'You are not currently logged in.', array( 'status' => 401 ) );
  }
  return $result;
});

但し、上記の方法だと、ユーザーがログインしていないと Contact Form7 などの REST API を使用しているプラグインが機能しなくなってしまいます。

以下の方法で、特定のプラグインなどを除外して REST API を無効にすることができますが、今後 REST API を使ったプラグインや機能が増えていくことを考えるとこの方法が妥当かどうか自分のサイトに合わせて判断する必要があります。

以下では、REST API を使用している Embed(oEmbed)や編集エディタの機能、 Contact Form 7、 Akismet のプラグイン以外で REST API を無効にしています。

rest_pre_dispatch」というフックを使っています。「WordPress4.7 の WP REST API を無効にする(ねんでぶろぐ)」を参考にさせていただきました。

function deny_rest_api_except_permitted( $result, $wp_rest_server, $request ){
  
  //permit oembed, Contact Form 7, Akismet
  $permitted_routes = [ 'oembed', 'contact-form-7', 'akismet'];

  $route = $request->get_route();

  foreach ( $permitted_routes as $r ) {
    if ( strpos( $route, "/$r/" ) === 0 ) return $result;
  }

  //permit Gutenberg(ユーザーが投稿やページの編集が可能な場合)
  if ( current_user_can( 'edit_posts' ) || current_user_can( 'edit_pages' )) {
    return $result;
  }

  return new WP_Error( 'rest_disabled', __( 'The REST API on this site has been disabled.' ), array( 'status' => rest_authorization_required_code() ) );
}
add_filter( 'rest_pre_dispatch', 'deny_rest_api_except_permitted', 10, 3 );

除外したいプラグインが増えた場合は、その文字列を配列 $permitted_routes = [ ]; に追加します。「サイトのURL/wp-json/」で namespaces や routes を確認できます。

「自分の WordPress サイトのURL/wp-json/」で表示される情報

/oembed/1.0
/oembed/1.0/embed
/oembed/1.0/proxy
/akismet/v1
/akismet/v1/key
/akismet/v1/settings
/akismet/v1/stats
/akismet/v1/stats/(?P<interval>[\w+])
/contact-form-7/v1
/contact-form-7/v1/contact-forms
/contact-form-7/v1/contact-forms/(?P<id>\d+)
/contact-form-7/v1/contact-forms/(?P<id>\d+)/feedback
/contact-form-7/v1/contact-forms/(?P<id>\d+)/refill
/wp/v2
/wp/v2/posts
/wp/v2/posts/(?P<id>[\d]+)
/wp/v2/posts/(?P<parent>[\d]+)/revisions
/wp/v2/posts/(?P<parent>[\d]+)/revisions/(?P<id>[\d]+)
/wp/v2/pages
/wp/v2/pages/(?P<id>[\d]+)
/wp/v2/pages/(?P<parent>[\d]+)/revisions
/wp/v2/pages/(?P<parent>[\d]+)/revisions/(?P<id>[\d]+)
/wp/v2/media
/wp/v2/media/(?P<id>[\d]+)
/wp/v2/types
/wp/v2/types/(?P<type>[\w-]+)
/wp/v2/statuses
/wp/v2/statuses/(?P<status>[\w-]+)
/wp/v2/taxonomies
/wp/v2/taxonomies/(?P<taxonomy>[\w-]+)
/wp/v2/categories
/wp/v2/categories/(?P<id>[\d]+)
/wp/v2/tags
/wp/v2/tags/(?P<id>[\d]+)
/wp/v2/users
/wp/v2/users/(?P<id>[\d]+)
/wp/v2/users/me
/wp/v2/comments
/wp/v2/comments/(?P<id>[\d]+)
/wp/v2/settings

前述の方法で WP REST API を無効にすると、「サイトのURL/?rest_route=/wp/v2/users」などでアクセスすると以下のように「"code":"rest_disabled","message":"The REST API on this site has been disabled.","data":{"status":401}」と表示されます。

 WP REST API を無効後に「自分の WordPress サイトのURL/?rest_route=/wp/v2/users」で表示される画面

/wp/v2/users をリダイレクト

単純に、/wp/v2/users にアクセスできないようにするだけであれば、以下を functions.php に記述すると言う方法もあるかと思いますが、これが妥当な方法かは定かではありません。以下の場合だと、URL や クエリに /wp/v2/users が含まれている場合、ホームにリダイレクトします。

function disable_users_query() {
  if( preg_match('/wp\/v2\/users/i', $_SERVER['REQUEST_URI'])  || preg_match('/wp\/v2\/users/i', $_SERVER['QUERY_STRING']) ){
    wp_redirect( home_url() );
    exit;
  }
}
add_action('init', 'disable_users_query');;