WordPressセキュリティ強化:ブルートフォース、SQLi、RCEを未然に防ぐ方法

Security tutorial - IT technology blog
Security tutorial - IT technology blog

クイックスタート:5分で基本的なセキュリティを固める

WordPressのログインページがまだ/wp-login.phpでレートリミットもない状態ですか?すでに危険にさらされています。新規インストールしたサイトが一晩で10,000件のログイン失敗を記録するケースを実際に目にしてきました。今すぐ止める方法を紹介します。

ステップ1:WorkfenceまたはSolid Securityをインストールする

どちらのプラグインも攻撃面の80%を即座にカバーできます。ほとんどの環境ではWordfence Freeがおすすめです。リアルタイムファイアウォール、ログインレートリミット、マルウェアスキャナーが標準搭載されており、有料プランは不要です。

wp-admin → プラグイン → 新規追加からWordfenceを検索してインストール・有効化し、セットアップウィザードで以下の3つを有効にしてください:

  • ブルートフォース保護(N回失敗でロックアウト)
  • 管理者アカウントへの二要素認証
  • 拡張保護モードのファイアウォール(WordPressより先に読み込まれるよう.htaccessまたはphp.iniへの1行追記が必要)

ステップ2:管理者ユーザー名を変更する

管理者アカウントのユーザー名がまだadminのままですか?攻撃者はすでに認証情報の半分を知っている状態です。修正は2分でできます。別の名前で新しい管理者ユーザーを作成し、そのアカウントでログインして、古いadminユーザーを削除してください。その際、投稿を新しいユーザーに再割り当てするよう求められます。

パスワードにはtoolcraft.app/ja/tools/security/password-generatorを使っています。完全にブラウザ上で動作し、ネットワーク経由のデータ送信もAPIコールもログ記録もありません。本番環境の認証情報を生成する際はこの点が重要です。

ステップ3:不要なXML-RPCを無効化する

XML-RPCはブルートフォース増幅攻撃に悪用されます。1回のHTTPリクエストで数百のユーザー名とパスワードの組み合わせを同時にテストできてしまいます。WordPressモバイルアプリやJetpackを使用していない場合は、無効化しましょう。

Apacheをご利用の場合、.htaccessに以下を追加してください:

<Files xmlrpc.php>
  Order Deny,Allow
  Deny from all
</Files>

Nginxをご利用の場合、サーバーブロックに以下を追加してください:

location = /xmlrpc.php {
    deny all;
    return 403;
}

詳細解説:SQLインジェクションとRCEの防御

WordPressにおけるSQLiの侵入経路

SQLiがWordPressコア自体を通じて発生することはほとんどありません。脆弱なプラグイン経由で入ってくるのが実態です。生のユーザー入力を文字列結合している$wpdb->query()呼び出しを持つプラグインが典型的な侵入口です。ログイン不要で、フォームフィールド、検索ボックス、RESTエンドポイントから直接攻撃が通ってしまいます。

Wordfenceのファイアウォールは受信リクエストを既知のパターン(UNION SELECTOR 1=1、スタッククエリ、エンコードされた変形など)と照合します。シグネチャベースなので完璧ではありませんが、公開直後から数時間以内にすべての公開サイトを探索する自動スキャナーは確実にブロックできます。

根本的な対策はプラグインを常に最新の状態に保つことです。VPSでは以下のコマンドを使って更新が遅れているものをチェックしています:

# 更新待ちのプラグインを確認する
wp plugin list --update=available --format=table --path=/var/www/html

# 毎晩午前3時にすべてを自動更新する
0 3 * * * /usr/local/bin/wp plugin update --all --path=/var/www/html --quiet

カスタマイズしたプラグイン以外はすべて自動更新しましょう。カスタマイズしたものについては、月次のカレンダーリマインダーで手動更新するだけで十分です。

RCEの攻撃経路をブロックする

WordPressへのRCE攻撃は主に3つの経路から行われます:

  1. ファイルアップロードの脆弱性 — プラグインが.phpを含む任意のファイル形式を受け入れてしまう
  2. テーマ/プラグインエディター — 管理者認証情報が漏洩した何者かがwp-admin内で直接PHPを編集する
  3. デシリアライズガジェット — まれだが特定のプラグインの組み合わせで壊滅的な被害をもたらす

ファイルエディターを無効化する。wp-config.phpに以下の2行を追加してください:

define( 'DISALLOW_FILE_EDIT', true );
define( 'DISALLOW_FILE_MODS', true ); // wp-adminからのプラグイン/テーマインストールもブロックする

アップロードディレクトリでのPHP実行をブロックする。Nginxの設定:

location ~* /(?:uploads|files)/.*\.php$ {
    deny all;
    return 403;
}

Apacheの場合:

<Directory "/var/www/html/wp-content/uploads">
  <Files "*.php">
    Order Deny,Allow
    Deny from all
  </Files>
</Directory>

wp-config.phpを堅牢化する

データベースの認証情報、シークレットキー、テーブルプレフィックス——これらはすべてwp-config.phpに含まれています。HTTPからアクセスできないよう、Webルートの1つ上のディレクトリに移動しましょう:

mv /var/www/html/wp-config.php /var/www/wp-config.php

# WordPressは自動的に1つ上のディレクトリを確認する — コード変更は不要
chmod 600 /var/www/wp-config.php
chown www-data:www-data /var/www/wp-config.php

また、ソルトの再生成も行いましょう。https://api.wordpress.org/secret-key/1.1/salt/にアクセスして出力をコピーし、wp-config.phpのAUTH_KEY/SECURE_AUTH_KEYブロックと置き換えてください。既存のすべてのセッションが即座に無効化され、ログイン中のユーザーは強制ログアウトされます。不正アクセスが疑われる後には、これが正しい動作です。

上級編:サーバーレベルの堅牢化

HTTPセキュリティヘッダー

パフォーマンスコストはゼロで、クリックジャッキング、MIMEスニッフィング、反射型XSSに対する実質的な防御が得られます。Nginxの設定に以下を追加してください:

add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.google-analytics.com; img-src 'self' data: https:; style-src 'self' 'unsafe-inline';" always;

デプロイ後はsecurityheaders.comでドメインをチェックしてください。B+以上を目指しましょう。それ以下の場合は、重要な設定が欠けているか誤設定があることを意味します。

NginxレベルでのレートリミットNginx

Wordfenceのブルートフォース保護はPHPレイヤーで発動します。つまり、リクエストを拒否するためだけにWordPressが起動し、データベースに接続し、CPUを消費することになります。このブロックをより早い段階で行いましょう:

http {
    limit_req_zone $binary_remote_addr zone=wp_login:10m rate=5r/m;

    server {
        location = /wp-login.php {
            limit_req zone=wp_login burst=3 nodelay;
            limit_req_status 429;
            # ... 残りの設定
        }
    }
}

IPあたり毎分5回のログイン試行、バースト3回まで許可。毎秒100リクエストを送りつけるスクリプトは、PHPが起動する前に429を返されます。共有ホスティングへの攻撃のほとんどは、これだけでノイズを95%以上削減できます。

データベース権限:最小権限の原則

多くのデフォルト設定ではWordPressのDBユーザーにALL PRIVILEGESを付与しています。WordPressが実際に必要なのは8つだけです:SELECTINSERTUPDATEDELETECREATEDROPALTERINDEX。それ以外はすべて剥奪しましょう:

REVOKE FILE, SUPER, PROCESS, RELOAD ON *.* FROM 'wp_user'@'localhost';

SHOW GRANTS FOR 'wp_user'@'localhost';

特に重要なのはFILEの剥奪です。これがあると、SQLiを突破した攻撃者がLOAD_FILE()でサーバーファイルを読み取ったり、INTO OUTFILEでPHPバックドアを設置したりできなくなります。

現場から学ぶ実践的なヒント

プラグイン数を監査する

プラグインはひとつひとつが攻撃面です。それだけです。40以上のアクティブなプラグインを持つWordPressサイトを引き継いだことがありますが、半分は放棄されており、2年間パッチが当たっていないCVEを持つものもありました。以下のコマンドを実行して、2022年以降に更新されていないものはすべて削除してください:

wp plugin list --path=/var/www/html --format=table | grep -v 'active'

ファイル整合性モニタリングを設定する

Wordfenceは変更されたファイルを検出しますが、OSレベルのベースラインチェックはより速く、プラグインの起動も不要です:

# uploadsディレクトリ以外のすべてのPHPファイルのスナップショットを作成する
find /var/www/html -name '*.php' -not -path '*/wp-content/uploads/*' \
  | sort | xargs md5sum > /root/wp_baseline.md5

# 週次cronでチェックする
md5sum -c /root/wp_baseline.md5 2>/dev/null | grep FAILED

出力にFAILEDが表示された場合、スナップショット以降にPHPファイルが変更されています。通常の更新だと決めつけず、まず調査してください。そうでない可能性があります。

堅牢化の前にバックアップを取る

wp-config.phpの移動、CSPヘッダーの有効化、XML-RPCの無効化は、存在を忘れていた連携機能を静かに壊してしまうことがあります。必ず先にスナップショットを取りましょう:

# WP-CLIでエクスポート
wp db export /root/wp_backup_$(date +%Y%m%d).sql --path=/var/www/html

# またはmysqldumpを使う場合
mysqldump -u root -p wordpress_db > /root/wp_backup_$(date +%Y%m%d).sql

ログインアクティビティを監視する

Wordfenceはダッシュボードで最近のログインを表示します。より詳細な可視性が必要な場合は、WP Activity Logプラグインとsyslogフォワーダーを組み合わせることで、検索可能な監査証跡が得られます。午前3時に何かが壊れたとき、推測ではなくタイムラインが必要です。

セキュリティは一度きりのチェックリストではありません。継続的なメンテナンスが必要です。プラグインレベルのブロック、Nginxレートリミット、データベース権限の制限、月次監査を組み合わせることで、24時間365日すべての公開WordPressサイトを探索する自動攻撃キャンペーンに対して、大きく優位に立てます。今日すぐ5分間の手順から始めましょう。今週中にサーバー設定を追加してください。残りは週末まで待って構いません。

Share: