ワードプレスは、広く利用されているソフトウェアです。
そのため、脆弱性も、容易に手に入れることができます。
ワードプレスがハッキングされた。というのは、新聞などのメディアには乗らないですが、
それなりに、被害を受けているサイトがあるかと思います。
被害を防ぐには
パスワードを複雑なものにする
ログインする場所を防御をする
というのが有効な手段かと思います。
ワードプレスのログを見ると・・・
まずは、実際のログを見てみますと・・・
XXX.XXX.XXX.XXX - - [XX/Aug/2020:01:45:16 +0900] "GET / HTTP/1.1" XXX.XXX.XXX.XXX - - [XX/Aug/2020:01:45:16 +0900] "GET /wp-includes/wlwmanifest.xml HTTP/1.1" XXX.XXX.XXX.XXX - - [XX/Aug/2020:01:45:16 +0900] "GET /?author=1 HTTP/1.1" XXX.XXX.XXX.XXX - - [XX/Aug/2020:01:45:17 +0900] "GET /?author=2 HTTP/1.1" XXX.XXX.XXX.XXX - - [XX/Aug/2020:01:45:17 +0900] "GET /wp-json/wp/v2/users/ HTTP/1.1" XXX.XXX.XXX.XXX - - [XX/Aug/2020:01:45:18 +0900] "GET /wp-json/oembed/1.0/embed?url=https://(FQDN)/ HTTP/1.1" XXX.XXX.XXX.XXX - - [XX/Aug/2020:01:45:18 +0900] "POST /xmlrpc.php HTTP/1.1"
というアクセスがありました。悪意のありそうなサーバーから、連続的にアクセスしています。
「wp-includes/wlwmanifest.xml 」は、ワードプレスプラグインの「Windows Live Writer」から情報を取得しようとしているようです。
「author=1」「author=2」は、ログインユーザ名を取得を試みています。
「/wp-json/wp/v2/users/」「/wp-json/oembed/1.0/embed」も同様にユーザ名の取得を試みています。
そして、取得した情報から、最終的に、「xmlrpc.php」にアクセスをして、ログインを試みます。
ログインに成功すると、サイトの改ざんなどが行われるかもしれません。
この手法が、すべてではなく、悪意のあるサーバーは、あらゆる手段を駆使して、ユーザとパスワードを盗もうとします。
ハッキングに必要な情報は・・
ハッキングに必要な情報は、
「ログインユーザ」
「ログインパスワード」
「ログインをする場所」
です。
それぞれ、脆弱性対策を行いましょう。
ログインユーザを隠蔽
ワードプレスにログインする場合、任意のユーザ名が設定されています。
この「ユーザ名」ですが、「私しかしらない」ということはありません。
何も対策をとっていない場合は、簡単に入手できます。
author情報
https://(FQDN)/?author=1
で、情報を取得できます。
# wget https://(FQDN)/?author=1 (略) --2020-XX-XX XX:XX:XX-- https://(FQDN)/author/adminuser/ (略)
と出力されます。上記の「auther」の後の英数字「adminuser」が、ログインユーザ名です。
xxx.xxx.xxx.xxx - - [XX/Aug/2020:08:29:54 +0900] "GET /?author=1 HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:08:29:58 +0900] "GET /?author=2 HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:08:30:01 +0900] "GET /?author=3 HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:08:30:03 +0900] "GET /?author=4 HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:08:30:07 +0900] "GET /?author=5 HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:08:30:09 +0900] "GET /?author=6 HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:08:30:10 +0900] "GET /?author=7 HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:08:30:14 +0900] "GET /?author=8 HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:08:30:15 +0900] "GET /?author=9 HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:08:30:19 +0900] "GET /?author=10 HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:08:30:20 +0900] "GET /?author=11 HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:08:30:23 +0900] "GET /?author=12 HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:08:30:27 +0900] "GET /?author=13 HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:08:30:28 +0900] "GET /?author=14 HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:08:30:30 +0900] "GET /?author=15 HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:08:30:32 +0900] "GET /?author=16 HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:08:30:34 +0900] "GET /?author=17 HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:08:30:36 +0900] "GET /?author=18 HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:08:30:38 +0900] "GET /?author=19 HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:08:30:40 +0900] "GET /?author=20 HTTP/1.1"
↑実際のログです。ある悪意のあるサーバーから、「ログインユーザ」を検索しています。
ログインユーザがわかれば、今度は、パスワードで、ログインを試みます。
このログインユーザを「隠蔽」するには・・・
↑ワードプレスプラグイン「Edit Author Slug」で設定をします。
まずは、
--2020-XX-XX XX:XX:XX-- https://(FQDN)/author/adminuser/
の「author」を変更します。
↑「Edit Author Slug」導入後、「設定」から、「Edit Author Slug」を選びます。
↑上記の「author」のところを、任意の英数字に変更します。
--2020-XX-XX XX:XX:XX-- https://(FQDN)/xxxx/adminuser/
↑上記の「xxxx」の部分が変更となります。
次に
--2020-XX-XX XX:XX:XX-- https://(FQDN)/author/adminuser/
↑の「adminuser」を変更します。
↑「ユーザ一覧」を選ぶと、右側に「投稿者スラッグ」という列が出てきます。
「編集」を選びます。
↑画面下に、「Edit Author Slug」というブロックがあり、「投稿者スラッグ」として、4つ選ぶ事ができます。今回、3つめの任意の英数字を選択します。
↑保存をして、「ユーザ一覧」に戻ると、「投稿者スラッグ」の列に、任意の英数が設定されています。
--2020-XX-XX XX:XX:XX-- https://(FQDN)/list/576sdfadfbn8fc346a2eadfa78/
↑と変更されました。
実際のログインユーザは、元のユーザ名であり、従来通りのログイン名で、入ることができます。
公開前に、このプラグインを設定すればいいのですが、
公開後に、プラグインを導入した場合は、「ログインユーザ」は、既に検索されている可能性もあります。
この場合、「ログインユーザ」を変更しましょう。
「REST API」でユーザ名取得
「REST API」
# wget https://(FQDN)/wp-json/wp/v2/users/
↑上記のURLをたたくと、ログインユーザを取得できます。
これは、「REST API」といって、wordpress標準の機能です。
「Contact Form 7」などでも利用されています。
単純に、「REST API」を止めてしまうと、「Contact Form 7」が機能しなくなります。
この場合、function.phpに、以下のソースを入れると、脆弱性が少なくなります。
add_filter( 'rest_endpoints', function( $endpoints ){ if ( isset( $endpoints['/wp/v2/posts'] ) ) { unset( $endpoints['/wp/v2'] ); } if ( isset( $endpoints['/wp/v2/posts'] ) ) { unset( $endpoints['/wp/v2/posts'] ); } if ( isset( $endpoints['/wp/v2/posts'] ) ) { unset( $endpoints['/wp/v2/posts'] ); } if ( isset( $endpoints['/wp/v2/posts/(?P<id>[\d]+)'] ) ) { unset( $endpoints['/wp/v2/posts/(?P<id>[\d]+)'] ); } if ( isset( $endpoints['/wp/v2/posts/(?P<parent>[\d]+)/revisions'] ) ) { unset( $endpoints['/wp/v2/posts/(?P<parent>[\d]+)/revisions'] ); } if ( isset( $endpoints['/wp/v2/posts/(?P<parent>[\d]+)/revisions/(?P<id>[\d]+)'] ) ) { unset( $endpoints['/wp/v2/posts/(?P<parent>[\d]+)/revisions/(?P<id>[\d]+)'] ); } if ( isset( $endpoints['/wp/v2/pages'] ) ) { unset( $endpoints['/wp/v2/pages'] ); } if ( isset( $endpoints['/wp/v2/pages/(?P<id>[\d]+)'] ) ) { unset( $endpoints['/wp/v2/pages/(?P<id>[\d]+)'] ); } if ( isset( $endpoints['/wp/v2/pages/(?P<parent>[\d]+)/revisions'] ) ) { unset( $endpoints['/wp/v2/pages/(?P<parent>[\d]+)/revisions'] ); } if ( isset( $endpoints['/wp/v2/pages/(?P<parent>[\d]+)/revisions/(?P<id>[\d]+)'] ) ) { unset( $endpoints['/wp/v2/pages/(?P<parent>[\d]+)/revisions/(?P<id>[\d]+)'] ); } if ( isset( $endpoints['/wp/v2/media'] ) ) { unset( $endpoints['/wp/v2/media'] ); } if ( isset( $endpoints['/wp/v2/media/(?P<id>[\d]+)'] ) ) { unset( $endpoints['/wp/v2/media/(?P<id>[\d]+)'] ); } if ( isset( $endpoints['/wp/v2/types'] ) ) { unset( $endpoints['/wp/v2/types'] ); } if ( isset( $endpoints['/wp/v2/types/(?P<type>[\w-]+)'] ) ) { unset( $endpoints['/wp/v2/types/(?P<type>[\w-]+)types'] ); } if ( isset( $endpoints['/wp/v2/statuses'] ) ) { unset( $endpoints['/wp/v2/statuses'] ); } if ( isset( $endpoints['/wp/v2/statuses/(?P<status>[\w-]+)'] ) ) { unset( $endpoints['/wp/v2/statuses/(?P<status>[\w-]+)'] ); } if ( isset( $endpoints['/wp/v2/taxonomies'] ) ) { unset( $endpoints['/wp/v2/taxonomies'] ); } if ( isset( $endpoints['/wp/v2/taxonomies/(?P<taxonomy>[\w-]+)'] ) ) { unset( $endpoints['/wp/v2/taxonomies/(?P<taxonomy>[\w-]+)'] ); } if ( isset( $endpoints['/wp/v2/categories'] ) ) { unset( $endpoints['/wp/v2/categories'] ); } if ( isset( $endpoints['/wp/v2/categories/(?P<id>[\d]+)'] ) ) { unset( $endpoints['/wp/v2/categories/(?P<id>[\d]+)'] ); } if ( isset( $endpoints['/wp/v2/tags'] ) ) { unset( $endpoints['/wp/v2/tags'] ); } if ( isset( $endpoints['/wp/v2/tags/(?P<id>[\d]+)'] ) ) { unset( $endpoints['/wp/v2/tags/(?P<id>[\d]+)'] ); } 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]+)'] ); } if ( isset( $endpoints['/wp/v2/users/me'] ) ) { unset( $endpoints['/wp/v2/users/me'] ); } if ( isset( $endpoints['/wp/v2/comments'] ) ) { unset( $endpoints['/wp/v2/comments'] ); } if ( isset( $endpoints['/wp/v2/comments/(?P<id>[\d]+)'] ) ) { unset( $endpoints['/wp/v2/comments/(?P<id>[\d]+)'] ); } if ( isset( $endpoints['/wp/v2/settings'] ) ) { unset( $endpoints['/wp/v2/settings'] ); } return $endpoints; });
出典元:
Turn off WordPress’s default endpoint.
これで、「404エラー(not found)」のエラーを返します。
feed情報
昔から、使われている「feed情報」
https://(FQDN)/feed/
で、アクセスをすると、
↑「creator」というところに、ログインユーザ名が表示されます。(上記は、対応済み)
これを変更するには、ワードプレスのログインユーザにおいて、
↑「ニックネーム」「ブログ上の表示名」を、ログインユーザでないものに変更をします。
インターネット上を検索すると、「feed」自体を無効にするアドバイスがありますが、google botなど、検索エンジンからfeedへのアクセスもあり、feed自体を無効にしてしまうと、SEOに悪影響があると考えています。
feedの機能は、有効にしつつ、脆弱性を高めるのは、上記の「ニックネーム」の変更がよいのではないかと思います。
ログインする場所を防御をする
XML-RPCを防御
ワードプレスでは、「xmlrpc」(XML-RPC)という、ワードプレスに、外部から、投稿ができるプロトコルが標準装備されています。
pingbackなどにも利用されていますが、今は、一般的ではないかと思います。
悪意のある外部のサーバーから
https://(FQDN)/xmlrpc.php
にアクセスをして、IDとパスワードで、総当たり攻撃(ブルートフォース攻撃)や、リスト攻撃で、パスワードを調べます。
xxx.xxx.xxx.xxx - - [XX/Aug/2020:17:16:57 +0900] "POST /xmlrpc.php HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:17:41:01 +0900] "POST /xmlrpc.php HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:18:04:53 +0900] "POST /xmlrpc.php HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:18:28:15 +0900] "POST /xmlrpc.php HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:18:52:06 +0900] "POST /xmlrpc.php HTTP/1.1" xxx.xxx.xxx.xxx - - [XX/Aug/2020:19:14:02 +0900] "POST /xmlrpc.php HTTP/1.1"
↑と、パスワードを入手しようとするサーバーからのログが記録されています。
この「xmlrpc.php」を「アクセス不可」にすることで、脆弱性対策となります。
これは、様々な方法があります。
apacheでアクセス不可
ワードプレスプラグイン、SiteGuardで、アクセス不可
nginxでアクセス不可
location = /xmlrpc.php { deny all; }
と、設定をします。
apacheでアクセス不可
<Files "xmlrpc.php"> Require all denied </Files>
と、設定をします。
ここで、パスワードが判明すると、「XML-RPC」の機能で任意のコンテンツを投稿されてしまうかもしれません。
ワードプレスプラグイン、SiteGuardで、アクセス不可
↑「XMLRPC無効化」を選びます。
そのほか、.htaccessでの制御なども可能です。(SiteGuardは、.htaccessを利用しています)
不可にすると、本来の機能が失われますので、ご留意ください。
wp-login.phpを防御
ログインする場所は、デフォルトでは、
https://(FQDN)/wp-login.php
です。
今回、SiteGuardプラグインで、防御をしてみます。
↑上記の
1)ログインページ変更
2)画像認証
3)ログインロック
を、設定してみましょう。
そのほか、IP制限、基本認証なども、有効な手段かと思います。
その他、OS,phpなどのソフトウェアも・・・
当然ながら、周辺ソフトウェア、apache,nginix,phpやOSのアップデートも、重要です。
ワードプレス脆弱対策
ワードプレスを含めて、100%の脆弱性対策はできないですが、できる限り100%に近づけたいですね。バージョンアップをすると、逆にハッキングされやすくなったりして・・・
自分におごらず、あらゆる情報をインプットして、対策を取りたいです。
関連記事
お勧めプラグイン「SiteGuard WP Plugin」で「画像ファイル、書き込み失敗」