はじめに
六回目となった今シリーズですが、今回でシリーズはラストとなりました。
最後を締めくくるのはWordPressのセキュリティ設定についてです。
WordPressは自由度の高いウェブサイト構築を助ける便利なツールですが、その反面、脆弱性だらけで多くのセキュリティリスクを有しているとも言われています。
しかし、それは利用者自身の使い方にも依存しているため、リスクは良くも悪くも変化します(インストール当初からある程度固めておくのが良いとは思います)。
つまり、ある程度の対策を行っておくだけで、その危険性を大きく排除することが可能です。
WordPressの公式ドキュメントにも対策方法が掲載されているので、閲覧するだけでなく一度目を通して可能な設定はやっておくことをおすすめします。
- WordPress の安全性を高める
この記事ではWordPressを使う際にやっておくべきセキュリティ対策をいくつか紹介していきます。
また、できるだけWordPressを使用していないように見せかける方法を採用しています。
これまでのシリーズ一覧は以下のとおりです。
- Ubuntu20.04のInstallおよびSSH、FTP設定【第一回】
- Nginxの導入および設定&セキュアなHTTPS化【第二回】
- Rust製SSG、Zolaの導入および各種設定【第三回】
- Nginxリバースプロキシで静的コンテンツ専用サーバを作成【第四回】
- WordPress&MariaDBの導入および高速化設定【第五回】
- サイトの安全性を更に高めるWordPressセキュリティ設定【第六回】
- https://www.pr1sm.com/web/wordpress-security-settings/
なお、このシリーズではホスティングにVultrを使っています。
Vultr
サーバのスペックおよび構成は以下の通りです。
$ cat /etc/lsb-release
- DISTRIB_ID=Ubuntu
- DISTRIB_RELEASE=20.04
- DISTRIB_CODENAME=focal
- DISTRIB_DESCRIPTION="Ubuntu 20.04.3 LTS"
Vultr : High Frequency Server
- 32GB MVMe
- 1CPU
- 1GB Memory
- Additional Features
- Virtual Private Clouds: ON
インストールするルートを変更する
WordPressでは公開ディレクトリ直下にインストールされるケースがありますが、セキュリティ的にはよくありません。
理由として、WordPressの関連ファイルに機械的なアクセスを行いやすいからです。
例えばexample.com
直下にWordPressをインストールした場合、example.com/wp-config.php
というように、WordPress関連のディレクトリ(wp-admin
、wp-content
、wp-includes
)やファイル(wp-config.php
、wp-login.php
など)にすぐアクセスすることができます。
しかし、example.com/wp/wp-content
のようにディレクトリを一つずらしてインストールしたならば、上記のようなアクセスを弾くことができます。
とはいえ、必要のないファイルにわざわざアクセスする者は単純ではないので、これで大きくセキュリティ対策ができるわけではありませんが、少しでも脅威を排除するためにやっておいて損は無いでしょう。
公開ディレクトリではなくサブディレクトリにWordPressを新規インストールする手順は、前回の記事で公開しています。
- WordPress&MariaDBの導入および高速化設定【第五回】
上記記事ではドメイン名/apps/
にWordPressをインストールしつつ、表示上はドメイン名
のみで行えるようにしています。
この設定では/apps/
ディレクトリは存在しているため、直接アクセスするとステータスコードは200を返します。
そのため、/apps/
ディレクトリへアクセスして欲しくない場合、これまでのシリーズと同様にNginxで設定を行って制御を行います。
$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
で設定ファイルを編集します。
## 省略 ##
# /apps(/)にアクセス時、"/?access=apps"へ内部リダイレクトする
location ~ /apps/?$ {
rewrite ^/ /?access=apps? last;
}
## 省略 ##
設定後は$ sudo service nginx restart
でNginxを再起動します。
これにより、/apps/
ディレクトリにアクセスされても他の404ページと同様の動きが行われます。
ブログ上の表示名を変更する
WordPressでウェブサイトを新規に作成する際、ユーザ名とパスワードの入力を行ったのを覚えているでしょうか。
この時に登録したユーザ名は、セキュリティ上の理由から別のニックネームへと変更しなければなりません。
ユーザ名は管理ページへのログインの際に使用される非常に重要な情報であり、ユーザ名が特定されてしまえば後はパスワードだけでログインすることが可能です。
対策していないサイトでのユーザ名の特定は非常に簡単で、ウェブサイトURLの末尾に?author=1
というようなクエリ文字列を付けるだけで簡単に判明します。
そのクエリ文字列を付けた場合、URLは自動的に記事の投稿者ページに移動しますが、ユーザ名を変更していない場合はそれがログイン用のユーザ名となっています。
これを変更するにはログイン後、メニュー一覧のユーザー
からプロフィール
を選択し、対応するユーザを選択して新しいニックネーム
を付けることです。
ニックネームの追加後、ブログ上の表示名
に追加した名前がタブに増えているので、それに変更して保存します。
これにより、投稿者名が別の名前に変更されたため、ユーザ名を特定することができなくなりました。
複数のユーザを登録している場合も同様ですので、すべて変更しておくことを強くおすすめします。
ただし、ユーザ名がadmin
やlogin
、root
等の分かりやすい名前にするのはまったくおすすめしません。
なお、他にもfunctions.php
に下記コードを追加することでauthor
に関連するURLを無効化することができます。
functions.php
/** 省略 **/
// 投稿者アーカイブを無効化してWordPressのユーザ名を隠す方法
// https://blog.webcontent.jp/entry/no-author-archive
// 投稿者アーカイブを無効化
add_filter( 'author_rewrite_rules', '__return_empty_array' );
// "?author=値"もしくは"/author/文字列"を含んだURLの場合、404ページに内部リダイレクトする
function disable_author_archive() {
if( $_GET['author'] || preg_match('#/author/.+#', $_SERVER['REQUEST_URI']) ){
// "/author/文字列"を含んだURLの場合
if(preg_match('#/author/.+#', $_SERVER['REQUEST_URI'])){
// ステータスコードを404に変更する
status_header( '404' );
}
// トップページにリダイレクトさせる
// wp_safe_redirect( home_url() );
// テンプレート内の404ページにリダイレクトする
include( get_query_template( '404' ) );
// 処理を停止する
exit();
}
}
// "init"に対してアクションフックを行う
add_action('init', 'disable_author_archive');
/** 省略 **/
上記方法では投稿者アーカイブを無効にし、?author=値
もしくは/author/文字列
を含むURLにアクセスがあった場合、テンプレートの404ページ(もしくはトップページ)にリダイレクトさせます(/author/文字列
では更にステータスコードを404に変更)。
しかしながらクエリ文字列の場合、通常はページに変化が無いものなので、特定URLにアクセスした時にトップページへリダイレクトしたり404ページに移動するような仕様は、相手からするとWordPressを使っていることが丸わかりです。
そのため、より自然な動作を演出するには、サーバ側で特殊なリダイレクト処理を挟む必要があります。
当シリーズではNginxを使用しているため、$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
で設定ファイルを編集します。
ドメイン名.conf
## 省略 ##
## if文はlocationの外に設置(意図しない動作をするため)##
# "?author=値"にアクセス時、"/?access=author"へ内部リダイレクトする
if ($args ~ "author=.*") {
rewrite ^/ /?access=author? last;
}
# "/author/"を含むURLにアクセス時、"/?access=author"へ内部リダイレクトする
location ~ /author/ {
rewrite ^/ /?access=author? last;
}
## 省略 ##
上記コードを用いた場合、?author=1
を加えたURLにアクセスすると、URL表示上では変化がありませんが、内部ではホスト名?access=author
に変換しています(example.com/?access=author
)。
2つ目のコードも同様に、/author/
を含むURLにアクセスすると、URL上は変化が無いまま、内部ではホスト名?access=author
として処理されています。
URL表示上は変化させずに内部リダイレクトを行うことにより、できるだけ違和感無くアクセスを弾くことができます。
また、上記Nginxのコードを用いれば、先程functions.php
に記述したauthor
用のコードは必要ありません(add_filter( 'author_rewrite_rules', '__return_empty_array' );
は必要です)。
なお、アクセス時のヘッダを調べたい場合は、curl 調べたいURL -I -v
を使うことで各種ヘッダ、curlがアクセスした際のhttpsの処理およびヘッダを見ることができます。
Nginxでは内部リダイレクトした場合、クライアントにはその情報が基本的に知らされないため、curlを用いたチェックでも、表示上のURLと同じ内容を返すようになっています。
wp-config.phpを移動する
WordPressにとって非常に大切な設定ファイルであるwp-config.php
は、セキュリティ上でも最上位レベルの重要さを持っています。
このファイルはデータベースのユーザ名やパスワード等のコアとなる設定が書き込まれているため、これを悪意のある第三者が利用できた場合、あなたのウェブサイトは完全に破壊されてしまいます。
初期設定ではWordPressのインストールディレクトリ直下にwp-config.php
が設置されています。
ありがたいことにwp-config.php
はインストールディレクトリ直下だけでなく、その一つ前のディレクトリに設置しても機能することが可能です。
なお、最初に説明したインストールディレクトリの変更を行っている場合、一つ前に移動しても公開ディレクトリ直下になるだけなので、この方法を使用することはできません。
ただ、例として公開ディレクトリ直下にWordPressをインストールした場合、現在の場所をフルパスで表現すると/var/www/example.com/wp-config.php
ですが、これを/var/www/wp-config.php
に移動することができます。
これにより公開ディレクトリにはwp-config.php
が設置されていないため、ブラウザでのアクセスなど、外部からはこのファイルを認識することができなくなります。
やり方も簡単で、FTPで移動できるならそのまま前のディレクトリに移動すれば良いだけですが、パーミッションによる影響などですぐに移動できない場合は$ sudo mv /var/www/example.com/wp-config.php /var/www/wp-config.php
を実行することでファイルを移動できます。
ファイル移動後にウェブサイトを閲覧できる状態ならば、これで作業は完了です。
今シリーズでの設定の様に、公開ディレクトリ直下にWordPressをインストールしていない場合は、ファイルのパーミッションを制御することで対策します。
パーミッションによる制御
WordPressではとても多くのファイルを扱っているため、これらファイルのパーミッション(アクセス権限)を誤ってしまうと何らかの被害が出る可能性があります。
そのため、正しいパーミッション設定を行って不正アクセス対策をしておく必要があります。 基本的な事として、あらゆるファイル(およびディレクトリ)には、所有者(user:group)と権限(user:group:other)が存在しています。 所有者はそのファイルに対しての所有権を持ったユーザであり、 WordPressでの設定では、 権限はそのファイル(およびディレクトリ)に対し、読み取り( 数字は権限を加えるごとに足していく仕組みになっており、読み取りでは 例えば 一方、 ちなみに権限が自由に割り当て可能だとしても、誰もが操作可能となる なお、権限はこれら以外にも、親ディレクトリの権限を継承するSet User ID( では実際のパーミッションですが、これまでのシリーズでは所有者に コマンドで実行するには、 そして権限にはディレクトリが ディレクトリに対する権限設定をコマンドで実行するには 権限に ファイルに対する権限設定をコマンドを実行するには ただし、これら権限はSFTPユーザも読み書き可能にするという柔軟さを重視したものであって、SFTPユーザは閲覧のみで編集させる必要が無い場合は、ディレクトリが もしSFTPユーザに一切権限を与えない(SFTPを使わない)場合には、所有者をnginx:nginxに変更し、権限はディレクトリが なお、一部レンタルサーバなどの共用サーバでは、他の利用者とグループが同じに設定されているケースもあるため、この場合は上記と同じくディレクトリに 前のセクションにてパーミッション設定を行いましたが、これらとは異なるパーミッションを設定した方が良いファイルもあります。 それは シリーズではNginxを主に扱っていますが、念の為にApatchでは これらはWordPressにとって非常に大切なファイルであり、より厳格にパーミッションを設定した方が望ましいです。 外部からのアクセスを完全に排除したいならば、サーバ側でアクセス禁止を設定する方法もあります。 設定後は そしてファイルそのもののアクセスを排除する場合は以下を記述します。 なお、Apache2.4からはパーミッションの基本
user
とgroup
の2つに分けられます。user
としてApatchやNginxなどのウェブサーバを実行するユーザ、group
には(S)FTPを使うユーザなど、複数の利用者が使用するための共通グループが割り当てられます。Read
)書き込み(Write
)実行(Execute
)権限を0から1を使った数字で表し、user
、group
、other
の順にそれぞれ割り当てます(other
はuser
とgroup
以外のユーザ)。4
、書き込みでは2
、実行では1
となっています。755
の場合、user
には7
が割り当てられ、これは読み取り、書き込み、実行の権限が与えられています。group
とother
には5
が割り当てられていますが、これは読み書きが可能なものの、実行(Execute
)はできません。777
を設定することはセキュリティ上全くおすすめしません。4000
)およびSet Group ID(2000
)というシステムや、スティッキービット(1000
)と呼ばれる所有者とrootユーザのみが削除を行える特殊な権限があります。パーミッションの設定
nginx
:SFTPユーザ名
が設定されています。これは使用者の環境によって変更してください。$ sudo chown -R nginx:SFTPユーザ名 /var/www/ドメイン名
を使用します。ドメイン名
の箇所はウェブサーバの公開ディレクトリを指定します。775
、ファイルに664
が割り当てられます。$ sudo find /var/www/ドメイン名 -type d -exec chmod 2775 {} \;
を使用します。$ sudo find /var/www/ドメイン名 -type d -print | xargs chmod 2775
を使用してください。2000
を足すことでSet Group IDが適用され、そのディレクトリ内で生成されるファイルおよびディレクトリの権限は同一グループに継承されます。$ sudo find /var/www/ドメイン名 -type f -exec chmod 664 {} \;
を使用します。$ sudo find /var/www/ドメイン名 -type f -print | xargs chmod 664
を使用してください。755
、ファイルは644
を割り当てます。705
、ファイルには604
を割り当てます。705
、ファイルに604
を割り当てる必要があります。重要ファイルのパーミッション
wp-config.php
です。.htaccess
(このファイルはApatchのみ)も重要とされるファイルです。wp-config.php
では権限に600
、より厳格にするならば400
に設定します。$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
で編集を行います。ドメイン名.conf
## 省略 ##
# "location ~ \.php$"より上に置くこと
# "wp-config.php"へのアクセスを拒否する
location ~ wp-config\.php$ {
# すべての外部アクセスを拒否する
deny all;
}
## 省略 ##
$ sudo service nginx restart
でNginxを再起動します。.htaccess
で権限を設定する場合は606
、パーマリンク設定が不要ならば604
に設定します。.htaccess
<Files ".ht*">
Require all denied
</Files>
Allow
、Deny
、Order
、Satisfy
ディレクティブは非推奨となっています。
ログインURLを変更する
未対策のWordPressでは、wp-login.php
にアクセスすると誰でもログインページに移動することができます。
それだけでなく、下記のコードによるとWordPressを使用しているサイト内でwp-admin
、dashboard
、admin
、login
という文字列をURLに加えただけでも、自動的にログインページへリダイレクトされます。
- wp_redirect_admin_locations()
誰でもログインページを見ることができるのは当然良いとは言えず、推測での自動リダイレクトは便利な側面があるものの、セキュリティで考えるとよろしくありません。
悪意のある者の目に触れさせないためにも、ログインページという大変重要な場所は、できる限り隠蔽することが望ましいです。
今回はログイン用のファイルを新しく作成し、functions.php
にそれを読み込ませる設定を行います。
例としてtest-example.php
というログイン用のファイルを作成します。なお、今回設置する場所は/var/www/ドメイン名/apps/
となっています。
ファイル名は自由に決めてもらって構いませんが、できるだけ相手から推測されないためにも$ openssl rand -hex 16
等の比較的信頼できる暗号強度の高い文字列を使った名前が良いでしょう。
test-example.php
<?php
// 第4章 WordPressの登録・認証系の秘匿化
// https://ka2.org/part4-conceal-of-registration-or-authentication-on-wordpress
// WordPress管理画面のログインURLを変更する
// https://gray-code.com/blog/wordpress/change-the-url-of-admin-page/
// 許可するIPリスト
$allow_ips = [
// ログイン画面にアクセスを許可するIPアドレスを定義(未定義の場合は全ての接続元を許可する)
// コメントアウトの行は空欄に該当する
// cf. '123.123.123.123', ...
];
// ログインページの表示が許可された場合の処理
if ( empty( $allow_ips ) || ( ! empty( $allow_ips ) && in_array( $_SERVER['REMOTE_ADDR'], $allow_ips, true ) ) ) {
define( 'LOGIN_PAGE_FILE', basename( $_SERVER['SCRIPT_FILENAME'] ) );
define( 'LOGIN_CREDENTIAL', hash( 'sha512', $_SERVER['HTTP_HOST'] .':'. $_SERVER['DOCUMENT_ROOT'] ) );;
// wp-login.phpを読み込む
require_once dirname( __FILE__ ) .'/wp-login.php';
// このファイルの設置場所を"/apps/"から公開ディレクトリ直下に変更する場合
// functions.phpでも設定が必要
// require_once dirname( __FILE__ ) .'/apps/wp-login.php';
} else {
// ログインページの表示が許可されない場合の処理
// WordPressの情報を取得するためにwp-load.phpを読み込む
require_once dirname( __FILE__ ) . '/wp-load.php';
// このファイルの設置場所を"/apps/"から公開ディレクトリ直下に変更する場合
// functions.phpでも設定が必要
// require_once dirname( __FILE__ ) . '/apps/wp-load.php';
// ステータスコードを404に変更する
status_header( '404' );
// テンプレート内の404ページにリダイレクトする
include( get_query_template( '404' ) );
// 処理を停止する
exit();
}
ログイン用ファイルの作成が終われば、次にfunctions.php
内に追加のコードを加えます。
functions.php
/** 省略 **/
// 第4章 WordPressの登録・認証系の秘匿化
// https://ka2.org/part4-conceal-of-registration-or-authentication-on-wordpress
// WordPress管理画面のログインURLを変更する
// https://gray-code.com/blog/wordpress/change-the-url-of-admin-page/
// WordPressインストールディレクトリの設定
$_wp_install_dir = trim( str_replace( $_SERVER['DOCUMENT_ROOT'], '', ABSPATH ), '/apps/' );
// ログイン用ファイルを"/apps/"ではなく、公開ディレクトリ直下に設置する場合
// ログイン用ファイルにも設定が必要
//$_wp_install_dir = trim( str_replace( $_SERVER['DOCUMENT_ROOT'], '', ABSPATH ), '/' );
// インストールディレクトリの再設定および定義
$_wp_install_dir .= ! empty( $_wp_install_dir ) ? '/' : '';
defined( 'WP_INSTALL_DIR' ) or define( 'WP_INSTALL_DIR', $_wp_install_dir );
// 新しいログインページのファイル名を定義する
defined( 'LOGIN_PAGE_FILE' ) or define( 'LOGIN_PAGE_FILE', 'test-example.php' );
// ログインURLの処理
add_filter( 'login_url', function( $login_url, $redirect, $force_reauth ) {
$login_url = str_replace( WP_INSTALL_DIR . 'wp-login.php', LOGIN_PAGE_FILE, $login_url );
return $login_url;
}, 10, 3 );
// ログアウトURLの処理
add_filter( 'logout_url', function( $logout_url, $redirect ) {
$logout_url = str_replace( WP_INSTALL_DIR . 'wp-login.php', LOGIN_PAGE_FILE, $logout_url );
return $logout_url;
}, 10, 2 );
// 新規登録URLの処理
add_filter( 'register_url', function( $url ) {
$url = str_replace( WP_INSTALL_DIR . 'wp-login.php', LOGIN_PAGE_FILE, $url );
return $url;
}, 10 );
// パスワード再発行URLの処理
add_filter( 'lostpassword_url', function( $url ) {
$url = str_replace( WP_INSTALL_DIR . 'wp-login.php', LOGIN_PAGE_FILE, $url );
return $url;
}, 10 );
// ログインページ内におけるサイトURLの置換処理
add_filter( 'site_url', function( $url, $path, $orig_scheme, $blog_id ) {
if( ($path == 'wp-login.php' || preg_match( '/wp-login\.php\?action=\w+/', $path) ) && (is_user_logged_in() || strpos( $_SERVER['REQUEST_URI'], LOGIN_PAGE_FILE) !== false) ) {
$url = str_replace( 'wp-login.php', LOGIN_PAGE_FILE, $url);
}
return $url;
}, 10, 4 );
// リダイレクト処理
add_filter( 'wp_redirect', function( $location, $status ) {
if ( strpos( $_SERVER['REQUEST_URI'], LOGIN_PAGE_FILE ) !== false ) {
$location = str_replace( WP_INSTALL_DIR . 'wp-login.php', LOGIN_PAGE_FILE, $location );
}
return $location;
}, 10, 2 );
// ログインページの処理
add_action( 'login_init', function() {
// ログインページへのアクセスが拒否された場合
if ( ! defined( 'LOGIN_CREDENTIAL' ) || hash( 'sha512', $_SERVER['HTTP_HOST'] .':'. $_SERVER['DOCUMENT_ROOT'] ) !== LOGIN_CREDENTIAL ) {
// ステータスコードを404に変更する
status_header( '404' );
// テンプレート内の404ページにリダイレクトする
include( get_query_template( '404' ) );
// 処理を停止する
exit();
}
});
// "admin"や"login"、"dashboard"などの管理ページ自動リダイレクト機能を止める
remove_action( 'template_redirect', 'wp_redirect_admin_locations', 1000 );
// stop redirection on /wp-admin call to /wp-login
// https://wordpress.stackexchange.com/questions/257913/stop-redirection-on-wp-admin-call-to-wp-login
// "/wp-admin/"にアクセスした際のログインページリダイレクトを無効化
add_filter('auth_redirect_scheme', 'stop_redirect', 9999);
function stop_redirect($scheme) {
// 認証クッキーの判定処理
if ( $user_id = wp_validate_auth_cookie( '', $scheme) ) {
return $scheme;
}
// ステータスコードを404に変更する
status_header( '404' );
// テンプレート内の404ページにリダイレクトする
include( get_query_template( '404' ) );
// 処理を停止する
exit();
}
/** 省略 **/
上記コードにより、wp-login.php
が行う処理は新しく作成したログイン用ページが担うことになりました。
また、上記処理ではwp-login.php
への直接アクセスを無効化および特定文字列によるリダイレクトを阻止し、同時にステータスコードを404に変更してします。
本来はログイン画面を変更してもwp-login.php
は存在しているので、ステータスコードは200で返却されていますが、ステータスコードも偽装することでそのファイルが存在していないように振る舞います。
しかし、ログインページへのアクセスを弾くにはこれでも不十分で、上記コードでは/wp-admin/
へアクセスした場合にも404を返却しますが、サーバの設定もしくはWordPressの機能によって/wp-admin
にアクセス時、/wp-admin/
へとリダイレクトするため、ディレクトリの存在が分かってしまいます(WordPressを使っていることが判明してしまう)。
Nginxでこれを防ぐには、rewrite
で内部リダイレクトを行います。
ドメイン名.conf
## 省略 ##
# "/wp-admin(/)"にアクセス時、"/?access=wp-admin"へ内部リダイレクトする
location ~ /wp-admin/?$ {
rewrite ^/ /?access=wp-admin? last;
}
## 省略 ##
上記設定はauthor
での方法とほぼ同じであり、できるだけ自然な挙動にするためにURLはそのままにしつつ、内部リダイレクトするようにしています。
WordPressテンプレートの404ページが用意されていればそれが表示されるため、他の存在しないファイルやディレクトリにアクセスした場合と同じ挙動の様に演出してくれます。
これにより、特定ファイルにアクセスした時だけ異なる404ページが表示されたり、URL変更を伴ったリダイレクトが行われる、といった違和感を発生させることはありません。 もしNginxでFastCGI Cacheを有効にしている場合は正しい表示ができなくなるため、ログイン用ファイルをキャッシュしない設定が必要です。 編集後は ログイン用ファイルにIP制限が利用できない環境にある場合は、作成したログインページにBasic認証を使う方法もあります。 まず パスワード確認後、ランダムな文字列が生成されるので、こちらもメモしておきます。 次に 保存後は 設定後は これにより、ログイン用ファイルにアクセス時、Basic認証が行われるようになりました。FastCGI Cacheを使用している場合
$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
で編集を行います。## 省略 ##
# 設置する位置によっては"$skip_cache"が上書きされてしまうので注意
# ログイン用ファイルにはFastCGI Cacheを使わない
if ($request_uri ~ "/test-example\.php$") {
set $skip_cache 1;
}
## 省略 ##
$ sudo service nginx restart
で再起動します。IP制限ができない環境の場合
$ openssl passwd
でBasic認証に使うパスワードを入力します。$ sudo vim /etc/nginx/wp_pass
でBasic認証に使うユーザ名と先程のパスワードの『暗号化された文字列』を入力します。wp_pass
Basic認証に使うユーザ名(自由):暗号化後のパスフレーズ
$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
でログイン用ファイルへアクセス時の設定を作成します。ドメイン名.conf
## 省略 ##
# ログイン用ファイルにアクセスした時の設定
location ~ /ログイン用ファイル名\.php$ {
# Nginx側でIP制限を行う場合
# allow ここに許可するIPアドレスを入れる;
# allow 複数のIPを許可する場合は一行ずつ追加
# 許可されたIP以外全て拒否する
# deny all;
# Basic認証を設定
auth_basic "login";
# Basic認証のユーザファイルを指定
auth_basic_user_file /etc/nginx/wp_pass;
# 左から順に参照し、該当が無ければ最後のuri(もしくはcode)に内部転送される
try_files $uri $uri/ /index.php?$args;
# PHPに渡すパラメータファイルを指定
include fastcgi_params;
# FASTCGIサーバーへのキープアライブ接続をON
fastcgi_keep_conn on;
# スラッシュで終わるURIの後にFASTCGIが要求するファイル名
fastcgi_index index.php;
# fastcgi_path_info変数の値をキャプチャする正規表現の定義
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
# FastCGIサーバーに渡すパラメータを指定
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# コード300以上のレスポンスをerror_pageディレクティブで処理
fastcgi_intercept_errors on;
# FastCGIサーバーのアドレスをUnixドメインソケットパスに指定
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}
## 省略 ##
$ sudo service nginx restart
でNginxを再起動します。
headに自動出力される要素を削除する
WordPressサイトは初期状態のままだと、多くの要素が自動的に<head>
内に出力されます。
例えばWordPressのバージョン情報や、絵文字を使用するためのコード、短縮URLなど、様々な要素が挿入されています。
けれども、これらは必ず必要というものではなく、特にバージョン情報の表記はセキュリティリスクにも関わることから、必要でないものは非表示にした方が良いと思われます。
他にもWordPressを使用していることを一目で分かってしまったり、サイトのパフォーマンス低下を招くため、不要なものを消しておくことはサイト構造の強化に繋がります 。
また、これらはすべてfunctions.php
で除外指定を行うことで取り除くことが可能です。
functions.php
/** 省略 **/
// wp_head()で出力される不要なタグを除去
// https://miyachi-web.com/remove-tags-in-wp_head/
// RSSフィードのURLを除去
remove_action('wp_head', 'feed_links', 2);
remove_action('wp_head', 'feed_links_extra', 3);
// 絵文字機能を停止
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
remove_action( 'wp_print_styles', 'print_emoji_styles' );
remove_action( 'admin_print_styles', 'print_emoji_styles' );
remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
// "dns-prefetch"の"s.w.org"を削除する(絵文字関連)
add_filter( 'emoji_svg_url', '__return_false' );
// 前後ページのURL表示を停止
remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head' );
// 埋め込み機能(Embed)の停止
remove_action( 'wp_head', 'wp_oembed_add_discovery_links' );
remove_action( 'wp_head', 'wp_oembed_add_host_js' );
// REST APIのURL表示を除去
remove_action('wp_head', 'rest_output_link_wp_head');
// REST APIに関するレスポンスヘッダを削除
remove_action('template_redirect', 'rest_output_link_header', 11 );
// shortlinkに関するレスポンスヘッダを削除
remove_action('template_redirect', 'wp_shortlink_header', 11);
// レスポンスヘッダのx-pingbackを削除
add_filter('pings_open', '__return_false' );
// 外部ブログツールからの投稿を受け入れを停止
remove_action('wp_head', 'rsd_link');
remove_action('wp_head', 'wlwmanifest_link');
// バージョン表記を除去
remove_action('wp_head', 'wp_generator');
// 短縮URL機能を停止
remove_action('wp_head', 'wp_shortlink_wp_head');
// ウィジェット「最近のコメント」の表示を停止
function remove_recent_comment_css() {
global $wp_widget_factory;
remove_action( 'wp_head', array( $wp_widget_factory->widgets['WP_Widget_Recent_Comments'], 'recent_comments_style'));
}
add_action( 'widgets_init', 'remove_recent_comment_css');
function register_javascript() {
wp_deregister_script('comment-reply');
}
add_action('wp_enqueue_scripts', 'register_javascript');
// 静的ファイルのクエリパラメータを削除
function remove_query_string( $src ){
$parts = explode( '?ver', $src );
return $parts[0];
}
add_filter( 'script_loader_src', 'remove_query_string', 15, 1 );
add_filter( 'style_loader_src', 'remove_query_string', 15, 1 );
// HTMLコード内に挿入されるglobal-stylesを削除
// 【WordPress 5.9】緊急対応「global-styles-inline-css」を削除する方法
// https://on-ze.com/archives/10109
add_action( 'wp_enqueue_scripts', 'remove_my_global_styles' );
function remove_my_global_styles() {
wp_dequeue_style( 'global-styles' );
}
/** 省略 **/
上記設定により、wp_head()
で出力される要素の多くが停止されるようになります。
不要な機能を停止させる
WordPressには多くの便利な機能がありますが、通常のサイト運営では使うことのないものもあります。
使わない状態であるならば、いっそ機能を停止させておいた方がセキュリティ的には良いでしょう。 REST APIはウェブサイトの情報を外部から取得するためのシステムです。 WordPressでは しかし、REST APIは便利な反面、アクセス制限をしていないと誰でも情報を閲覧することができてしまいます。 例えば、 前のセクションで この際、REST APIは使う予定が無いのでいっそ停止してしまおうと考えてしまいがちですが、REST APIは多くのプラグインだけでなく、管理ページでも使われているので、停止は推奨されていません。 しかし、ログインユーザに限りREST APIを有効化する方法や、REST APIの使用を許可制にする方法があり、どちらも 記述の際はどちらか必要な方を使用してください。 REST APIを許可制にする例では なお、 RSS Feedはサイト内の記事に関する更新情報を取得できる便利な機能です。 しかしその需要は年々大きく減少しており、ウェブブラウザではFirefoxが数年前にRSS Feed Readerを廃止しています。 RSS Feed配信については大きなサイトを除いて、惰性でなんとなく配信しているという人は多いのではないでしょうか。 WordPressでもRSS Feedで更新情報を配信できますが、その情報にはWordPressに関する情報も含まれているため、WordPressを使っていることが分かってしまいます。 RSS Feedのテンプレートそのものをカスタマイズする方法もありますが、購読者がほとんどいないならば停止してしまった方がリソースの節約やセキュリティ的にも良いでしょう。 RSS Feedを停止する場合、まずは 以上の設定でRSS Feedの配信は停止しますが、ページそのものにはまだアクセスが可能です。 そのため、RSS FeedのURLへ直接アクセスするとそのページにはXMLスタイルシートが存在していないことから、クライアント側でエラーページが表示されてしまいます。 これを解決するには 以上の設定により、RSS Feed関連のURLにアクセスすると404ページが表示されるようになります。 WordPressにはURLの正規化を行う自動リダイレクト機能が搭載されています( 例えば各ファイルやディレクトリにアクセスする際、最後にスラッシュ(トレイリングスラッシュ)を付けていないと自動的にスラッシュが付与される機能( これらは一見便利な機能に見えますが、意図したように動作しないこともあるため、不便な側面もあります。 そのため、 上記コードでは存在しないページのみをリダイレクトしない設定の他に、オプションとして指定したページのみリダイレクトしない設定、そして自動リダイレクトを完全に無効化する設定を追加しています。 自動リダイレクトの完全無効化に関しては、URL末尾のスラッシュ有り無し関係なくリダイレクトされない(どちらでもページの閲覧ができる)ため、注意が必要です。 もし自動リダイレクトを無効にするならば、NginxのRewrite機能を用い、拡張子がついたファイル(もしくはクエリ)を除いたすべてのURLにトレイリングスラッシュ(末尾のスラッシュ)を付与することで、この問題を回避できます。 設定後は これにより、設定したウェブサイト内ではすべてのURLにトレイリングスラッシュが追加されるようになりました。 また、この設定ではファイルの存在有無に関わらず、末尾にスラッシュが付与されています。 WordPressにはQuery Vars(クエリ変数)という、テーマやプラグインのデザイン・機能を補助するための変数が用意されています。 例えば、 しかし、前述したようにこの機能はテーマやプラグインの補助を行うものであって、使わない時は使うことがほとんどありません、と言いたい所ですが、実は各種パーマリンクの設定でもこれが使われていますので、全て無効化することはできません。 クエリ文字列に関しては一部無効化しても影響がないため、WordPressを使用していることを隠すならば、ある程度は無効化してしまっても良いでしょう。 ただし、パーマリンクを 今回の例では、Public Query Varsを一部無効化(検索( また、ログイン中のユーザに関してはこの無効化を解除します。 もし設定後に何らかのエラーや不具合が発生した場合は調整してください。 無効化する場合は 以上のコードを加えることで、非ログイン中ではPublic Query Varsを使うことができなくなります。 ただし、上記コードでは指定したクエリ文字列にアクセスがあった場合に404ページに移動する設定です。 もし存在しないクエリ文字列と同様の動作を行う(見た目上は変化無し)場合にはウェブサーバ側で設定を書き換える必要があります。 設定後は これにより、クエリ文字列がPublic Query Var関連だった場合、内部リダイレクトが行われるようになります。 WordPressはバージョン5.0からGutenbergというブロックエディタを用いた新しい記事編集システムを採用しています。 このシステムではCSSやHTMLの知識が無い方でも、ある程度直感的かつ視覚的にコンテンツを作成編集できるのですが、細かい装飾が困難であったり編集手順がやや多かったりで、これまで従来のエディタに慣れ親しんで来た方にはやや不評でした。 WordPressはGutenbergのブロックエディタをメインとして開発しつづけているため、時間が経つにつれて仕方なく移行する方達も増えてきましたが、それでもまだ従来のエディタを使い続けている方もまだまだ多いです。 また、ブロックエディタでは常にHTML内に専用のCSS( 最新版では常にブロックエディタのみが使用可能になっており、従来のエディタに戻す場合はプラグインの導入、もしくは プラグインを入れなくてもブロックエディタを簡単に無効化できるならば、そちらの方がセキュリティ的にも良いので 以上の設定により、ブロックエディタの無効化および関連CSSの出力停止が行われます。REST API
/wp-json/
をURLに加えることでウェブサイト内の様々な情報を取得することができます(example.com/wp-json/
など)。/wp-json/wp/v2/users
にアクセスした場合、そこにはWordPressに登録しているユーザの情報が表示されています。author
へのアクセスを制限してユーザを確認させない方法を実施しましたが、ここで登録ユーザの情報を閲覧できるため、対策した意味がなくなってしまいます。functions.php
に記載するだけで機能します。functions.php
/** 省略 **/
// WordPressのREST API周りのfilter hook / action hook のまとめと、一部のREST APIを開放する方法
// https://qiita.com/TanakanoAnchan/items/f4fc11f66e9cf2d7490e
// 非ログイン時ではREST APIへのアクセスをすべて404にする
add_action( 'rest_api_init', function () {
//ログインしていない時
if ( ! is_user_logged_in() ) {
// ステータスコードを404に変更する
status_header( '404' );
// テンプレート内の404ページにリダイレクトする
include( get_query_template( '404' ) );
// 処理を停止する
exit();
}
}, -1 );
// REST APIを許可制にする
// WordPress のユーザー・一覧表示対策
// https://www.webdesignleaves.com/pr/wp/wp_user_enumeration.html
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
としてoembed
、contact-form-7
、akismet
が記載されていますが、他にも追加したいプラグインがある場合はそこに新しく対象を記載します。/wp-json/
内にあるnamespace
で、プラグインに使われている名前およびルートを確認できます。RSS Feed
functions.php
に各種RSSの機能を停止させましょう。functions.php
/** 省略 **/
// 【WordPress】【セキュリティ対策】不要なRSSフィード配信は無効化しよう
// https://cree.fun/6008
// フィード配信を停止
remove_action('do_feed_rdf', 'do_feed_rdf');
remove_action('do_feed_rss', 'do_feed_rss');
remove_action('do_feed_rss2', 'do_feed_rss2');
remove_action('do_feed_atom', 'do_feed_atom');
// フィードリンクを除去
remove_action('wp_head', 'feed_links', 2);
remove_action('wp_head', 'feed_links_extra', 3);
/** 省略 **/
functions.php
を編集し、RSS Feedの関連URLを404ページに移動させなければなりません。functions.php
/** 省略 **/
// RSS Feedのページを404ページに内部リダイレクトする
function disable_rss_feed() {
// "/feed(/)"、"/rss(2)(/)"、"/rdf(/)"、"/atom(/)"を含んだページにアクセスした場合
if( preg_match('#/(?:feed|rss2?|rdf|atom)(?:$|/)#', $_SERVER['REQUEST_URI']) ){
// ステータスコードを404に変更する
status_header( '404' );
// テンプレート内の404ページにリダイレクトする
include( get_query_template( '404' ) );
// 処理を停止する
exit();
}
}
// "init"に対してアクションフックを行う
add_action('init', 'disable_rss_feed');
/** 省略 **/
自動リダイレクト
/wp-includes/canonical.php
によって制御)。/wp-admin
が/wp-admin/
となる)、記事ページのURLに近い文字をタイプするとそのページに移動できる(example.com/ab
をタイプするとexample.com/about/
に移動できる)機能などがあります。functions.php
を用いて一部を無効化しますが、必要となる項目のみを記述してください。functions.php
/** 省略 **/
// 存在しないページでは自動リダイレクトを無効にする
add_filter('redirect_canonical', 'remove_redirect_guess_404_permalink', 10, 2);
function remove_redirect_guess_404_permalink($redirect_url, $requested_url) {
// 存在しないページの場合はリダイレクトしない
if(is_404()) {
return false;
}
// 存在するページではリダイレクトする
return $redirect_url;
}
// 特定のページだけWordPressの自動リダイレクト機能を無効にする
// https://www.doe.co.jp/hp-tips/wordpress/special-redirect/
function redirect_special_permalink( $redirect_url, $requested_url ) {
$match_url = array(
'feed',
);
foreach( $match_url as $url ) {
if ( strpos($requested_url, $url) ) return false;
}
return $redirect_url;
}
add_filter( 'redirect_canonical', 'redirect_special_permalink', 10, 2 );
// リダイレクトをすべて無効化
// add_filter( 'redirect_canonical', '__return_false' );
/** 省略 **/
$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
を編集し、条件を追加します。ドメイン名.conf
## 省略 ##
# Serverディレクション内の他設定よりも後ろに設置する
# クエリ文字列がついておらず、拡張子ファイルを除いたすべてのURLにトレイリングスラッシュを付与
if ($args = "") {
rewrite ^([^.]+[^/])$ $1/ permanent;
}
## 省略 ##
$ sudo service nginx restart
でNginxを再起動します。QueryVars
?p=数値
や?cat=値
を入力することで、該当する記事ページや一覧に移動できます。基本
にしていたり、何度も言うようにテーマやプラグインでクエリ変数を使用している場合は、無効化した際に意図しない動作が発生するため注意が必要です。?s=
)やプレビュー(?preview=
)は有効化のまま)し、Private Query Varsはそのままにします。functions.php
に新しく追加のコードを記述します。functions.php
/** 省略 **/
// Public Query Varsをクエリ文字列に限り無効化
function disable_public_query_vars() {
// ログインしていない場合
if ( ! is_user_logged_in() ) {
if(preg_match('#\?(?:attachment|attachment_id|author_name|cat|calendar|category_name|comments_popup|cpage|day|error|exact|feed|hour|m|minute|monthnum|more|name|order|orderby|p|page_id|page|paged|pagename|pb|post_type|posts|preview|robots|s|search|second|sentence|static|subpost|subpost_id|taxonomy|tag|tag_id|tb|term|w|withcomments|withoutcomments|year)=.*#', $_SERVER['REQUEST_URI']) ){
// ステータスコードを404に変更する
status_header( '404' );
// テンプレート内の404ページにリダイレクトする
include( get_query_template( '404' ) );
// 処理を停止する
exit();
}
}
}
// "init"に対してアクションフックを行う
add_action('init', 'disable_public_query_vars');
/** 省略 **/
$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
を編集し、条件を追加します。
# Public Query Vars判定用変数を"A"に設定する
set $wpq_ "A";
# WPにログイン中の場合は"$wpq_"を空欄にする
if ($http_cookie ~ wordpress_logged_in_) {
set $wpq_ "";
}
# クエリ文字列がPublic Query Varsである場合、"$wpq_"に"B"を加える
if ($args ~ "(?:attachment|attachment_id|author_name|cat|calendar|category_name|comments_popup|cpage|day|error|exact|feed|hour|m|minute|monthnum|more|name|order|orderby|p|page_id|page|paged|pagename|pb|post_type|posts|robots|second|sentence|static|subpost|subpost_id|taxonomy|tag|tag_id|tb|term|w|withcomments|withoutcomments|year)=.*") {
set $wpq_ "${wpq_}B";
}
# "$wpq_"が"AB"である場合、内部リダイレクトする
if ($wpq_ = "AB" ) {
rewrite ^/ /?access=query-vars? last;
}
$ sudo service nginx restart
でNginxを再起動します。Block Editor
wp-includes/css/dist/block-library/style.min.css
)が出力され、これはブロックエディタでは必ず必要なファイルであることから、少々邪魔に感じます。functions.php
での編集が必要です。functions.php
を編集する方法を採用します。functions.php
/** 省略 **/
// WordPress:Gutenberg(ブロックエディタ)を無効化する方法
// https://www.nxworld.net/wp-disable-gutenberg.html
// ブロックエディタを無効化
add_filter( 'use_block_editor_for_post', '__return_false' );
// Remove the Gutenberg Block Library CSS from WordPress
// https://smartwp.com/remove-gutenberg-css/
// HTML内に出力されるブロックエディタ用CSSを無効化
function mytheme_enqueue() {
wp_dequeue_style( 'wp-block-library' );
wp_dequeue_style( 'wp-block-library-theme' );
wp_dequeue_style( 'wc-blocks-style' );
}
add_action( 'wp_enqueue_scripts', 'mytheme_enqueue' );
/** 省略 **/
コンテンツを徹底的に秘匿する
セキュリティを考えた場合、ウェブサイトがWordPressであることを攻撃者にできるだけ悟られないようにすることが大切です。
WordPressにはwp-admin
、wp-content
、wp-includes
という専用のディレクトリだけでなく、wp-文字列
というファイルなども存在しています。
これらにアクセスして存在が確認できた場合、そのウェブサイトがWordPressを使用しているということに繋がります。
名前が共通であるということは機械的にアクセスすることもできるため、悪意のある者が様々なファイルに何度もアクセスしてくるケースが多々あります。
そのため、これらの存在をできる限り秘匿する必要があります。 先程にも話した通り、WordPressのインストールディレクトリには様々なファイルが作られています。 多くは必要なファイルですが、一部は特に削除しても問題ないファイルです。 その中でも、インストールディレクトリ直下にある これらは削除することができますが、WordPressのアップデートを行った際に復活する場合があるため、設定ファイルで処理の追加を行います。 シリーズ内ではNginxを用いているため、 設定後は これにより、該当ファイルにアクセスするとURLはそのままで内部リダイレクトが行われ、WordPressテンプレートの404ページが存在していれば、それが表示されます。 ただし、管理ページでしかユーザを登録しないならば、どちらのファイルも不要です。 使用する予定が無い場合は 設定後は 以上の設定により、どちらのファイルでも内部リダイレクトが行われるため、外部からは直接アクセスできなくなります。 しかしながら、多くのWordPressサイトではファイルに直接アクセスした際、エラーが発生して404となっていることが多いです。 そのため、外部からアクセスは不要と考え、こちらも 設定後は コメント欄を使用しない、もしくはサードパーティ製のコメントツールを使用するならばこのファイルは不要です。 これまでと同じく、アクセスを排除するならば 設定後は これまでの設定では、 そのため、ファイルに直接アクセスが来た場合でも、違和感の無い方法でアクセスを排除した方がセキュリティ的にも良いでしょう。 設定を行う場合、こちらも 設定後は この方法を使うことで 例えば訪問者の少ないウェブサイトでは全く予約投稿してくれなかったり、その逆ではウェブサイトに高い負荷が常に発生したりします。 そのため、WordPressによって このファイルを停止させるということは予約機能が失われるので、大変不便だと思う人がいるかもしれません。 ただし、停止させるのはWordPress自身が動かす場合であって、サーバ側で動かすならば全く問題がありません。 というわけで まずは 保存するとWordPress側での 次にサーバ側の設定を行います。 上記の設定では毎分 もし毎分動かすのが嫌な場合は、最初の ただし、cronの仕様により、時間のズレ(投稿予約時間が18:10でcronが20分ごとの場合、10分の予約が飛ばされるため作動しない)には気をつけなければなりません。 そしてこれら設定に続き、ファイルの存在を秘匿するために 設定が終われば これにより しかし、WordPress 3.5以降ではリンクマネージャは非表示になっていることから、自ら使おうとしない限りは使用することはありません。 これらも使用しないことが確定しているならば、 設定が終われば このファイルを読み込むことで、WordPress外にある外部ファイルでもWordPressの仕組みを利用することができます。 基本的にサーバ側がphpファイルを実行する際に読み込まれるものであるため、外部からのアクセスは必要ありません。 そのため、このファイルもこれまでと同様に外部アクセスを排除します。 設定が終われば このファイルはどのWordPressサイトでも共通の名前であるため、ファイルを直接指定した不正アクセスを目的とする接続が非常に多いです。 これまでの設定では そのため、アクセスそのものを回避したい場合は、今までと同じように内部リダイレクトでアクセス制限を行います。 設定が終われば これにより、 しかし、外部からアクセスした場合にはエラーが発生するため、こちらも外部アクセスが不要と判断し、ファイルへのアクセスを排除します。 設定後は このファイルを使うことで、WordPressモバイルアプリでの通信やトラックバック等をサポートでき、Jetpackをはじめとする複数のプラグインでも様々な機能のために利用されていました。 しかし、現在ではREST APIがその代替として使われることが多くなりました。 今となっては下位互換として設置しているだけなので、過去と比べて使う頻度は大きく減っています。 そのため、自身のサイトがこのファイルを使っていないことが明らかなのであれば、無効化しても問題ありません。 なお、このファイルは悪用されるケースが多いため、使用していない場合は放置せずに無効化させることを推奨します。 無効化する場合、まずはWordPressテーマ内の 上記設定により、 しかし、ファイル自体は存在しているため、 設定が終われば そのことから、基本的に外部アクセスは弾いておくべきであり、公開する必要はほとんどありません。 WordPressインストール直後の場合、このディレクトリ以降は大抵アクセスすることはできませんが、ファイルにアクセスするとログインページへと移動してしまいます。 つまり、他のセクションでログインページへのアクセスを無効化する方法を紹介したものの、まだログインページにアクセスする方法は残っているのです。 このセクションでは ただし、完全にアクセスを弾いた場合はログインユーザですらアクセスできなくなることや、ログインページでは必要なファイルが読み込まれなくなってしまうため、ログインユーザもしくはログインページにアクセスした者限定で また、 ではまず、ログインURL変更の際にログイン用ページに使用したファイルである 上記ではこれまでの記述に 次に 設定が終われば これにより、 それ以外の第三者は関連ファイルにアクセスしても、404ページが表示されるようになっているため、 設定によっては第三者のアクセスが不要になるため、アクセス制限を行っても問題はありませんが、プラグインやテーマによっては アクセス制限を行う際には先程の 設定が終われば これにより、 しかし、このディレクトリは第三者にもリソースの読み込みが必要となっているケースも多いため、アクセス制限は基本的にできません。 けれども前2つとは異なり 今回はシンボリックリンクを用いて このセクションではWordPressのデフォルト構成からなるべく外す設定を行いますが、保守性のためにディレクトリそのものの移動やリネームは行わない方法を使います。 まずは 例では変更後階層のフルパスは 次に 変更後、サイトにアクセスして変更前と見た目は何も変化が無いのに加え、ソース内では これにより しかし、 編集後は これで こちらも先程と同じく簡単に変更することができます。 ただし、プラグインによっては その場合は自身でプラグインソースを変更することで使用できますが、可用性を考慮していないプラグインは変更によって別の悪影響が発生する可能性もあるため、できればそのようなプラグインは使わない方が良いかもしれません。 では最初に 例では変更後階層のフルパスは 次に これにより そして前回と同じようにNginxの設定を変更して 編集後は これで不要なファイル
license.txt
、readme.html
、wp-config-sample.php
は第三者のアクセスが不要なファイルです。license.txt
はその名の通りWordPressに関するライセンスを記載したファイルです。readme.html
はWordPressの簡単な説明書のようなことが記載されています。wp-config-sample.php
はwp-config.php
を作成する際のサンプルとなるファイルです。$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
で処理を記述します。ドメイン名.conf
## 省略 ##
# "location ~ \.php$"より上に置くこと
# 以下のファイルにアクセス時、"/?access=files"へ内部リダイレクトする
location ~ /(?:license\.txt|readme\.html|wp-config-sample\.php)$ {
rewrite ^/ /?access=files? last;
}
## 省略 ##
$ sudo service nginx restart
でNginxを再起動します。登録関連ファイル
wp-activate.php
、wp-signup.php
はユーザ登録を行う際に必要となるファイルです。wp-activate.php
はユーザ登録後の認証のために使用されるファイルです。wp-signup.php
はユーザ登録を行う際に用いられます。$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
で両ファイルのアクセスを排除します。ドメイン名.conf
## 省略 ##
# "location ~ \.php$"より上に置くこと
# "wp-activate.php"もしくは"wp-signup.php"にアクセス時、"/?access=activate-signup"へ内部リダイレクトする
location ~ /(?:wp-activate|wp-signup)\.php$ {
rewrite ^/ /?access=activate-signup? last;
}
## 省略 ##
$ sudo service nginx restart
でNginxを再起動します。wp-blog-header.php
wp-blog-header.php
はWordPressの設定読み込みおよびテンプレートの読み込みを行うファイルです。$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
で外部アクセスを排除します。ドメイン名.conf
## 省略 ##
# "location ~ \.php$"より上に置くこと
# "wp-blog-header.php"にアクセス時、"/?access=wp-blog-header"へ内部リダイレクトする
location ~ /wp-blog-header\.php$ {
rewrite ^/ /?access=wp-blog-header? last;
}
## 省略 ##
$ sudo service nginx restart
でNginxを再起動します。wp-comments-post.php
wp-comments-post.php
は名前の通り、コメントの投稿に関するファイルです。$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
で設定を行います。ドメイン名.conf
## 省略 ##
# "location ~ \.php$"より上に置くこと
# "wp-comments-post.php"にアクセス時、"/?access=wp-comments-post"へ内部リダイレクトする
location ~ /wp-comments-post\.php$ {
rewrite ^/ /?access=wp-comments-post? last;
}
## 省略 ##
$ sudo service nginx restart
でNginxを再起動します。wp-config.php
wp-config.php
は不要なファイルではありませんが、外部アクセスは弾いた方が良いとされるファイルです。deny all
によってすべてのアクセスを排除していますが、特定ファイルにアクセスした場合にのみ特殊な動作をするのは、多少仕組みに理解のある者ならばWordPressを利用していることが分かってしまいます。$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
で設定を行います。ドメイン名.conf
## 省略 ##
# "location ~ \.php$"より上に置くこと
# "wp-config.php"にアクセス時、"/?access=wp-config"へ内部リダイレクトする
location ~ /wp-config\.php$ {
rewrite ^/ /?access=wp-config? last;
}
## 省略 ##
$ sudo service nginx restart
でNginxを再起動します。wp-config.php
へ直接アクセスが来た場合でも、他の404と同じ挙動を示すことができます。wp-cron.php
wp-cron.php
は予約投稿やプラグインによるバックアップなど、時限的に作動させるファイルですが、これは訪問者がやってくるごとに起動するという厄介な仕様を持っています。wp-cron.php
を動かすことはやめておくべきです。wp-cron.php
をWordPress側は停止させ、サーバ側で作動させる方法を説明します。$ sudo vim /var/www/ドメイン名/apps/wp-config.php
で設定ファイルを編集します(フルパスの場所はそれぞれ変更してください)。wp-config.php
/* カスタム値は、この行と「編集が必要なのはここまでです」の行の間に追加してください。 */
/** 省略 **/
// WordPressでのwp-cron.phpを停止する
define('DISABLE_WP_CRON', 'true');
/** 省略 **/
/* 編集が必要なのはここまでです ! WordPress でのパブリッシングをお楽しみください。 */
wp-cron.php
は作動しなくなりました。$ sudo vim /etc/crontab
でサーバ側のcronを編集します。crontab
## 省略 ##
# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed
* * * * * nginx /usr/bin/php -q /var/www/ドメイン名/apps/wp-cron.php > /dev/null
## 省略 ##
/var/www/ドメイン名/apps/wp-cron.php
をサーバ側が動かすようになっています(フルパスはそれぞれの環境ごとに変更してください)。*
を5
に変更することで5分ごとに動かすようになります。$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
で外部アクセスを排除します。ドメイン名.conf
## 省略 ##
# "location ~ \.php$"より上に置くこと
# "wp-cron.php"にアクセス時、"/?access=wp-cron"内部リダイレクトする
location ~ /wp-cron\.php$ {
rewrite ^/ /?access=wp-cron? last;
}
## 省略 ##
$ sudo service nginx restart
で再起動します。wp-cron.php
はWordPressが動かすのではなくサーバ側でのみ作動し、外部からの直接アクセスもできないようになりました。使用機会の少ないファイル
wp-links-opml.php
、wp-mail.php
、wp-trackback.php
は使用する機会があまり無いと思われるファイルです。wp-links-opml.php
はWordPress管理ページのリンクで設定したブログロールのリンク情報を、OPMLと呼ばれるXMLがベースのフォーマットを出力するためのファイルです。wp-mail.php
はメールを用いてブログ投稿を行うためのファイルです。メールでの投稿を行わないならば使うことはありません。wp-trackback.php
はトラックバックと呼ばれる外部サイトからのリンク引用を通知するシステムです。トラックバックを無効化している場合は使用することはありません。$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
で編集してアクセスを排除します。ドメイン名.conf
## 省略 ##
# "location ~ \.php$"より上に置くこと
# "wp-links-opml.php"、"wp-mail.php"、"wp-trackback.php"にアクセス時、"/?access=files"へ内部リダイレクトする
location ~ /(?:wp-links-opml|wp-mail|wp-trackback)\.php$ {
rewrite ^/ /?access=files? last;
}
## 省略 ##
$ sudo service nginx restart
で再起動します。wp-load.php
wp-load.php
はWordPress関数を使うために読み込まれるファイルです。$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
で追加の編集を行います。ドメイン名.conf
## 省略 ##
# "location ~ \.php$"より上に置くこと
# "wp-load.php"にアクセス時、"/?access=wp-load"へ内部リダイレクトする
location ~ /wp-load\.php$ {
rewrite ^/ /?access=wp-load? last;
}
## 省略 ##
$ sudo service nginx restart
で再起動します。wp-login.php
wp-login.php
はWordPressの管理ページに入る際必要となる大切なファイルです。wp-login.php
は無効化して更にステータスコードも404に指定しています(ファイルそのものは本来コード200)。$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
で追加の編集を行います。ドメイン名.conf
## 省略 ##
# "location ~ \.php$"より上に置くこと
# "/wp-login.php"を含むURLにアクセス時、"/?access=wp-login"へ内部リダイレクトする
location ~ /wp-login\.php$ {
rewrite ^/ /?access=wp-login? last;
}
## 省略 ##
$ sudo service nginx restart
で再起動します。wp-login.php
は外部アクセスされないようになりました。wp-settings.php
wp-settings.php
はWordPressに関する共通変数の設定と修正に使用されるファイルです。$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
で編集を行います。ドメイン名.conf
## 省略 ##
# "location ~ \.php$"より上に置くこと
# "wp-settings.php"にアクセス時、"/?access=wp-settings"へ内部リダイレクトする
location ~ /wp-settings\.php$ {
rewrite ^/ /?access=wp-settings? last;
}
## 省略 ##
$ sudo service nginx restart
で再起動します。xmlrpc.php
xmlrpc.php
はWordPressと他のシステム間でXML-RPCを使った通信を行えるようにするためのファイルです。functions.php
を編集します。functions.php
/** 省略 **/
// xmlrpc.phpを無効化
add_filter( 'xmlrpc_enabled', '__return_false' );
/** 省略 **/
xmlrpc.php
の機能は無効化されました。$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
で外部アクセスを排除するための設定を追加します。ドメイン名.conf
## 省略 ##
# "location ~ \.php$"より上に置くこと
# "xmlrpc.php"にアクセス時、"/?access=xml-rpc"へ内部リダイレクトする
location ~ /xmlrpc\.php$ {
rewrite ^/ /?access=xml-rpc? last;
}
## 省略 ##
$ sudo service nginx restart
で再起動します。wp-admin
wp-admin
はその名の通り、管理関連のファイルが設置されているディレクトリです。wp-admin
関連のファイルおよびディレクトリへのアクセスを弾く方法を説明します。wp-admin
にアクセスできるようにします。/wp-admin/admin-ajax.php
はプラグイン等で第三者にも必要となるケースがあるため、アクセス可能にしなければならない場合もあります。第三者のアクセス必要がなければ、こちらもログインユーザおよびログインページアクセス者にのみアクセスを許可します。test-example.php
を編集します。test-example.php
<?php
// 第4章 WordPressの登録・認証系の秘匿化
// https://ka2.org/part4-conceal-of-registration-or-authentication-on-wordpress
// WordPress管理画面のログインURLを変更する
// https://gray-code.com/blog/wordpress/change-the-url-of-admin-page/
// 許可するIPリスト
$allow_ips = [
// ログイン画面にアクセスを許可するIPアドレスを定義(未定義の場合は全ての接続元を許可する)
// コメントアウトの行は空欄に該当する
// cf. '123.123.123.123', ...
];
// ログインページの表示が許可された場合の処理
if ( empty( $allow_ips ) || ( ! empty( $allow_ips ) && in_array( $_SERVER['REMOTE_ADDR'], $allow_ips, true ) ) ) {
define( 'LOGIN_PAGE_FILE', basename( $_SERVER['SCRIPT_FILENAME'] ) );
define( 'LOGIN_CREDENTIAL', hash( 'sha512', $_SERVER['HTTP_HOST'] .':'. $_SERVER['DOCUMENT_ROOT'] ) );;
// Cookie用変数を定義
// Cookie名を"wp_check_"に設定
$name = 'wp_check_';
// Cookieの値には暗号論的に安全なランダムバイト列(32bytes)を16進表現に変換した値を使用
$value = bin2hex(random_bytes(32));
// 期限は60秒に指定(expires)、Cookieのpathは'/'に指定、domainは指定無し(サブドメインを含めない)
// Cookieは同一サイトのみ付与(strict)、CookieはHTTPSでのみ送信(secure)、JavaScriptによるCookie送信を禁止(httponly)
$options = ['expires' => time()+60,'path' => '/','domain' => '', 'samesite' => 'strict', 'secure' => true, 'httponly' => true];
// Cookieを配置する
setcookie($name, $value, $options);
// wp-login.phpを読み込む
require_once dirname( __FILE__ ) .'/wp-login.php';
// このファイルの設置場所を"/apps/"から公開ディレクトリ直下に変更する場合
// functions.phpでも設定が必要
// require_once dirname( __FILE__ ) .'/apps/wp-login.php';
} else {
// ログインページの表示が許可されない場合の処理
// WordPressの情報を取得するためにwp-load.phpを読み込む
require_once dirname( __FILE__ ) . '/wp-load.php';
// このファイルの設置場所を"/apps/"から公開ディレクトリ直下に変更する場合
// functions.phpでも設定が必要
// require_once dirname( __FILE__ ) . '/apps/wp-load.php';
// ステータスコードを404に変更する
status_header( '404' );
// テンプレート内の404ページにリダイレクトする
include( get_query_template( '404' ) );
// 処理を停止する
exit();
}
wp_check_
という名前のCookieを新しく追加しており(名前は自由)、このCookieはNginxの設定で必要となってきます。$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
で設定ファイルを編集します。ドメイン名.conf
## 省略 ##
# WordPressのファイル読み込み許可を判定する変数の初期値を"deny"にセットする(名前と値は自由)
set $wp_ "deny";
# WordPressにログイン中は"$wp_"を空欄にする
if ($http_cookie ~ wordpress_logged_in_) {
set $wp_ "";
}
# ログインページ用Cookieの"wp_check_"が存在していれば"$wp_"を空欄にする
if ($http_cookie ~ wp_check_) {
set $wp_ "";
}
# "/wp-admin/admin-ajax.php"へのアクセス設定
location = /apps/wp-admin/admin-ajax.php {
# ログイン者とログインページアクセス者のみアクセス可能にする場合はコメントアウト
# `wp_`を空欄にすることでアクセス制限を解除(誰でもアクセスできる)
set $wp_ "";
# 左から順に参照し、該当が無ければ最後のuri(もしくはcode)に内部転送される
try_files $uri$wp_ $uri$wp_/ /index.php?$args$wp_ /?access=wp-admin;
# PHPに渡すパラメータファイルを指定
include fastcgi_params;
# FASTCGIサーバーへのキープアライブ接続をON
fastcgi_keep_conn on;
# スラッシュで終わるURIの後にFASTCGIが要求するファイル名
fastcgi_index index.php;
# fastcgi_path_info変数の値をキャプチャする正規表現の定義
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
# FastCGIサーバーに渡すパラメータを指定
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# コード300以上のレスポンスをerror_pageディレクティブで処理
fastcgi_intercept_errors on;
# FastCGIサーバーのアドレスをUnixドメインソケットパスに指定
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}
# "location ~ \.php$"の処理よりも上にする
# "非ログインか"$wp_"が空欄でない時、"/wp-admin"か"/wp-admin/"を含むURLにアクセスすると内部転送する
location ~ "/wp-admin(?:$|/)" {
# 左から順に参照し、該当が無ければ最後のuri(もしくはcode)に内部転送される
try_files $uri$wp_ $uri$wp_/ /index.php?$args$wp_ /?access=wp-admin;
location ~ \.php$ {
# 左から順に参照し、該当が無ければ最後のuri(もしくはcode)に内部転送される
try_files $uri$wp_ $uri$wp_/ /index.php?$args$wp_ /?access=wp-admin;
# PHPに渡すパラメータファイルを指定
include fastcgi_params;
# FASTCGIサーバーへのキープアライブ接続をON
fastcgi_keep_conn on;
# スラッシュで終わるURIの後にFASTCGIが要求するファイル名
fastcgi_index index.php;
# fastcgi_path_info変数の値をキャプチャする正規表現の定義
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
# FastCGIサーバーに渡すパラメータを指定
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# コード300以上のレスポンスをerror_pageディレクティブで処理
fastcgi_intercept_errors on;
# FastCGIサーバーのアドレスをUnixドメインソケットパスに指定
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}
}
## 省略 ##
$ sudo service nginx restart
で再起動します。wp-admin
および関連ファイルへのアクセスは、ログインユーザもしくはログインページにアクセスした者限定で行えるようになりました。wp-admin
の存在に気付きにくくなります(/wp-admin/admin-ajax.php
にアクセスできる場合は別)。wp-includes
wp-includes
はWordPressのシステム全般に関連するファイルが格納されているディレクトリです。wp-includes
を使用している場合があります。wp-admin
の時と同じく、ログイン者およびログインページアクセス者のみwp-includes
および関連ファイルのアクセスを許可します。$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
で設定ファイルを編集します。ドメイン名.conf
## 省略 ##
# "location ~ \.php$"の処理よりも上にする
# "非ログインで"/wp-includes"か"/wp-includes/"を含むURLにアクセス時、内部転送する
location ~ "/wp-includes(?:$|/)" {
# 左から順に参照し、該当が無ければ最後のuri(もしくはcode)に内部転送される
try_files $uri$wp_ $uri$wp_/ /index.php?$args$wp_ /?access=wp-includes;
location ~ \.php$ {
# 左から順に参照し、該当が無ければ最後のuri(もしくはcode)に内部転送される
try_files $uri$wp_ $uri$wp_/ /index.php?$args$wp_ /?access=wp-includes;
# PHPに渡すパラメータファイルを指定
include fastcgi_params;
# FASTCGIサーバーへのキープアライブ接続をON
fastcgi_keep_conn on;
# スラッシュで終わるURIの後にFASTCGIが要求するファイル名
fastcgi_index index.php;
# fastcgi_path_info変数の値をキャプチャする正規表現の定義
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
# FastCGIサーバーに渡すパラメータを指定
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# コード300以上のレスポンスをerror_pageディレクティブで処理
fastcgi_intercept_errors on;
# FastCGIサーバーのアドレスをUnixドメインソケットパスに指定
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}
}
## 省略 ##
$ sudo service nginx restart
で再起動します。wp-includes
およびその関連ファイルのアクセスは、ログイン者もしくはログインページアクセス者のみに限定されるようになりました。wp-content
wp-content
はWordPressでのアップロードファイルやテーマ、プラグイン等が格納されているディレクトリであり、wp-admin
、wp-includes
と同様に悪意あるアクセスは多くなっています。wp-content
はディレクトリそのものの名前を変更することができることから、WordPressを使用しているという情報をある程度秘匿することができます。wp-content
の場所を変更しますが、ディレクトリを直接リネームして場所を変更するとwp-content
という名前のディレクトリが消えるため、このセクション後半のNginxを用いたアクセス制限は必要ありません。wp-content
のシンボリックリンクを作成します。今回の例ではwp-content
ディレクトリをfiles
という名前でシンボリックリンクを作成します。$ ln -s /var/www/ドメイン名/apps/wp-content /var/www/ドメイン名/files
を実行し、シンボリックリンクを作成します。/var/www/ドメイン名/files
となっています。$ sudo vim /var/www/ドメイン名/apps/wp-config.php
を編集してwp-content
の場所を変更させます。wp-config.php
/** 省略 **/
/* カスタム値は、この行と「編集が必要なのはここまでです」の行の間に追加してください。 */
/** 省略 **/
// "wp-content"の場所を変更
define( 'WP_CONTENT_DIR', "{$_SERVER['DOCUMENT_ROOT']}/files" );
define( 'WP_CONTENT_URL', WP_HOME . '/files' );
/** 省略 **/
/* 編集が必要なのはここまでです ! WordPress でのパブリッシングをお楽しみください。 */
/** 省略 **/
wp-content
からfiles
に変更されていれば問題ありません(FastCGI Cacheなどのキャッシュ類が効いていると変更後の情報になっていない場合があるので注意)。wp-content
はfiles
というディレクトリ名で運用することが可能となりました。wp-content
ディレクトリそのものは存在しているため、更にNginxでディレクトリの存在を秘匿します(ディレクトリをリネームしている場合は存在しないので不要)。$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
で設定ファイルを編集します。## 省略 ##
# "location ~ \.php$"より上に置くこと
# "/wp-content"もしくは"/wp-content/"を含むURLにアクセス時、"/?access=wp-content"へ内部リダイレクトする
location ~ "/wp-content(?:$|/)" {
rewrite ^/ /?access=wp-content? last;
}
## 省略 ##
$ sudo service nginx restart
で再起動します。wp-content
へアクセスがやってきても、他の存在しないファイルと同様の動作をするようになります。Pluginsディレクトリ
wp-content
を無事変更できましたら、次はプラグイン(plugins
)ディレクトリも変更しておくとより良いでしょう。plugins
というディレクトリ名でないと使用できないものもあります。$ ln -s /var/www/ドメイン名/apps/wp-content/plugins /var/www/ドメイン名/addons
でシンボリックリンクを作成します。/var/www/ドメイン名/addons
となっています。wp-config.php
で再び追加編集を行います。wp-config.php
/** 省略 **/
/* カスタム値は、この行と「編集が必要なのはここまでです」の行の間に追加してください。 */
/** 省略 **/
// "plugins"の場所を変更
define( 'WP_PLUGIN_DIR', "{$_SERVER['DOCUMENT_ROOT']}/addons" );
define( 'WP_PLUGIN_URL', WP_HOME . '/addons' );
/** 省略 **/
/* 編集が必要なのはここまでです ! WordPress でのパブリッシングをお楽しみください。 */
/** 省略 **/
plugins
ディレクトリはaddons
ディレクトリとして変更することができました。plugins
ディレクトリそのもののアクセスを排除します。$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
で設定ファイルを編集します。ドメイン名.conf
## 省略 ##
# "location ~ \.php$"より上に置くこと
# "/plugins"もしくは"/plugins/"を含むURLにアクセス時、"/?access=plugins"へ内部リダイレクトする
location ~ "/plugins(?:$|/)" {
rewrite ^/ /?access=plugins? last;
}
## 省略 ##
$ sudo service nginx restart
で再起動します。plugins
ディレクトリへのアクセスも排除することができました。
WordPressを使用しているかチェックする
とても多くの設定を行ってきましたが、これによってサイトの構造は大きく変化しました。
そのため、構造上はWordPressを使用していることを外部からはある程度判別しにくくなったと思われます。
ただし、テーマ内で出力されるHTMLコード内にはWordPressだと分かってしまう要素が数多くあるため、テーマ内のコードもすべて改変することにより、更に秘匿することができるでしょう。
最後に、ウェブサイトがWordPressを使用しているかどうかを簡単にチェックできるサービスを使って、外部からはどのように見えるのかを調べてみることにします。
- Is that site WordPress?
これまでの設定を加えたテスト用サイトを調べてみた所、以下のような結果になりました。
このテストではチェックした項目に限り、サイトがWordPressで無いことを示しています。
WordPressを構成する要素は多岐に渡るため、このテストを突破したとしても完璧な設定とは言えませんが、ある程度の成果を残すことはできました。
WordPressその他設定
このセクションはおまけのようなもので、使うと便利かもしれない設定をいくつか紹介します。興味がなければスルーしてください。
これらはすべてfunctions.php
内に記載するものです。
functions.php
/** 省略 **/
// __return_false()
// https://developer.wordpress.org/reference/functions/__return_false/
// load_textdomain_mofile
// https://developer.wordpress.org/reference/hooks/load_textdomain_mofile/
// 翻訳ファイルを無効化
add_filter( 'load_textdomain_mofile', '__return_false' );
// NginxとWordpressの404ページがproxy cacheできない
// http://qiita.com/osamu1203/items/9ce685df1ce3649c1819
// 404ページをキャッシュ無効化させない
function remove_nocache_on_404(){
if(is_404()){
header_remove("Pragma");
header_remove("Cache-Control");
header_remove("Expires");
}
}
add_action('wp','remove_nocache_on_404');
// コメントの投稿欄でタグを無効にしてみた
// http://serenegiant.com/blog/?p=1360
// コメントのHTML使用を無効化
remove_filter('comment_text', 'make_clickable', 9);
add_filter( 'comment_text_rss', 'escape_tags', 9);
add_filter( 'comment_excerpt', 'escape_tags', 9);
function escape_tags( $comment_content ) {
if ( get_comment_type() == 'comment' ) {
$comment_content = htmlentities($comment_content, ENT_QUOTES, "UTF-8");
}
return $comment_content;
}
// WPによる自動画像圧縮を無効
add_filter('jpeg_quality', function($arg){return 100;});
add_filter( 'wp_editor_set_quality', function($arg){return 100;});
// HTMLをミニファイ(縮小)する
// unfulvio / functions.php
// https://gist.github.com/unfulvio/5889564
/* Minifies HTML and removes comments (except IE tags and comments within script tags)
*
* To disable compression of code portions, use '<!--wp-html-compression no compression-->' tag
*
* @see http://forrst.com/posts/Wordpress_Minify_output_HTML-29q
* @see http://www.intert3chmedia.net/2011/12/minify-html-javascript-css-without.html
*/
class WP_HTML_Compression
{
// Settings
protected $compress_css = true;
protected $compress_js = false;
protected $info_comment = true;
protected $remove_comments = true;
// Variables
protected $html;
public function __construct($html)
{
if (!empty($html))
{
$this->parseHTML($html);
}
}
public function __toString()
{
return $this->html;
}
protected function minifyHTML($html)
{
$pattern = '/<(?<script>script).*?<\/script\s*>|<(?<style>style).*?<\/style\s*>|<!(?<comment>--).*?-->|<(?<tag>[\/\w.:-]*)(?:".*?"|\'.*?\'|[^\'">]+)*>|(?<text>((<[^!\/\w.:-])?[^<]*)+)|/si';
preg_match_all($pattern, $html, $matches, PREG_SET_ORDER);
$overriding = false;
$raw_tag = false;
// Variable reused for output
$html = '';
foreach ( $matches as $token ) {
$tag = (isset($token['tag'])) ? strtolower($token['tag']) : null;
$content = $token[0];
if ( is_null( $tag ) ) {
if ( !empty( $token['script'] ) ) {
$strip = $this->compress_js;
} else if ( !empty($token['style'] ) ) {
$strip = $this->compress_css;
} else if ( $content == '<!--wp-html-compression no compression-->' ) {
$overriding = !$overriding;
// Don't print the comment
continue;
} else if ( $this->remove_comments ) {
if ( !$overriding && $raw_tag != 'textarea' ) {
// Remove any HTML comments, except MSIE conditional comments
$content = preg_replace('/<!--(?!\s*(?:\[if [^\]]+]|<!|>))(?:(?!-->).)*-->/s', '', $content);
}
}
} else {
if ( $tag == 'pre' || $tag == 'textarea' || $tag == 'script' ) {
$raw_tag = $tag;
} else if ( $tag == '/pre' || $tag == '/textarea' || $tag == '/script' ) {
$raw_tag = false;
} else {
if ($raw_tag || $overriding) {
$strip = false;
} else {
$strip = true;
// Remove any empty attributes, except:
// action, alt, content, src
$content = preg_replace('/(\s+)(\w++(?<!\baction|\balt|\bcontent|\bsrc)="")/', '$1', $content);
// Remove any space before the end of self-closing XHTML tags
// JavaScript excluded
$content = str_replace(' />', '/>', $content);
}
}
}
if ( $strip ) {
$content = $this->removeWhiteSpace($content);
}
$html .= $content;
}
return $html;
}
public function parseHTML($html)
{
$this->html = $this->minifyHTML($html);
}
protected function removeWhiteSpace($str)
{
$str = str_replace( "\t", ' ', $str );
$str = str_replace( "\n", '', $str );
$str = str_replace( "\r", '', $str );
while ( stristr($str, ' ' ) ) {
$str = str_replace(' ', ' ', $str);
}
return $str;
}
}
function wp_html_compression_finish($html) {
return new WP_HTML_Compression($html);
}
function wp_html_compression_start() {
ob_start( 'wp_html_compression_finish' );
}
add_action( 'get_header', 'wp_html_compression_start' );
/** 省略 **/
おわりに
多くの対策方法を紹介してきましたが、これで問題が全く発生しないというわけではありません。
クラッキング等を行う悪意のある人々は常にWordPressの脆弱性を狙っています。
彼らからの攻撃をできるだけ回避する方法として大きな役割を持つのは、WordPressを常に最新版へアップデートすることです。
脆弱性のあるファイルをそのまま利用することはリスクが大きいため、たとえ面倒だとしても更新を欠かさずに行ってください。
また、WordPressを拡張するには非常に便利なプラグインですが、便利だからと言って何でもインストールするのは大変よろしくありません。
拡張できるということは何でもできるに等しく、プラグインそのものの脆弱性や機能の重複などによって予期しない不具合が発生する可能性があります。
ウェブサイトの動作が遅くなるだけならまだしも、データベースの破壊や大切な情報の流出、更にはウィルス侵入のきっかけになってしまっては元も子もありません。
そのため、基本的にプラグインは使用しないと考え、それでもプラグインをインストールしたい際には、それが『本当にこのサイトに必要なのか』どうかを考えなければなりません。
これはプラグインに限らずテーマも同様であり、ソースコードに悪意のあるコードが含まれている可能性があるため、信頼できない所のファイルを導入するのは特にやめておくべきです。
WordPressには多くの問題が指摘されているものの、それはWordPressだけに留まらず、利用する者の意識と行動によってその危険性は大きく左右されます。
少しでもリスクを減らすために、ウェブサイトを運営する際にはセキュリティについて日々意識していきましょう。
参考資料
- WordPressセキュリティ対策強化の基本
- Nginx causes 301 redirect if there's no trailing slash
- WordPressサイトを徹底的に秘匿化する
- WordPress用 Apache/2.4.6 設定まとめ(2019/04/21)
- nginxのconfで複数の条件を判定してリダイレクト
- Hide the fact a site is using WordPress?
- curl コマンド 使い方メモ
- WordPress のユーザー・一覧表示対策
- nginx rewrite おさえるべきポイント
- Nginxでトレイリングスラッシュとファイル拡張子を削除する秘伝わざ
- いまさら聞けないNGINXコンフィグ_F5-NGINX-Community-20200805
- nginx構成で末尾のスラッシュを使用する
- WordPressのアクションフックの種類と実行順
- ファイルパーミッションの変更
- Linux: SUID、SGID、スティッキービットまとめ
- 【Linux】スティッキービットはなぜ必要なのか?
- nfsarmento / nginx-wordpress.conf
- Nginxで複数条件のIF文を書く方法がすごいw
- Pitfalls and Common Mistakes
- CSRF トークンの作成方法と、random_bytes の適切なバイト数
- 2022年版、WordPress(ワードプレス)のセキュリティ設定マニュアル
- .htaccessに追記した方が良いコード【WordPressセキュリティ対策】
- wp-links-opml.php | WordPress.org
- リンクデーターのエクスポート&インポート(wp-links-opml)ブログロール…美味しそう?
- WordPressのxmlrpc.php詳細ガイド(xmlrpc.phpとは、セキュリティリスク、無効にする方法)
- nginx連載5回目: nginxの設定、その3 - locationディレクティブ
- Understanding Nginx location directive and redirects
- nginxでサブパスに複数のWordPressを配置する方法
- 正規表現を用いる際のパフォーマンスチェックリスト
- nginx、怖いリバースプロキシの話(正規表現のキャプチャがURIデコードされて困った件)
- 正規表現あれこれ
- WordPress / wp-settings.php
- wordpress関数を外から使うにはwp_load.phpを読み込む
- WordPressユーザーの「権限グループ」と「権限」徹底解説ガイド
- 【wordpress】プラブインなしで管理バーを権限別に表示・非表示にする
- 0からREST APIについて調べてみた
- REST API Handbook
- wordpressで謎の転送処理されているのを突き止める
- What does "This XML file does not appear to have any style information". mean? [closed]
- NginxによるTrailingSlashのメモ
- WordPress Query Vars
- WordPress : head内に自動出力される余分なタグを削除する。
- APIで使うパラメータを渡すのに便利!WordPressでパラメータをURLに付加する方法
- [WP]wp_footer()で出力されるJavascriptを非表示にする方法
- WordPress4.4以降からhead内に挿入されるようになった不要なタグ「Embed」を削除。
- WordPressのヘッダーに出力される不要なタグを削除する
- WordPressのHTTPヘッダーにあるX-Pingbackを無効化
- Nginxで複数条件のIF文を書く方法がすごいw