WordPress セキュリティ対策の基本
WordPress は世界中で広く使われている CMS(コンテンツ管理システム)である一方、そのシェアの大きさゆえに攻撃の対象になりやすいという側面もあります。特に初期設定のまま運用していたり、基本的な対策が施されていない場合、不正アクセスや情報漏えいといったリスクにさらされる可能性が高まります。
以下では、WordPress サイトを安全に運用するために「最低限実施したい基本対策」から「可能であれば取り入れたい追加対策」までを解説します。また、.htaccess を用いたアクセス制御や HTTP セキュリティヘッダーの設定、ログインページの防御方法、ユーザー名漏えい対策、パフォーマンス向上のための設定など、実践的な例を交えながら紹介します。
更新日:2025年07月25日
作成日:2025年07月23日
最低限実施したい基本対策
最低限の対策と、可能なら実施したい追加対策
WordPress は世界中で広く利用されている CMS である一方、悪意ある攻撃の対象にもなりやすい存在です。被害を未然に防ぐためには、基本的なセキュリティ対策を確実に実施し、可能であれば追加の保護策も取り入れることが重要です。
- WordPress 本体・プラグイン・テーマを常に最新版に保つ
- 常に最新バージョンにアップデートするのが大切です。
- 古いバージョンには既知の脆弱性が含まれていることが多く、攻撃の標的になりやすくなります。
- 定期的にアップデートを確認し、可能であれば自動更新の設定をします。
- 信頼できるプラグイン・テーマのみを使用
- 公式のWordPressディレクトリや、実績のある開発元が提供するテーマやプラグインを利用します。
- 不要なプラグイン・テーマの削除
- 無効化だけではセキュリティリスクは残ります。
- 使用していないものは完全に削除します。
多くのWordPressへの攻撃は、プラグインの脆弱性を狙ったものであり、次いでログイン試行(ブルートフォース)攻撃が多く報告されています。
- 推測されにくいユーザー名・強力なパスワードを使用する
- ユーザー名がログイン ID そのものであるため、漏れるとパスワード総当たり攻撃にさらされやすくなります。そのため、「admin」「user」「test」などの単純なものを避けるべきです。
- パスワードは英数字・記号を含む20文字以上の強力な文字列にする。
- 参考:パスワードのベストプラクティス
- ニックネームを設定する
- WordPressでは投稿者名などに「ログインID」が表示されることがあります。ユーザーのプロフィール画面で「ニックネーム」を「ユーザー名」とは異なる値に設定し、「ブログ上の表示名」にニックネームを選択することで、ログインIDの漏洩を防止します。
- SSL(HTTPS)を必ず有効にする
- 通信内容を暗号化することで、パスワードや個人情報の盗聴を防止できます。
- 定期的にバックアップを取得する
- どれだけセキュリティを強化しても、リスクをゼロにはできません。
- バックアップは「最後の砦」です。
- プラグインやサーバー側のバックアップ機能を活用し、確実に復旧できるようにします。
- ディレクトリリスティングの無効化
- .htaccess に Options -Indexes を記述し、ディレクトリ内のファイル一覧が表示されることを防ぎます(Apache 環境)。
- ログインページの保護
- wp-config.php の保護
WordPressの設定ファイル wp-config.php を保護します。
対策例:
- .htaccess で直接アクセスを拒否
- wp-config.php を public_html より上の階層に移動(以下リンク参照)
最重要ポイント:まずはアップデートを確実に!
どんなに強固なセキュリティ対策をしていても、脆弱なプラグインやテーマを放置すれば無意味になってしまいます。まず最初に、すべてを最新の状態に保つことが何よりも重要です。
他の対策は「脆弱性が存在しないこと」を前提にしています。
攻撃者の多くは、既に報告されている脆弱性情報(CVEなど)を利用して自動化された攻撃を仕掛けています。そのため、アップデートがされていない WordPress・プラグイン・テーマは、それだけで「既知の穴」が開いたままの状態といえます。
過去には、古いプラグインに存在していたパストラバーサル(ディレクトリトラバーサル)脆弱性が悪用され、wp-config.php や .htpasswd など重要ファイルが読み取られる被害が報告されています。
このような場合、アクセス制限などの対策では防ぐことができません。ファイルシステムそのものが不正に読み取られてしまうため、根本的な対策は脆弱性の修正(=アップデート)しかありません。
可能なら実施したい追加対策
- 管理画面への2段階認証(2FA)
- XML-RPC の無効化(使用していなければ)
- ユーザー名漏えい対策
- user_nicename をログイン ID とは異なるものに変更
- REST API 制限(認証なしのユーザー情報公開を制限)
- ?author=1 などのアクセス防止
- 著者アーカイブページへのアクセスを制限(使用していなければ)
- head 内の不要なタグを削除
- WordPress バージョン情報を出力する wp_generator などの削除
- 管理画面からのファイル編集の無効化
- 管理者アカウントが乗っ取られた場合の被害を軽減
.htaccess に追加できるセキュリティ設定
.htaccess は Apache サーバーで使われるディレクティブファイルで、アクセス制御やファイル実行制限などに使用します。.htaccess は WordPress インストールディレクトリにあります。
以下の例は、Webサーバーは Apache 2.4 以降であることを前提にしています。
記述した内容を、WordPress によって上書きされないようにするためには、.htaccess ファイルの # BEGIN WordPress と # END WordPress タグの外側に記述する必要があります。
また、.htaccess の設定を誤るとサイト全体が閲覧不能になるリスクもあるため、バックアップを取った上で慎重に作業することをおすすめします。
ディレクトリリスティング無効化
以下を記述してディレクトリ内のファイル一覧が表示されることを防ぎます。
Options -Indexes
アクセス制限を検討すべきファイル・ディレクトリ
WordPress サイトを保護する上で重要なのは、「公開すべきではないファイルやディレクトリ」に対する不要なアクセスを遮断することです。
特に、設定ファイルやインクルードファイル、バックアップファイルなどは、第三者からのアクセスを許してはならない重要データを含んでいることがあります。
また、アップロードフォルダに .php ファイルが配置された場合、悪意あるコードが実行される可能性があるため、別途対策が必要です。
以下は、アクセス制限を検討すべき代表的なファイルやディレクトリの一覧と、その理由をまとめたものです。適切に .htaccess などで制御することで、リスクを軽減できます。
種別 | 対象 | 理由・目的 |
---|---|---|
設定ファイル | wp-config.php | データベース接続情報や認証用キーが含まれており、漏洩すれば完全な乗っ取りが可能になる。直接アクセスを制限することで読み取りを防止。 |
設定ファイル | .htaccess | アクセス制御やリダイレクト設定が書かれている。改ざんや外部からの閲覧を許すと、意図しないアクセス許可・制御ルールの漏洩につながる。 |
PHPスクリプト | xmlrpc.php | 外部アプリとの連携用だが、総当たり攻撃やDoS攻撃の対象になりやすい。使用していない場合は無効化またはアクセス制限が推奨される。 |
情報ファイル | readme.html | WordPressのバージョン情報が記載されており、脆弱性の特定・攻撃の糸口に使われる。不要であれば削除または制限。 |
情報ファイル | license.txt | バージョンが推測可能であり、攻撃者にヒントを与える可能性がある。readmeと同様に対応。 |
インクルードファイル | /wp-includes/ | 本来はテーマ・プラグインから内部的に読み込まれるものであり、直接のWebアクセスを許すべきでない。攻撃に悪用されるおそれがある。 |
インクルードファイル | /wp-admin/includes/ | 管理画面内部処理用のファイル群であり、外部からの直接実行はセキュリティリスクとなる。 |
アップロードフォルダ | /wp-content/uploads/ | 本来は画像やドキュメント保存用。誤って .php ファイルがアップロードされた場合に備え、実行制限を設けるべき。 |
バックアップ・一時ファイル | .bak, .old, .zip, .tar.gz, .sql など | バックアップファイルには設定ファイルやDB情報などの機密データが含まれていることが多く、外部からのアクセスを絶対に防ぐ必要がある。 |
アクセス制限の設定例
以下は、WordPress の重要ファイルやディレクトリに対して不要なアクセスを防ぐための .htaccess の具体的な設定例です。Apache環境(Apache 2.4 以降)が前提です。
# --------------------------------------------------
# wp-config.php, .htaccess 自身, xmlrpc.php へのアクセス制限
# --------------------------------------------------
<FilesMatch "^(wp-config\.php|\.htaccess|xmlrpc\.php)$">
Require all denied
</FilesMatch>
# --------------------------------------------------
# readme.html, license.txt など WordPress 情報漏洩対策
# --------------------------------------------------
<FilesMatch "^(readme\.html|license\.txt)$">
Require all denied
</FilesMatch>
# --------------------------------------------------
# wp-admin/includes や wp-includes 配下のファイルに対する不正アクセスを遮断
#
# RewriteBase は、WordPress が自動生成した .htaccess の RewriteBase と同じ値にする
# (例:ルート直下に設置している場合は /、サブディレクトリ /blog/ などの場合は /blog/)
#
# ※マルチサイトを使用している場合は、以下のルールのうち
# RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
# は削除してください(正規動作に支障が出る場合があります)
#
# 各ルールの目的:
# RewriteRule ^wp-admin/includes/ - [F,L] → 管理画面の内部ファイル群への直接アクセスを遮断
# RewriteRule !^wp-includes/ - [S=3] → wp-includes 配下でない場合は、以下3つのルールをスキップ
# RewriteRule ^wp-includes/[^/]+\.php$ - [F,L] → wp-includes 直下にある PHP ファイルへの直接アクセスを遮断
# RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L] → TinyMCE の言語ファイルに対する PHP 実行を遮断
# RewriteRule ^wp-includes/theme-compat/ - [F,L] → 古いテーマ互換用ファイルへの直接アクセスを遮断
# --------------------------------------------------
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^wp-admin/includes/ - [F,L]
RewriteRule !^wp-includes/ - [S=3]
RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]
</IfModule>
# --------------------------------------------------
# バックアップファイルや一時ファイルのアクセス制限
# --------------------------------------------------
<FilesMatch "\.(bak|old|sql|tar\.gz|zip)$">
Require all denied
</FilesMatch>
# BEGIN WordPress
# ...(WordPress により出力される設定) 以降省略
※ WordPress をサブディレクトリにインストールしている場合は、RewriteBase をそのディレクトリ名(WordPress が自動生成する .htaccess の RewriteBase の値)に変更する必要があります(34行目)。
対象 | 解説 |
---|---|
wp-config.php, .htaccess, xmlrpc.php | サイトの設定情報や制御ルールが含まれている重要ファイルへの直接アクセスを拒否します。xmlrpc.php は使用していない場合に無効化が推奨されます。 |
readme.html, license.txt | WordPress のバージョンやライセンス情報が記載されており、攻撃者に情報を与える要因となるため制限します。 |
wp-admin/includes/配下 wp-includes/配下 |
wp-admin/includes/ や wp-includes/ 以下のファイルは、WordPress の内部動作にのみ使用されるものであり、通常はブラウザから直接アクセスされるべきではありません。これらへの外部からのアクセスは、不正利用や脆弱性の攻撃経路となる可能性があるため、リライトルールにより複数のルールで条件分岐しながら遮断しています。(Securing wp-includes)。 |
バックアップ・一時ファイル | 誤って残されたバックアップファイルから設定情報やDB内容が漏洩するリスクを回避します。拡張子単位(.bak, .old, .sql, .zip, .tar.gz)でブロック。 |
アップロードフォルダの保護
WordPress では、wp-content/uploads/ ディレクトリには通常 .php ファイルをアップロードできないようになっていますが、サーバー側では PHP の実行自体は許可されているため、万が一 .php ファイルが配置された場合、そのまま実行されてしまうリスクがあります。
そのため、アップロードディレクトリ直下(wp-content/uploads/)に .htaccess を配置し、PHP の実行を明示的に禁止しておくことが推奨されます。
# --------------------------------------------------
# wp-content/uploads 以下での PHP 実行を禁止
# このファイルは wp-content/uploads/.htaccess に配置します
# --------------------------------------------------
<FilesMatch "\.php$">
Require all denied
</FilesMatch>
セキュリティ HTTP ヘッダー設定
WordPress サイトのセキュリティを強化する方法のひとつに、HTTP ヘッダーを通じたブラウザへのセキュリティ設定の指示があります。
以下は、.htaccess に記述することで有効になる、主要なセキュリティ HTTP ヘッダーの設定例です。
<IfModule mod_headers.c>
# MIME スニッフィング対策
Header set X-Content-Type-Options "nosniff"
# iframe 埋め込み制限(クリックジャッキング防止)
# 古いブラウザ対応が必要なら以下のコメントアウトを外す
# Header always set X-Frame-Options "SAMEORIGIN"
# iframe 埋め込み制限(推奨)Content-Security-Policy(CSP)
Header set Content-Security-Policy "frame-ancestors 'self';"
# HTTPS 強制(HSTS) ※ HTTPS に未対応のサイトには絶対に設定しないこと()
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# X-Powered-By の削除(サーバーやPHPのバージョン情報などを隠す)
Header unset X-Powered-By
</IfModule>
ヘッダー名 | 説明 |
---|---|
X-Content-Type-Options | ブラウザがファイルの MIME タイプを勝手に判断しないようにする。XSS(クロスサイトスクリプティング)攻撃の防止に有効。 |
X-Frame-Options | 他サイトからの iframe 埋め込みを防ぐことで、クリックジャッキング攻撃を防止。但し現在は Content-Security-Policy の使用が推奨されており、非推奨ヘッダーとなっている。 |
Content-Security-Policy | frame-ancestors 'self' により、他サイトからの iframe 埋め込みを防止。X-Frame-Options の後継・上位互換として広く推奨される(Content-Security-Policy)。 |
Strict-Transport-Security | HTTPS 通信を強制。ブラウザに対し、すべての通信を HTTPS に限定させる指示を行う(HSTS)。※ HTTPS に未対応のサイトには絶対に設定しないこと(以下参照)。 |
X-Powered-By | HTTPレスポンスヘッダーから X-Powered-By ヘッダーを削除する。このヘッダーには通常、PHPのバージョンや使用しているサーバー技術(例:PHP/8.2.0)が含まれており、攻撃者にとって有用な情報となる可能性があるため、不要な情報を非表示にすることで、サーバー構成の特定を難しくし、セキュリティを強化します。 |
HTTPS 未対応サイトで Strict-Transport-Security を設定すると
Strict-Transport-Security ヘッダーは、「このサイトは今後すべて HTTPS でアクセスしてね」という指示をブラウザに与えるものです。そのため、HTTPS未対応のサイトにこれを設定してしまうと以下のような問題が発生します。
- ブラウザが http:// アクセスを自動で https:// に書き換える
- でもサーバー側は HTTPS に対応していない
- → ブラウザはエラーを返し、サイトが表示されなくなる
この設定は、ブラウザにキャッシュ(記憶)されるため、HSTS ヘッダーを消してもすぐには元に戻りません。該当ブラウザの HSTS ポリシー(セキュリティキャッシュ)をクリアする必要があります。
テスト時はまず max-age=60(1分間)など短期間で設定しておくのも安全策です。
Content-Security-Policy
Content-Security-Policy ヘッダーには、frame-ancestors のほかにも多くのディレクティブが存在します。ただし、WordPress はインラインのスクリプトやスタイルを多用しているため、script-src や style-src を厳格に設定すると、正規機能がブロックされて、非互換による不具合が発生するおそれがあります。
比較的安全に導入できる CSP ディレクティブ
一方、以下のディレクティブは互換性への影響が少なく、WordPress サイトに比較的簡単に導入できて、実害の出にくいセキュリティ強化として使用できます。
ディレクティブ | 推奨値 | 説明 |
---|---|---|
frame-ancestors | 'self' | 他サイトからの iframe 埋め込みを拒否(クリックジャッキング対策) |
object-src | 'none' | Flash や Java アプレットなど、<object> 要素の読み込みを完全に禁止 |
base-uri | 'self' | <base> タグを外部ドメインに設定するのを防止(意図しない相対URLの変換を抑止) |
form-action | 'self' | <form> の送信先を同一オリジンに限定。クロスサイト POST 攻撃防止(CSRF対策補助) |
.htaccess に以下を追記することで、これらのディレクティブを簡単に有効化できます。
<IfModule mod_headers.c>
Header set Content-Security-Policy "frame-ancestors 'self'; object-src 'none'; base-uri 'self'; form-action 'self';"
</IfModule>
【注意】
Content-Security-Policy(CSP)はリクエストあたり 1つのみ有効です。複数の CSP ヘッダーが存在すると、ブラウザが最初の1つだけを適用するため、他の CSP と統合する(まとめる)必要があります。
また、上記設定は、CSP を比較的安全に導入する一例ですが、実際に適用する前に、以下のように 一度 Report-Only モードで挙動を確認してから本番環境に反映することを推奨します。
<IfModule mod_headers.c>
# Report-Only モードで試して問題がなければ Content-Security-Policy で本適用する
Header set Content-Security-Policy-Report-Only "frame-ancestors 'self'; object-src 'none'; base-uri 'self'; form-action 'self';"
</IfModule>
functions.php で CSP を設定
必要に応じて、functions.php で send_headers アクションと PHP の header() 関数を使って Content-Security-Policy(CSP)を出力することもできます。
// WordPress の PHP コード内で適用(send_headers フック)
add_action('send_headers', function () {
header("Content-Security-Policy: "
. "base-uri 'self'; "
. "form-action 'self'; "
. "object-src 'none'; "
. "frame-ancestors 'self';");
});
以下は Report-Only モードを設定する例です。
add_action('send_headers', function () {
// Report-Only モード
header("Content-Security-Policy-Report-Only: "
. "base-uri 'self'; "
. "form-action 'self'; "
. "object-src 'none'; "
. "frame-ancestors 'self';");
});
パフォーマンス向上に役立つキャッシュ設定
以下はセキュリティ対策そのものではありませんが、同じ .htaccess に記述することで、サイトの表示速度向上やサーバー負荷の軽減に貢献する設定です。
特に画像やCSS・JavaScriptなどの静的ファイルに対して、Expires ヘッダーを使ってブラウザキャッシュの有効期限を明示的に指定することで、再読み込み時のHTTPリクエストを削減し、ユーザー体験の改善につながります。
たとえば、変更頻度が少ない画像やフォントファイルには「1年」など長めのキャッシュ期間を指定することで、無駄な通信を避けられます。一方、頻繁に更新される可能性のある HTML などには「1分」など短い有効期限を設定することで、更新内容が即座に反映されやすくなります。
CSS や JavaScript なども、基本的には「1年」など長めのキャッシュを指定できますが、ファイル名にバージョン番号(例:style.css?v=2)を付けるなどしてキャッシュバスティングを行うことが前提です。これにより、ファイルの内容が変わった際にもブラウザが新しいファイルを読み込むようになり、長期キャッシュと更新反映の両立が可能になります。
適切なキャッシュ期間は、サイトの構成や運用ポリシーによって異なります。頻繁に CSS や JS を変更するサイトでは、「1日」〜「1週間」など短めのキャッシュ期間を指定する方が適しているケースもあります。
ファイルの性質や更新頻度に応じて適切なキャッシュ期間を指定することが、パフォーマンスと柔軟性の両立につながります。
<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault "access plus 1 day"
# 適切なキャッシュ期間はサイトごとに異なるため、適宜変更してください。
# HTML は短め
ExpiresByType text/html "access plus 1 minute"
# 画像
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
ExpiresByType image/x-icon "access plus 1 year"
# CSS
ExpiresByType text/css "access plus 1 year"
# JavaScript
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType text/javascript "access plus 1 year"
# PDF
ExpiresByType application/pdf "access plus 1 year"
# フォント(最新のMIME形式)
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType font/woff "access plus 1 year"
ExpiresByType font/ttf "access plus 1 year"
ExpiresByType font/otf "access plus 1 year"
ExpiresByType font/eot "access plus 1 year"
</IfModule>
ログインページの保護
WordPress のログインページ(wp-login.php)は最も狙われやすい攻撃対象の一つです。以下のような対策を組み合わせることで、ブルートフォース攻撃や総当たり攻撃への防御を強化できます。
IP アドレス制限(.htaccess)
以下は .htaccess を使って特定の IP アドレスからのみアクセスを許可する方法です。
「xxx.xxx.xxx.xxx」の部分をアクセスを許可したいIPに変更します。
複数の IP を許可したい場合は、Require ip xxx.xxx.xxx.xxx の行を追加します。
但し、この方法は固定 IP が使えない環境(可変IP:モバイル回線・一般プロバイダ)では不向きです。
<Files "wp-login.php">
<RequireAll>
Require all denied
Require ip xxx.xxx.xxx.xxx
</RequireAll>
</Files>
Basic 認証をかける(.htaccess + .htpasswd)
WordPress のログインページに入る前に、ID/パスワードによる追加認証を要求する方法です。
Apache レベルでの認証であるため、WordPress に到達する前に不正アクセスを遮断できます。
但し、ログイン情報が平文で送信されるため、HTTPS 環境での利用が必須です。
また、Basic 認証を設定するとアプリケーションパスワードは使用できなくなります。
アプリケーションパスワードは、外部のアプリケーションやスクリプト(サービス)で WordPress の REST API に安全にアクセスするために使用する専用のパスワードです。
wp-login.php に Basic 認証を設定するには、.htaccess に以下の認証ルールを記述し、.htpasswd にユーザー名+パスワードハッシュを保存します。
<Files wp-login.php>
AuthType Basic
AuthName "Restricted Area"
AuthUserFile /full/path/to/.htpasswd
Require valid-user
</Files>
AuthUserFile の /full/path/to/.htpasswd は、実際のサーバー上の絶対パスに置き換えます。
(例)AuthUserFile /home/youraccount/.htpasswds/yourdomain.com/.htpasswd
.htpasswd はドキュメントルート外(public_html 等より上の階層)の安全な場所に保存します。
.htpasswd の生成には htpasswd コマンドや、WordPress 関連のオンラインジェネレーターを使えます。なお、APR1-MD5 などの古い形式は非推奨です。必ず bcrypt 形式を使用してください。
.htpasswd の作成方法については以下を御覧ください。
セキュリティ上の注意点
- Basic 認証では、毎回のリクエストで ID とパスワードが HTTP ヘッダーに含まれて平文で送信されるため、通信が暗号化されていない場合は容易に傍受されるリスクがあります。
- そのため、通信が暗号化される HTTPS 環境での利用が必須です。HTTP のままだと、認証情報が平文のままネットワーク上を流れるため、極めて危険です。
- Basic 認証には明確なログアウト機能がないため、一度認証されると、ブラウザを閉じるか、認証情報をクリアしない限り、認証状態が続きます。
- 共用端末や公共のネットワークでは、作業後に必ずブラウザを終了する習慣を徹底する必要があります。
プラグインを利用
ログインページを保護する方法としては、上記以外にも、ログイン試行回数制限やログイン画面 URL を変更するプラグインを利用する方法があります。以下は代表的なプラグインの例です(他にも多数あり)。
プラグイン名 | 主な機能 |
---|---|
Limit Login Attempts Reloaded | ログイン試行回数の制限、ロックアウト、通知メールなど。 |
WPS Hide Login | wp-login.php の URL を仮想的に変更。(攻撃対象を隠す) |
SiteGuard WP Plugin | ログインURLの変更、ログイン試行回数制限、画像認証、XMLRPC 防御、管理ページログイン時の通知メールなど多機能。 |
組み合わせによる強化
WordPressサイトのログインセキュリティを強化するには、「ログインURLを変更して攻撃対象を隠す」+「ログイン試行回数を制限して不正アクセスを防ぐ」 という2段階の防御が非常に有効です。
- Limit Login Attempts Reloaded と WPS Hide Login を組み合わせて使用することもできます。
- SiteGuard WP Plugin には両方の機能が含まれており、総合的な対策が可能です。なるべくプラグインを少なくして管理を簡単にしたい場合は、こちらがおすすめです。
プラグインの長所
- WordPress の操作だけで導入・管理できるため導入が簡単
- 画面上から設定変更できる
- ログイン試行のログを取れる(攻撃の可視化)
- 柔軟性が高い
プラグインの短所
- WordPressが起動してから動作する(すでにリソースを消費している)
- 処理コストがかかるため、大規模な攻撃にはやや弱い
- プラグインが無効化されると対策も無効(何らかの脆弱性で管理画面に入られた場合、セキュリティプラグイン自体が無効化されるおそれがある)
- プラグインの脆弱性自体が攻撃対象となる可能性もある
それぞれのプラグインの詳細や使い方は以下のリンクからご覧いただけます。
ユーザー名漏えい対策
WordPressでは、/?author=1 のようなURLでアクセスすると、ユーザー名(user_nicename)が外部に漏れる可能性があります。初期設定ではこの値がログインID(user_login)と同じになっていることが多く、実質的にログインIDが判明してしまいます。
さらに、WP REST API が有効な場合、認証なしでもユーザー情報を取得できるため、攻撃者にとって有利な状況となり、ブルートフォース攻撃や辞書攻撃の成功率が高まるリスクがあります。
対策例(優先順位順):
- user_nicename をログインIDと異なるものに設定
- ログインIDがそのまま公開されるのを根本的に防ぐ基本対策。他の手段(REST API や author リンク)から user_nicename を通じてIDが漏れる可能性があるため、最も優先度が高い対策です。
- REST API でのユーザー情報取得を制限
- 認証なしで wp-json/wp/v2/users にアクセスされるとユーザー一覧が取得できるため、これを制限することで機械的な情報収集(ブルートフォース前のID特定)を防止できる。
- REST API 自体を制限する場合は、APIを利用する他のプラグインやテーマ機能に影響する場合があるため、テストの上で導入するのが望ましい。
- ?author=1 によるユーザー情報取得の制限
- author=ID によるリダイレクトで user_nicename がURLに現れることを防止。明示的な攻撃者によるユーザー特定手法として知られており、ピンポイント対策になる。
- 著者アーカイブページを無効化(使っていない場合)
- 複数投稿者を扱わないサイトでは不要な機能のため、無効化することで間接的な漏洩リスクを排除。
user_nicename をログインIDと異なるものに設定
user_nicename は管理画面上からは変更できないため、user_nicename をログイン ID(user_login)と異なるものにするには以下のような方法があります。
- データベースを直接編集
- プラグインを利用
- functions.php で変更
- ユーザー編集画面で変更(functions.php に記述)
以下は functions.php で wp_update_user() を使って、ユーザーID が 1
のユーザーの user_nicename を変更する例です。以下のコードは一度アクセスするだけで実行されるので(ページを開くたびに毎回実行されるため)、実行後は必ず add_action() をコメントアウトします。
function safe_update_user_nicename_on_init() {
$user_id = 1; // 対象ユーザーID
$new_nicename = 'foo-public'; // 新しい user_nicename ※ 実際にはユーザー名が推測されにくい名前にします。
$slug = sanitize_title_with_dashes($new_nicename);
// すでに同じスラッグが使われていないか確認
$existing_user = get_user_by('slug', $slug);
if ($existing_user && $existing_user->ID != $user_id) {
error_log('すでに使われているスラッグのため、更新を中止しました。');
return;
}
// 更新
$result = wp_update_user([
'ID' => $user_id,
'user_nicename' => $slug
]);
if (is_wp_error($result)) {
error_log('ユーザー更新に失敗しました: ' . $result->get_error_message());
} else {
error_log('ユーザーID ' . $user_id . ' の user_nicename を ' . $slug . ' に更新しました。');
}
}
// 実行が完了したら以下の add_action() をコメントアウト
add_action('init', 'safe_update_user_nicename_on_init');
詳細については以下を御覧ください。
WP REST API によるユーザー一覧表示を抑制
WP REST API を無効にするのではなく、wp-json/wp/v2/users や ?rest_route=/wp/v2/users にアクセスすると、401 Unauthorized を返して、ユーザーの一覧が表示されないようにする例です。
以下を functions.php に記述します。
// REST API のエンドポイント定義を変更するためのフィルターを追加
add_filter('rest_endpoints', function ($endpoints) {
// 対象とするエンドポイント(ユーザー一覧と個別ユーザー情報)
foreach (['/wp/v2/users', '/wp/v2/users/(?P<id>[\d]+)'] as $route) {
// 指定したエンドポイントが実際に存在しているか確認
if (isset($endpoints[$route])) {
// 該当ルートに定義された各エンドポイント(HTTP メソッド別)をループ処理(&$endpoint の & は参照渡し)
foreach ($endpoints[$route] as &$endpoint) {
// エンドポイントが配列形式で、permission_callback が定義されているか確認
if (is_array($endpoint) && isset($endpoint['permission_callback'])) {
// 元の permission_callback を変数に保持しておく(他のプラグインやコアの挙動を維持するため)
$original_callback = $endpoint['permission_callback'];
// 新しい permission_callback を定義(ログイン済みかつ、元の判定も通過する必要あり)
$endpoint['permission_callback'] = function ($request) use ($original_callback) {
// is_user_logged_in() が true かつ、元の permission_callback も true を返す場合のみ許可
return is_user_logged_in() && call_user_func($original_callback, $request);
};
}
}
}
}
// 変更を加えたエンドポイント情報を返す
return $endpoints;
});
コードの詳細ついては以下を御覧ください。
また、その他のユーザー名漏えい対策やその詳細については以下を御覧ください。
管理画面からのファイル編集の無効化
管理画面からのファイル編集機能(ダッシュボードでのファイル編集)は便利な反面、誤操作や攻撃者による悪用のリスクがあるため、以下を wp-config.php に追加して無効化するのが望ましいです。
特に、管理者アカウントが乗っ取られた場合の被害を軽減する手段として有効です。
define('DISALLOW_FILE_EDIT', true);
管理画面経由で PHPコ ードを直接改変できる機能を無効化することで、仮に管理画面に侵入された場合でも、即座に悪意あるコードを埋め込まれるリスクを減らすことができます。
設定サンプル
以下は、上記で説明した内容を反映した設定のサンプルです。
.htaccess
以下は .htaccess に追加できるセキュリティ関連の設定サンプルです。
この設定は、Apache 2.4 以降が稼働していること、および mod_rewrite.c、mod_headers.c、mod_expires.c が有効であることを前提としています(多くの共有サーバーでは有効です)。
サイトの構成や運用ポリシーにより適切な設定は異なるため、必要に応じて調整してください。
なお、このサンプルにはログインページの保護設定は含まれていません。必要に応じて、IP アドレス制限や Basic 認証の設定、またはセキュリティプラグインの利用をご検討ください。
※ この設定は本番環境で使用する前にテストすることを推奨します
注意
HTTPS 通信を強制する Strict-Transport-Security は HTTPS に未対応のサイトには設定しないようにしてください(サイトが表示されなくなります)。
また、Content-Security-Policy はまず、Content-Security-Policy-Report-Only(Report-Only モード)で問題がないことを確認するとをおすすめします。
# ディレクトリリスティング(ファイル一覧の表示)無効化
Options -Indexes
# --------------------------------------------------
# wp-config.php, .htaccess 自身, xmlrpc.php へのアクセス制限
# --------------------------------------------------
<FilesMatch "^(wp-config\.php|\.htaccess|xmlrpc\.php)$">
Require all denied
</FilesMatch>
# --------------------------------------------------
# readme.html, license.txt など WordPress 情報漏洩対策
# --------------------------------------------------
<FilesMatch "^(readme\.html|license\.txt)$">
Require all denied
</FilesMatch>
# --------------------------------------------------
# wp-admin/includes や wp-includes 配下のファイルに対する不正アクセスを遮断
# --------------------------------------------------
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^wp-admin/includes/ - [F,L]
RewriteRule !^wp-includes/ - [S=3]
RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]
</IfModule>
# --------------------------------------------------
# バックアップファイルや一時ファイルのアクセス制限
# --------------------------------------------------
<FilesMatch "\.(bak|old|sql|tar\.gz|zip)$">
Require all denied
</FilesMatch>
# --------------------------------------------------
# セキュリティ HTTP ヘッダー設定
# --------------------------------------------------
<IfModule mod_headers.c>
# MIME スニッフィング対策
Header set X-Content-Type-Options "nosniff"
# Content-Security-Policy(iframe 埋め込み制限、object 要素の読み込み禁止、base タグの外部ドメイン設定防止、form の送信先を同一オリジンに限定)
Header set Content-Security-Policy "frame-ancestors 'self'; object-src 'none'; base-uri 'self'; form-action 'self';"
# HTTPS を強制する場合は以下のコメントを外す ※ HTTPS に未対応のサイトには絶対に設定しないこと
# Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# X-Powered-By の削除(サーバーやPHPのバージョン情報などを隠す)
Header unset X-Powered-By
</IfModule>
# --------------------------------------------------
# キャッシュ設定(キャッシュ期間は、サイトの構成や運用ポリシーにより調整)
# --------------------------------------------------
<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault "access plus 1 day"
# HTML は短め
ExpiresByType text/html "access plus 1 minute"
# 画像
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
ExpiresByType image/x-icon "access plus 1 year"
# CSS
ExpiresByType text/css "access plus 1 year"
# JavaScript
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType text/javascript "access plus 1 year"
# PDF
ExpiresByType application/pdf "access plus 1 year"
# フォント(最新のMIME形式)
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType font/woff "access plus 1 year"
ExpiresByType font/ttf "access plus 1 year"
ExpiresByType font/otf "access plus 1 year"
ExpiresByType font/eot "access plus 1 year"
</IfModule>
functions.php
head 内に出力される不要なタグを削除してセキュリティと軽量化を強化するサンプル
以下のコードを functions.php に追加すると、WordPress のバージョン情報や使っていない機能のタグを削除して、セキュリティ対策や読み込みの軽量化に役立ちます。
/* head 内の不要なタグを削除 */
// WordPress バージョン情報を削除(攻撃者にバージョンを知らせない)
remove_action( 'wp_head', 'wp_generator' );
// RSS フィードや REST API に含まれる「generator」情報(WordPress のバージョン情報など)を削除
add_filter( 'the_generator', '__return_empty_string' );
// XML-RPC 機能を使用していない場合は、エンドポイント自動検出用タグ(rsd_link)を削除
remove_action( 'wp_head', 'rsd_link' );
// Windows Live Writer 用のリンクタグ(wlwmanifest_link)を削除(現在はほとんど使用されていない)
remove_action( 'wp_head', 'wlwmanifest_link' );
// 短縮URL(shortlink)のリンクタグを削除(使っていなければ不要)
remove_action( 'wp_head', 'wp_shortlink_wp_head' );
// REST API のエンドポイント URL を明示する head 内のタグを削除(REST API 自体は機能します)
remove_action( 'wp_head', 'rest_output_link_wp_head' );
// HTTP レスポンスヘッダーに追加される REST API エンドポイント情報も削除(REST API 自体は問題なく動作します)
remove_action( 'template_redirect', 'rest_output_link_header', 11 );
// 絵文字関連(JSとCSS)を削除
remove_action('wp_head', 'print_emoji_detection_script', 7);
remove_action('wp_print_styles', 'print_emoji_styles');
// admin 側も削除
remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
remove_action( 'admin_print_styles', 'print_emoji_styles' );
// CDN 上の SVG 絵文字画像(Twemoji)の読み込みを削除
add_filter( 'emoji_svg_url', '__return_false' );
必要に応じて以下も functions.php に追加できます。
参考ページ
WordPress セキュリティ関連ページ
WordPress 公式ドキュメント(サポート)のセキュリティ関連ページは以下で確認できます。