まずはWordPressをインストールするのだが、このインストール時点での各種設定を工夫することで、「WordPressっぽさ」をかなり排除できるのだ。WordPressのスタートアップでは、このフェーズが「導入敷居を低くする」という方向性に寄っているきらいがあって、すっ飛ばしてしまう人も多いので、今回のチャレンジではとことん突っ込んでやってみる。
 
 基本的に、どんなディストリビューションでもそうだが、インストールするバージョンは最新(の安定版)である必要がある。既知の不具合やセキュリティホールが含まれていないバージョンをベースにしないと、いくら施策してもセキュリティが担保されないからだ。
 ちなみに、このチャレンジを開始した時(2020年1月30日時点)の最新安定版は 5.3.2 である。

 ここで重要になるのが、WordPressのインストールパスだ。WEBサイトのホームURLとなるサーバのドキュメントルート直下ではなく、その一つ下のディレクトリにインストールすることが重要だ。今回はドキュメントルート直下に app というディレクトリを作成して、その中にWordPressをインストールしている。
 なお、インストール方法などの詳細はいたるところで紹介されているので割愛するが、コマンドラインからだと wp-cli を利用しておおむね下記のようになる。

$ cd {Document_Root_Path}
$ wp core download --locale=ja --version=latest --path=app
Downloading WordPress 5.3.2 (ja)...
md5 hash verified: e3a44f56b756b731042999b5af83d434
Success: WordPress downloaded.
$ cd app
$ wp config create --dbname={Database_Name} --dbuser={Database_User} --prompt=dbpass --dbhost=localhost --dbprefix={Table_Prefix}_ --dbcharset=utf8mb4
1/10 [--dbpass=<dbpass>]: {Database_Password}
**Success:** Generated 'wp-config.php' file. 
$ wp db create
**Success:** Database created.
$ wp core install --url=anonymous.example.com --title=Anonymous --admin_user=admin_user --admin_password=admin_passwd --admin_email=test@anonymous.example.com
Success: WordPress installed successfully.

 なお、WordPressインストール時の注意点として、データベースのテーブル接頭辞(プレフィックス)をデフォルト値のwp_ではなく、きちんと任意の値で設定しておくこと。
 また、コマンドラインではなく、GUIからインストールするのであれば、「検索エンジンでの表示」にチェックを入れて検索エンジンにサイトをインデックスされないようにしておくと良い。なぜ検索エンジンへのインデックスを抑止するかと言うと、サイトを公開するまでの秘匿化作業中にインデックスされたキャッシュによっては、公開時にこのサイトがWordPressサイトであることが既に攻撃者に露見しているということもあり得るからだ。
 まぁ、WordPressの「検索エンジンでの表示」オプションの信頼性はかなり低い(HTMLのヘッダ内に <meta name="robots" content="noindex,nofollow"> が追加されるだけ)ので、気休め程度に考えておいた方が良い……w
 できれば、サイト公開までの作業中はパブリックなネットワークから切り離されたクローズドな環境で行うか、ローカルPC上で行った後に公開直前にデプロイする等が最良だろう。

wp-config.php の初期設定

 さて、インストールが終わったら、さっそく秘匿化の第一弾を行っていこう。
 まずやるべきことは、WordPressの設定ファイル wp-config.php をインストールディレクトリ直下から移転させることだ。これは秘匿化よりもセキュリティ向上の恩恵が大きいので、私がWordPressでサイトを構築する際は必ずやっていることでもある。

 WordPressでは、インストールされたディレクトリ直下に wp-config.php ファイルがない場合、その一つ上のディレクトリも探してくれる。つまり、WordPressのインストールディレクトリがドキュメントルートの場合、 wp-config.php を一つ上の階層に作成しておくだけで、ディレクトリトラバーサル(パス遡上)攻撃への対策が施された環境(php.iniにてopen_basedirを厳格に設定している等)であれば、設定ファイルへの不正なアクセスを防ぐことができる。

 私の場合、WordPressをはじめとした各種アプリケーションの設定ファイルを一ヶ所に集約して管理しておきたいということもあって、サーバの特定アカウントのホームディレクトリ等に ~/.config/{ドメイン名やアプリケーション名}.cnf のように何の設定ファイルなのかが一目でわかるように置いている。WordPressの設定ファイルなら、サイトのドメイン名を付けたファイルとして実体をこちらに置いて、WordPressが参照するwp-config.phpのパスにはその設定ファイルへのシンボリックリンクを貼っている。参考までにそのやり方の手順を紹介しておく。

$ cd {WordPress_Install_Path}
$ mkdir -p ~/.config
$ mv wp-config.php ~/.config/{WordPress_Site_Domain_Name}.cnf
$ cd ..
$ ln -s ~/.config/{WordPress_Site_Domain_Name}.cnf wp-config.php

 ただし、今回のチャレンジでは、WordPressのインストールパスがドキュメントルートより一つ下のディレクトリとなっているので、ドキュメントルートより上層にwp-config.phpを置くというセキュリティ向上策は取れない。そのため、wp-config.phpファイルの防護策は後述の.htaccessの設定でおこなうことになる。
 また、今回はWordPressのインストールパスがドキュメントルートより下のサブディレクトリのため、wp-config.phpに下記の設定を追加する。

define( 'WP_SITEURL', "https://{$_SERVER['HTTP_HOST']}/app" );
define( 'WP_HOME', "https://{$_SERVER['HTTP_HOST']}" );

 これは、WordPressの管理画面から[設定]-[一般]から「WordPressアドレス(URL)」と「サイトアドレス(URL)」を設定するのと同義なのだが、wp-config.phpで指定しておくと管理画面側で設定されてDBに保存したこれらの設定値を常にオーバーライドしてくれるので、URLが異なる試験環境と公開環境をDBに依存せずに管理できたりして何かと都合が良いのだ。

 次に、このままだとWordPressサイトのホームURLが {ドメイン名}/app のままなので、ドメイン名のみでアクセスできるように変更する。

$ cd {Document_Root_Path}
$ ln -s ./app/index.php index.php

 これでブラウザからドメイン名のみのURLでアクセスしてサイトが表示されればOKだ。

wp-content ディレクトリの変更

 次に、WordPressのディレクトリ構成を変更していく。最も重要なのが、wp-content ディレクトリの名称変更だ。
 WordPressでは、テーマやプラグイン、アップロードされた各種ファイル等はこのディレクトリ下の指定のフォルダに格納されている。そのため、WEBサイトのHTMLを覗かれて、それらリソースのファイルパスに /wp-content/ が含まれていると、そのサイトがWordPressで構築されたことが一発でバレてしまうのだ。
 また、もしテーマやプラグインにセキュリティホールがあったとしても、 wp-content ディレクトリ名が違う名前であれば、特定テーマなどをファイルパス決め打ちで攻撃されたとしても途中のパスが違うので回避できるようになり、セキュリティ的なメリットも若干ながら享受できる。
 というわけで、今回は wp-contentassets に変更する。

$ cd {Document_Root_Path}
$ ln -s ./app/wp-content assets

 ディレクトリ名を変更した後は、設定ファイル wp-config.php に下記の設定を追加することで、WordPressが新しいディレクトリ名を使用してくれるようになる。

define( 'WP_CONTENT_DIR', "{$_SERVER['DOCUMENT_ROOT']}/assets" );
define( 'WP_CONTENT_URL', WP_HOME . '/assets' );

プラグインディレクトリの変更

 さらに、プラグインディレクトリの名称も変更する。これによって、WordPressサイトで定番として広く使われているプラグインが読み込んでいるCSSやJS等のリソースファイルのパスからWordPressの存在を勘繰られる可能性を少なくできる。
 というわけで、今回はプラグインディレクトリ wp-content/pluginsaddons にしてみる。

$ cd {Document_Root_Path}
$ ln -s ./app/wp-content/plugins addons

 ディレクトリ名を変更した後は、設定ファイルに新しいディレクトリパスを宣言する。

define( 'WP_PLUGIN_DIR', "{$_SERVER['DOCUMENT_ROOT']}/addons" );
define( 'WP_PLUGIN_URL', WP_HOME . '/addons' );

 実際に管理画面からプラグインの一覧を表示してみて、デフォルトでバンドルされている「Akismet」や「Hello Dolly」のプラグインが表示されればOKだ。試しに「Hello Dolly」あたりを有効化して問題が出なければ万全だ。

 なお、プラグインによってはプラグインディレクトリのパスをビルトイン関数やグローバル定数を参照せずに、PHPソース内にハードコーディングされているものもある。そういうプラグインは、この変更によって動作しなくなるので、注意が必要だ。まぁ、そういう可用性の低いプラグインは使わないというのが賢明かもしれない。

テーマディレクトリの変更

 テーマディレクトリの変更は、コンテンツディレクトリやプラグインディレクトリのようにグローバルな定数が準備されていない。いや、正確には TEMPLATEPATHSTYLESHEETPATH があるのだが、これはそれぞれビルトイン関数の get_template_directory()get_stylesheet_directory() のラッパーなので、WordPressが初期化される(=WordPress上のページを表示する)たびに変動する可能性があるのだ。そのため、テーマディレクトリの変更にはテーマ側かプラグイン側によるフィルタリングが必要になってくる。
 詳しくは、次章以降のテーマのカスタマイズ時に行うので、一旦スルーする。

uploadsディレクトリの変更

 WordPressの管理画面などからアップロードされた画像などのファイルは wp-content/uploads/ ディレクトリ内に格納される。今回既に wp-content/ ディレクトリは assets/ に変更してあるので、アップロードディレクトリも assets/uploads/ になっているのだが、このディレクトリも変更しておくとさらに安心だ。
 今回は public に変更してみる。

$ cd {Document_Root_Path}
$ ln -s ./app/wp-content/uploads public

 ディレクトリを変更したら、設定ファイルに新規パスを追加する。

define( 'UPLOADS', '../public' );

 しかし、これだけだとWEBページにアップロードファイルが表示された際に、そのファイルのURLが /app/../public/{ファイル名} のようになってしまい、WordPressのインストールディレクトリである app/ ディレクトリの存在が露見してしまう。何よりダサいので、対処が必要だ。ただし、この対応には、テーマやプラグイン側でのURLフィルタリングが必要になるため、現時点ではとりあえずスルーしておく。詳細はテーマのカスタマイズの章で説明する。

WordPress関連ファイルへのアクセス制限

 WordPressのインストールディレクトリ内に存在しているファイルやディレクトリは、そのサイトがWordPress建てであることを端的に示してしまうものばかりである。例えば静的なリソースであるlicense.txtreadme.htmlなどは、特に対策をしていないとブラウザから直接URLでアクセスできて、そのサイトがWordPressサイトであることがバレてしまう。Webサイトを運営する上では全く必要としないリソースでもあるので、削除してしまっても良いが、サイト稼働後も継続的にWordPress自体のアップデートしていくにあたって、その都度またインストールされてしまうファイルでもあるので、毎回いちいち対応するのは面倒であり、対応が漏れた時に秘匿化効果がなくなってしまうリスクもある。
 これらのファイルは、.htaccessを使って、WEBサーバ側でその存在を隠蔽するアクセス制限をしていく。

 WordPressサイトにおけるコアファイル関連のアクセス制限を紹介している記事は多いので、WordPressサイト向けの .htaccess 設定はよく見かける。しかしながら、それらはどれも対象ファイルやディレクトリへのリクエストについて「アクセス許可を与えない」形式でのやり方であり、これでは 「対象ファイルが存在する」ことを完全には隠し切れていない。セキュリティ的にはそれでも問題はないのだが、今回のチャレンジでは、主題である「秘匿化」の方向性を貫きたいので、WordPress関連のファイルは、HTTP CODEとして404を返すようにしてみる。

 まずは、WordPressのサイトURLのホームディレクトリ(今回のチャレンジではドキュメントルート)に .htaccess ファイルを作成する必要がある。WordPressはインストール時に .htaccess を生成しないので、自分で準備する必要があるのだ。

$ cd {Document_Root_Path}
$ touch .htaccess
$ chmod 604 .htaccess

 作成できたら、ブラウザからWordPressの管理画面にログインして、[設定]-[パーマリンク設定]を開き、「変更を保存」をクリックする(他には何も設定しなくても構わない)。これで、初期パーマリンク設定が .htaccess に書き込まれたはずだ。

 では、WordPress関連ファイルの隠蔽工作を実施していく。
 まず.htaccessを編集する前に自ホストのFQDNやIPアドレスを調べておこう。

$ hostname -A
FQDN1.example.com FQDN2.example.com
$ hostname -I
123.123.1.1 123.123.1.2

 これらのホスト名やIPなどをアクセスを許可するホストとして設定していけば良い。この辺りは運用する環境によって差が大きいので、適宜調整が必要だ。
 なお、設定はWordPressによって自動挿入された # BEGIN WordPress 〜 # END WordPressディレクティブより前に追加すること。

<IfModule mod_rewrite.c>
RewriteEngine On
# ---
# 許可する接続元の定義
# ---
# アクセスを許可する接続元IP&ホストの定義: is_allow
SetEnvIf Remote_Host "^FQDN(1|2)\.example\.com$" is_allow=true
SetEnvIf Remote_Addr ^123.123.1.                 is_allow=true
# SetEnvIf Remote_Addr ^192.168.                 is_allow=true
# SetEnvIf Remote_Addr ^127.0.0.                 is_allow=true
SetEnvIf Remote_Host ^localhost$                 is_allow=true
# アクセスを許可するリファラーの定義: is_allow_referer
SetEnvIf Referer "^https?://(|www\.)example\.com/" is_allow_referer=true
# ---
# 1. 静的リソースは接続元に依らず、固定的に404レスポンスを返す
# ---
RewriteRule (license.txt|readme(|-ja).html)$ - [R=404,L]
# ---
# 2. ドキュメントルート直下のwp-admin/、およびwp-login.phpは、
#    リダイレクトさせず、404レスポンスを返す
# ---
RewriteRule ^wp-admin/(.*)$ - [R=404,L]
RewriteRule ^wp-login.php(.*)$ - [R=404,L]
# ---
# 3. ドキュメントルート直下のwp-config.phpは、
#    接続元が許可ホスト以外の場合は、404レスポンスを返す
# ---
RewriteCond %{ENV:is_allow} !^true$
RewriteRule ^wp-config.php$ - [R=404,L]
# ---
# 4. インストールディレクトリ直下の.phpファイルへのアクセスは、
#    wp-login.php、xmlrpc.phpなど特定のファイル以外の場合、
#    接続元が許可ホスト以外、かつ許可リファラー以外の場合は、404レスポンスを返す
# ---
RewriteCond %{ENV:is_allow} !^true$
RewriteCond %{ENV:is_allow_referer} !^true$
RewriteCond %{REQUEST_FILENAME} -f
RewriteCond %{REQUEST_FILENAME} !(wp-login|xmlrpc).php$
RewriteCond %{REQUEST_FILENAME} !wp-admin/(options(|-general)|profile).php$
RewriteRule ^app/.*?\.php$ - [R=404,L]
# ---
# 5. インストールディレクトリ直下の各ディレクトリへのアクセスは、
#    接続元が許可ホスト以外、かつ許可リファラー以外の場合は、404レスポンスを返す
# ---
RewriteCond %{ENV:is_allow} !^true$
RewriteCond %{ENV:is_allow_referer} !^true$
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^app/.*?/?(.*)$ - [R=404,L]
# ---
# 6. xmlrpc.phpは後々専用の制限を追加するので個別設定(暫定的)
# ---
RewriteCond %{ENV:is_allow} !^true$
RewriteCond %{ENV:is_allow_referer} !^true$
RewriteCond %{REQUEST_FILENAME} -f
RewriteCond %{REQUEST_FILENAME} xmlrpc.php$
RewriteRule ^app/xmlrpc.php(.*)$ - [R=404,L]
# ---
# 7. wp-login.phpは後々専用の制限を追加するので個別設定(暫定的)
# ---
#RewriteCond %{ENV:is_allow} !^true$
#RewriteCond %{ENV:is_allow_referer} !^true$
#RewriteCond %{REQUEST_FILENAME} -f
#RewriteCond %{REQUEST_FILENAME} wp-login.php$
#RewriteRule ^app/login.php(.*)$ - [R=404,L]
</IfModule>

 わかりやすいようにコメント欄に設定タイプごとに番号を振っている。
 まず、1. 〜 3. の設定はこの時点で確定でよい。4. 以降の設定はインストールディレクトリ名に応じて適宜書き換える必要がある。また、エンドユーザをユーザ登録させたり、複数の管理ユーザにプロフィール変更を許可するようなサイトの場合、都度メールによるアクティベーションが発生するため、wp-admin/ の特定URLへのアクセスを許す必要も出てくる。
 つまりは、構築するWebサイトのインタラクティブ性に応じてアクセス制限が複雑かつ甘くなり、さらにはWordPressの秘匿性も低下していくということを認識しておく必要がある。

 さて、設定が有効になっているかは、ブラウザ側からインターネット経由で秘匿化対象となった各種ファイルやディレクトリに直接URLでアクセスして、期待した404レスポンスが返ってくるかを確認する必要があるのだが、対象ファイル数がかなり多くて確認するのがとても面倒だ。そこで、指定サイトがWordPressサイトとしてのレスポンスを返すかどうかを一括でチェックできるチェッカーを作ってみた。

https://ka2.org/wp-checker.php

 上記URLで調べたいサイトのURL(WordPressのインストールパスのURL)を入力するだけだ。あとは自動でそのサイトのドキュメントルートまで遡ってWordPressコアファイルのHTTPレスポンスを検証してくれる。

 この時点での設定では、

  • ドキュメントルート直下のindex.phpのHTMLソース内にWordPressを匂わす記述が含まれている
  • インストールディレクトリ直下にwp-login.phpが存在している

──との2点が警告されるはずだ。この警告への対応は、次章以降で行なっていく。

 ここまでがWordPress秘匿化の第1章である。この時点で構築されたWordPressサイトの構造は下記のようになっている。

/(DOCUMENT_ROOT)
 ├ addons/ (200 Ok)
 ├ app/ (404 Not Found)
 ├ assets/ (200 Ok)
 ├ public/ (403 Forbidden)
 ├ .htaccess (403 Forbidden)
 ├ index.php (200 Ok -> WordPressサイトが表示)
 └ wp-config.php (404 Not Found)

 この構造とHTTPレスポンスから、このサイトがWordPress建てであるということを推測するのは容易ではないと思う。

 次章ではテーマをカスタマイズして、表示されるページのHTMLソースコードからもWordPress臭を消していく。