2020年3月11日水曜日

WordPress では /?author=数字 で WordPressのユーザー名(ログインID)が漏洩してしまう件の対応について

何が問題か


WordPress では、
  • WordPressサイトのURL/?author=数字
にアクセスすることで、
  • WordPressサイトのURL/author/ユーザ名
に自動転送される仕組みになっています。
これを悪用すれば、
  • WordPress のユーザー名に割り当てられている「すべての」ユーザー名(ログインID)を知られてしまいます。
このことは
にあるように脆弱性診断システムなどで脆弱性ありと判断される場合があります。

対策しているかどうかの確認方法


コマンドラインツール curl を使えば(macOS には標準搭載)、たとえば、 http://example.com に WordPressのサイトがあるとすると
  • curl -v -D -  "http://example.com/?author=1"
と Mac ならターミナルから入力することで、標準出力にヘッダー情報が出てきます。
そこの location: をみると
  • location: http://example.com/author/ユーザー名/
になってユーザー名が見えてしまっています。これが存在しない場合、ユーザー名でない場合には何らかの対策がされているということになります。

実際にウェブサーバーログをみると、
?author=1
?author=2
?author=3
?author=4
...
など大量にスキャン(ポーリング)されているサイトもありました。

ユーザー名が漏洩したからといって即座に問題になるとは限らない


先に説明しておきますが、ログインのユーザー名が漏洩したからといって、それだけで不正ログインされるわけではありません。一般的にはパスワードが必要だからです。しかしながら、他のサイトとパスワードを同一(パスワードの使いまわし)をされていれば、もし他のサイトからパスワードが漏洩してしまうとログインできてしまいます。ですので別途ログインについては、下記のようなセキュリティ対策を施しておくのがよいです。
  • IPアドレス制限
  • 認証にクラウドサービス(Google認証等)をつかう
  • 総当り攻撃(ブルートフォース攻撃)への対策(一度に大量アクセスを防止)
などです。そうすれば、たとえユーザー名が漏洩したとしても問題はなくなります。

とはいえ、WordPress にどのユーザーがログインできているのか知られることは嫌ですよね。ですので対策したほうがよいでしょう。
ここでは

1. ウェブシステム側で止める
2. WordPress プラグインで止める(自作)

に絞って備忘録として残しておきます。

対策1. ウェブシステム側で止める


各ウェブサイトの設定において、先頭から
  • /?author=***  = 404エラーとする
  • /author/*** = トップページへリダイレクトする
これが可能なら、WordPress に到達する前に止めることができるのでウェブサイトへの負荷が減ります。
/author/** のケースは、 /hogehoge/author/** を除外するかどうかが、ややこしくなります。たとえばカテゴリー名に使った場合とかです。とはいえ、author は予約語でもあるので、まぁそこは除外するでよいでしょう。

Apache での設定方法


WordPress側では、RewriteBase が設定されているはずなので
  • RewriteRule ^\?author=(.*)? / [NC,R=404,L]
  • RewriteRule ^author/(.*)? / [R=302,L]
の2つを追加すればよいです。
管理者画面側では、?hogehoge=**&author=**
となるので、管理者画面では除外できるはずです(未検証)。
うまくいかなければ、RewriteCond ルールなどを併用すればいいでしょう。筆者は NGINX しかほぼ触っていないので、詳細は

NGINX での設定


nginx は if 文など条件を入れ子構造にできないという問題があります。
したがって細かな条件を書くのが超大変です。
ここではドメインやサブドメイン直下(http://example.com やhttp://hogehoge.example.com/) のように http://example.com/hogehoge というサブディレクトリに保存されていないと仮定すると、汎用性がある設定としては、

set $rp_flag  flag;   
set $rp_flag2 flag; 

if ($request_uri ~ "^/wp-admin/"){
        set $rp_flag false;
}
if ($request_uri ~ "^/author/"){
        set $rp_flag2 "${rp_flag2}_true";
}
if ($args ~ "author=(.*)"){
        set $rp_flag "${rp_flag}_true";
}
if ($rp_flag = flag_true){ 
        return 404;
}
if ($rp_flag2 = flag_true){
        return 302 /;
}


上記コードでは、

1. /wp-admin/ (管理画面)は対象外(リダイレクトしない)
 *つまり公開している側のみリダイレクトする
2. /author/ か GETパラメーターに author= があるかのいずれか
上記をすべて満たす場合に、トップページへリダイレクトする

ということになります。
「1」が必要な理由は、管理画面の投稿や固定ページ等で各ユーザーごとに一覧を表示するのに、author パラメーターが使われているためです。それは必要だろうという判断です。

このように、フラグを文字連結させた上で、それを文字判定させる手法になります。
さらに、requiest_uri に ? があるとパラメータ扱いになって、$args に保存されるという仕様もあってなおややこしい。

参考:

対策2. WordPress プラグインで止める(自作)



ウェブサーバーのほうで止めることができない場合(設定をいじれない)もあると思います。その場合には、テーマの functions.php に書くなどの方法もありますが、サイトが大量にあるとやってられません。
公式プラグインだと WP-CLIコマンドなどで一括インストールが可能になるので、その設定をしていれば、
  • wp @all  plugin install  プラグイン名  --activate
プラグインを作成してそれを公式プラグインに登録しちゃえばよいのです。
幸い筆者は WordPress の公式プラグインをいくつかアップして、メンテナンスしているのでそのあたりのやり方は知ってます。

ただ問題はどのような手法で止めるのかがかなり悩みました。

など速度低下が著しい模様。wp_safe_redirect をつかうのも同様じゃないかなぁと思います。そこで結局は
で提示された方法を採用しました。また /author/ の場合を追加して、その場合にはトップへリダイレクトすることにしました。author=数字 は明示的に攻撃しているといってもいいですが、 /author/ はもしかして誤って入力したかもしれず、表示されないよりはリダイレクトにしたほうがいいかなぁという想いです。つまりは、
  1. 管理画面以外(公開されている部分のみ)に適用
  2. QUERY_STRING に「author」項目がある = 404エラー
  3. REQUEST_URI に /author/ があること =  トップへリダイレクト
  4. redirect_canonical にも対応し、自動補正時にもチェックが走るようにする
という感じです。

2020年3月11日 @kimipooh