【備忘録】プラグインなしで実装するWordPressセキュリティ対策まとめ

【備忘録】プラグインなしで実装するWordPressセキュリティ対策まとめ

目次

    プラグインなしで実装するWordPressセキュリティ対策

    WordPressは世界中で最も使われているCMS。そして人気があるからこそ攻撃のターゲットにもなりやすい。セキュリティ対策というとプラグインに頼りがちだけど、実はプラグインなしでもかなりの対策が可能。むしろプラグインを入れすぎるとそれ自体がセキュリティホールになることも。

    今回は、プラグインに頼らないWordPressセキュリティ対策をまとめておく。コード実装を含めた実践的な内容で、後で見返したときにすぐに使えるようにしておきたい。

    ここに記載する対策は重要度順に並べてあるので、上から順に実装していくといいかも。

    1. 管理者アカウントの保護

    まず最初に取り組むべきなのは、管理者アカウントの保護。ここが破られると全てが台無しになる。よって、最低限のマナーとも言える。

    デフォルトの「admin」ユーザー名を使わない

    WordPressをインストールした時点で「admin」というユーザー名で管理者アカウントを作成してしまうと、攻撃者にとって仕事が半分終わったようなもの。ブルートフォース攻撃(総当たり攻撃)を仕掛けるときに、ユーザー名が既知であればパスワードだけを推測すればいい。

    既に「admin」ユーザーを使っている場合は、新しい管理者アカウントを作成してから古いアカウントを削除しよう。

    強力なパスワードの使用

    これは言うまでもないけど、強力なパスワードを設定すること。最低でも12文字以上で、大文字・小文字・数字・特殊記号を含むものを使用する。パスワードマネージャを使って、サイトごとに異なるパスワードを管理するのがベスト。

    二要素認証の実装

    プラグインなしで二要素認証を実装するには、functions.phpに以下のようなコードを追加する。これはGoogleの認証システムを利用した実装方法だ。

    /**
     * 二要素認証の実装
     */
    function custom_2fa_login_check( $user, $password ) {
        // 正規ログイン処理を実行
        $check_user = wp_authenticate_username_password( null, $user, $password );
        
        if ( is_wp_error( $check_user ) ) {
            return $check_user;
        }
        
        // 二要素認証コードチェック
        if ( ! isset( $_POST['two_factor_code'] ) || empty( $_POST['two_factor_code'] ) ) {
            return new WP_Error( '2fa_required', __( '二要素認証コードを入力してください。' ) );
        }
        
        $secret_key = get_user_meta( $check_user->ID, 'two_factor_secret', true );
        $code = trim( $_POST['two_factor_code'] );
        
        // Google認証システムを使ってコードを検証
        require_once( ABSPATH . 'wp-content/2fa/GoogleAuthenticator.php' );
        $ga = new GoogleAuthenticator();
        
        if ( ! $ga->verifyCode( $secret_key, $code, 2 ) ) {
            return new WP_Error( '2fa_invalid', __( '無効な認証コードです。' ) );
        }
        
        return $check_user;
    }
    add_filter( 'authenticate', 'custom_2fa_login_check', 99, 3 );

    この実装にはGoogleAuthenticatorのPHPライブラリが必要なので、事前にダウンロードしておく必要がある点に注意。また、ログインフォームに二要素認証入力欄を追加するためのコードも必要になる。

    2. wp-config.phpのセキュリティ強化

    wp-config.phpはWordPressの心臓部とも言えるファイル。ここにはデータベース接続情報など重要な情報が含まれているため、しっかり保護する必要がある。

    wp-config.phpの移動

    標準的なインストールではwp-config.phpはWordPressのルートディレクトリに置かれるけど、これを一つ上の階層に移動することで、直接アクセスされるリスクを減らせる。WordPressは通常、この位置のファイルも認識してくれる。

    セキュリティキーの設定

    wp-config.phpにはセキュリティキーを設定する場所がある。これはクッキーの暗号化に使われるもので、定期的に変更するのが望ましい。

    以下のようなコードをwp-config.phpに追加しよう(すでに存在する場合は更新)。

    define( 'AUTH_KEY',         'ここにユニークな文字列を入れる' );
    define( 'SECURE_AUTH_KEY',  'ここにユニークな文字列を入れる' );
    define( 'LOGGED_IN_KEY',    'ここにユニークな文字列を入れる' );
    define( 'NONCE_KEY',        'ここにユニークな文字列を入れる' );
    define( 'AUTH_SALT',        'ここにユニークな文字列を入れる' );
    define( 'SECURE_AUTH_SALT', 'ここにユニークな文字列を入れる' );
    define( 'LOGGED_IN_SALT',   'ここにユニークな文字列を入れる' );
    define( 'NONCE_SALT',       'ここにユニークな文字列を入れる' );

    ユニークな文字列はWordPressのシークレットキー生成サービスで生成できる。

    ファイル編集の無効化

    管理画面からテーマやプラグインのファイルを直接編集できる機能は便利だけど、セキュリティリスクになる。これを無効化するには、wp-config.phpに以下を追加する。

    define( 'DISALLOW_FILE_EDIT', true );

    さらに徹底するなら、ファイルシステムを通じた更新も無効化できる。

    define( 'DISALLOW_FILE_MODS', true );

    ただし、これを設定するとプラグインやテーマの更新、新規インストールがすべてできなくなるので注意。WordPressの自動更新も効かなくなるので、サーバー上で手動更新する必要が出てくる。本番環境で使うのは慎重に検討しよう。

    3. .htaccessによるセキュリティ設定

    .htaccessファイルはApacheサーバーの設定ファイルで、これを使ってさまざまなセキュリティ対策ができる。(注:Nginxなど他のサーバーを使用している場合は、それぞれのサーバー設定を確認してほしい)

    wp-adminディレクトリの保護

    管理画面へのアクセスを特定のIPアドレスに制限するには、wp-adminディレクトリに新しい.htaccessファイルを作成し、以下のように記述する。

    # 特定IPからのみアクセスを許可
    <IfModule mod_rewrite.c>
    RewriteEngine on
    RewriteCond %{REMOTE_ADDR} !^123\.456\.789\.0$
    RewriteCond %{REQUEST_URI} ^/(wp-admin|wp-login\.php) [NC]
    RewriteRule .* - [F]
    </IfModule>

    「123.456.789.0」の部分を自分のIPアドレスに置き換える。固定IPを持っていない場合は使いづらい対策だけど、企業サイトなど管理者が限られている場合は有効。IPアドレスは複数指定することも可能。

    ディレクトリリスティングの無効化

    ディレクトリリスティングとは、インデックスファイルがない場合にディレクトリ内のファイル一覧が表示される機能。これを無効化するには、ルートディレクトリの.htaccessに以下を追加する。

    # ディレクトリリスティングを無効化
    Options -Indexes

    XMLRPCの無効化

    XMLRPC.phpはリモート投稿などに使われるけど、DDoS攻撃の踏み台にされることも。必要なければ無効化しておくのが無難。

    # XMLRPCへのアクセスをブロック
    <Files xmlrpc.php>
    order deny,allow
    deny from all
    </Files>

    リモート投稿機能を使っていなければ問題ないが、JetpackなどXMLRPCを使うプラグインを利用している場合は注意が必要。

    セキュリティヘッダーの追加

    さまざまなセキュリティヘッダーを設定して、クロスサイトスクリプティング(XSS)やクリックジャッキングなどの攻撃を防ぐ。

    # セキュリティヘッダーの設定
    <IfModule mod_headers.c>
      # XSS保護を有効化
      Header set X-XSS-Protection "1; mode=block"
      # MIMEタイプのスニッフィングを防止
      Header set X-Content-Type-Options "nosniff"
      # クリックジャッキング防止
      Header set X-Frame-Options "SAMEORIGIN"
      # HSTS(HTTP Strict Transport Security)の有効化
      Header set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
      # Referrer-Policyの設定
      Header set Referrer-Policy "strict-origin-when-cross-origin"
    </IfModule>

    これらのヘッダーは最新のブラウザでさまざまな保護機能を有効にする。特に重要なのはX-XSS-ProtectionとX-Frame-Options。リスクが少なく、導入しておいて損はない。

    4. データベースのセキュリティ強化

    データベースはサイトの中枢。セキュリティを強化するためのいくつかの対策を紹介する。

    テーブルプレフィックスの変更

    WordPressのデフォルトテーブルプレフィックスは「wp_」。これを変更することで、SQLインジェクション攻撃のリスクを下げられる。新規インストール時に設定するのが簡単だけど、既存サイトでも変更は可能。

    既存サイトでテーブルプレフィックスを変更するには:

    1. データベースのバックアップを取る
    2. wp-config.phpの$table_prefixの値を変更(例:$table_prefix = 'xyz_';
    3. データベース内のテーブル名を一つずつリネーム
    4. usermeta、optionsテーブル内のプレフィックス参照も更新

    小規模サイトならそれほど大変ではないけど、大規模サイトでは慎重に作業する必要がある。

    データベースのクリーンアップ

    定期的にデータベースをクリーンアップすると、不要なデータが削減されてセキュリティリスクも減る。functions.phpに以下のコードを追加しよう。

    /**
     * データベースクリーンアップの定期実行
     */
    function custom_security_cleanup_database() {
        global $wpdb;
        
        // リビジョンの削除(最新の5つは残す)
        $wpdb->query("
            DELETE FROM $wpdb->posts 
            WHERE post_type = 'revision' 
            AND ID NOT IN (
                SELECT * FROM (
                    SELECT ID FROM $wpdb->posts 
                    WHERE post_type = 'revision' 
                    ORDER BY post_date DESC 
                    LIMIT 5
                ) AS temp
            )
        ");
        
        // 自動下書きの削除
        $wpdb->query("DELETE FROM $wpdb->posts WHERE post_status = 'auto-draft'");
        
        // ゴミ箱を空にする
        $wpdb->query("DELETE FROM $wpdb->posts WHERE post_status = 'trash'");
        
        // 孤立したポストメタの削除
        $wpdb->query("
            DELETE FROM $wpdb->postmeta 
            WHERE post_id NOT IN (
                SELECT ID FROM $wpdb->posts
            )
        ");
        
        // 孤立したコメントメタの削除
        $wpdb->query("
            DELETE FROM $wpdb->commentmeta 
            WHERE comment_id NOT IN (
                SELECT comment_ID FROM $wpdb->comments
            )
        ");
        
        // スパムコメントの削除
        $wpdb->query("DELETE FROM $wpdb->comments WHERE comment_approved = 'spam'");
        
        // 失敗したログイン試行記録を削除
        delete_option('failed_login_attempts');
    }
    
    // 毎週日曜日の深夜3時に実行
    if ( ! wp_next_scheduled( 'custom_security_cleanup_database_hook' ) ) {
        wp_schedule_event( strtotime('next Sunday 3am'), 'weekly', 'custom_security_cleanup_database_hook' );
    }
    add_action( 'custom_security_cleanup_database_hook', 'custom_security_cleanup_database' );

    このスクリプトは毎週日曜の深夜に自動実行され、不要なデータをクリーンアップする。サイト規模によっては負荷が大きくなる可能性もあるので、実行時間はアクセスが少ない時間帯に設定しよう。

    5. ログイン試行の制限

    ブルートフォース攻撃からサイトを守るため、ログイン試行回数を制限するコードをfunctions.phpに追加する。

    /**
     * ログイン試行制限機能
     */
    function custom_limit_login_attempts() {
        $max_attempts = 5; // 最大試行回数
        $lockout_time = 30 * MINUTE_IN_SECONDS; // ロックアウト時間(分)
        $ip = $_SERVER['REMOTE_ADDR'];
        
        // 現在のIPのログイン試行記録を取得
        $attempts = get_option('failed_login_attempts', array());
        
        // このIPがロックされているか確認
        if ( isset( $attempts[$ip] ) && $attempts[$ip]['lockout'] > time() ) {
            // ロックアウト中
            return new WP_Error(
                'too_many_attempts',
                sprintf(
                    __('ログイン試行回数が多すぎます。%d分後に再試行してください。'),
                    ceil( ( $attempts[$ip]['lockout'] - time() ) / 60 )
                )
            );
        }
        
        return null;
    }
    
    // ログイン失敗時の処理
    function custom_failed_login( $username ) {
        $max_attempts = 5;
        $lockout_time = 30 * MINUTE_IN_SECONDS;
        $ip = $_SERVER['REMOTE_ADDR'];
        
        // 現在のIPのログイン試行記録を取得
        $attempts = get_option('failed_login_attempts', array());
        
        // このIPの記録を初期化
        if ( !isset( $attempts[$ip] ) || !is_array( $attempts[$ip] ) ) {
            $attempts[$ip] = array(
                'count' => 0,
                'lockout' => 0
            );
        }
        
        // 失敗回数を増やす
        $attempts[$ip]['count']++;
        
        // 最大試行回数を超えた場合はロックアウト
        if ( $attempts[$ip]['count'] >= $max_attempts ) {
            $attempts[$ip]['lockout'] = time() + $lockout_time;
            $attempts[$ip]['count'] = 0;
        }
        
        // 更新
        update_option('failed_login_attempts', $attempts);
    }
    
    // ログイン成功時の処理
    function custom_login_success( $username ) {
        $ip = $_SERVER['REMOTE_ADDR'];
        $attempts = get_option('failed_login_attempts', array());
        
        // このIPの記録をリセット
        if ( isset( $attempts[$ip] ) ) {
            unset( $attempts[$ip] );
            update_option('failed_login_attempts', $attempts);
        }
    }
    
    // フックに接続
    add_filter( 'authenticate', 'custom_limit_login_attempts', 30, 3 );
    add_action( 'wp_login_failed', 'custom_failed_login' );
    add_action( 'wp_login', 'custom_login_success' );

    このコードは5回連続でログインに失敗すると、30分間そのIPからのログイン試行をブロックする。数値は必要に応じて調整しよう。

    デメリットとしては、複数ユーザーが同じIP(社内ネットワークなど)から接続している場合、一人のミスで全員がロックアウトされる可能性がある点。その場合は$max_attemptsの値を増やすか、別の方法を検討しよう。

    6. ファイルパーミッションの最適化

    適切なファイルパーミッション設定は、不正アクセスから守る基本的な対策。以下の設定を目安に、FTPクライアントなどで変更する。

    • ディレクトリ: 755(drwxr-xr-x)
    • ファイル: 644(-rw-r–r–)
    • wp-config.php: 600(-rw——-)

    特にwp-config.phpは最も厳しい権限設定にして、所有者以外はアクセスできないようにするのが理想的。

    7. 自動更新の設定

    WordPress、プラグイン、テーマを最新の状態に保つことは、セキュリティ対策の基本。自動更新を設定するには、wp-config.phpに以下のコードを追加する。

    // 自動更新の設定
    define( 'WP_AUTO_UPDATE_CORE', 'minor' ); // マイナーバージョンのみ自動更新(推奨)
    // define( 'WP_AUTO_UPDATE_CORE', true ); // すべてのバージョンを自動更新
    // define( 'WP_AUTO_UPDATE_CORE', false ); // 自動更新を無効化

    functions.phpに以下を追加すると、プラグインとテーマも自動的に更新されるようになる。

    /**
     * プラグインとテーマの自動更新を有効化
     */
    // プラグインの自動更新を有効化
    add_filter( 'auto_update_plugin', '__return_true' );
    
    // テーマの自動更新を有効化
    add_filter( 'auto_update_theme', '__return_true' );

    大規模サイトや特殊なカスタマイズをしている場合は、自動更新によって問題が発生することもある。その場合は、重要なセキュリティアップデートだけを自動適用させる選択肢もある。

    8. バックアップの自動化

    セキュリティ対策の最後の砦はバックアップ。攻撃を受けた際に、復旧できる状態を維持しておくことが重要。プラグインなしで自動バックアップを設定するには、サーバーのcronジョブを使用する。

    まず、WordPressのルートディレクトリに以下のようなバックアップスクリプトを作成する。

    #!/bin/bash
    
    # バックアップの保存先ディレクトリ
    BACKUP_DIR="/path/to/backups"
    SITE_NAME="mysite"
    DATE=$(date +"%Y-%m-%d")
    
    # データベース設定(wp-config.phpから取得する場合は別途スクリプトが必要)
    DB_NAME="wordpress_db"
    DB_USER="db_user"
    DB_PASS="db_password"
    
    # ディレクトリが存在しない場合は作成
    mkdir -p $BACKUP_DIR
    
    # ファイルをバックアップ
    tar -czf $BACKUP_DIR/$SITE_NAME-files-$DATE.tar.gz /path/to/wordpress
    
    # データベースをバックアップ
    mysqldump -u $DB_USER -p$DB_PASS $DB_NAME | gzip > $BACKUP_DIR/$SITE_NAME-db-$DATE.sql.gz
    
    # 30日以上経過したバックアップを削除
    find $BACKUP_DIR -name "*.tar.gz" -o -name "*.sql.gz" -mtime +30 -delete

    このスクリプトをcrontabに登録して、定期的に実行されるようにする。

    # 毎日午前2時にバックアップを実行
    0 2 * * * /path/to/backup-script.sh

    サーバーへのSSHアクセスがない場合は実装が難しいため、この方法が使えないこともある。その場合はバックアップ専用のプラグインを検討するか、ホスティング会社が提供するバックアップサービスを利用しよう。

    9. セキュリティヘッダーをPHPで実装

    .htaccessが使えない環境(NginxサーバーやWindowsサーバーなど)では、PHPでセキュリティヘッダーを設定できる。functions.phpに以下のコードを追加しよう。

    /**
     * セキュリティヘッダーの設定
     */
    function custom_security_headers() {
        // XSS保護
        header('X-XSS-Protection: 1; mode=block');
        
        // MIMEタイプのスニッフィング防止
        header('X-Content-Type-Options: nosniff');
        
        // クリックジャッキング防止
        header('X-Frame-Options: SAMEORIGIN');
        
        // HSTS(HTTP Strict Transport Security)
        if (is_ssl()) {
            header('Strict-Transport-Security: max-age=31536000; includeSubDomains; preload');
        }
        
        // Referrer-Policy
        header('Referrer-Policy: strict-origin-when-cross-origin');
        
        // Content-Security-Policy(慎重に設定が必要)
        // header("Content-Security-Policy: default-src 'self' https: data: 'unsafe-inline' 'unsafe-eval'");
    }
    add_action('send_headers', 'custom_security_headers');

    Content-Security-Policyは強力だけど、設定が複雑でサイトの機能を壊す可能性があるので、コメントアウトしてある。導入する際は徐々に制限を強めていくといい。

    10. ブルートフォース攻撃を検知して通知

    サイトが攻撃を受けていることを早期に検知できれば、対策を講じるチャンスがある。以下のコードは連続ログイン失敗を検知して管理者にメール通知を送る。

    /**
     * ブルートフォース攻撃検知と通知
     */
    function detect_brute_force_attack($username) {
        $threshold = 10; // この回数以上の連続失敗でアラート
        $window = 10 * MINUTE_IN_SECONDS; // 判定する時間枠(秒)
        $ip = $_SERVER['REMOTE_ADDR'];
        
        // 失敗履歴を取得
        $failures = get_option('login_failure_history', array());
        
        // 現在時刻
        $now = time();
        
        // 時間枠外の古い記録を削除
        foreach ($failures as $key => $timestamp) {
            if ($timestamp < $now - $window) {
                unset($failures[$key]);
            }
        }
        
        // 現在の失敗を記録
        $failures[] = $now;
        update_option('login_failure_history', $failures);
        
        // 閾値を超えたら通知
        if (count($failures) >= $threshold) {
            // アラートメールを送信
            $to = get_option('admin_email');
            $subject = '[警告] ' . get_bloginfo('name') . ' でブルートフォース攻撃の可能性';
            $message = "過去{$window}秒間に{$threshold}回以上のログイン失敗が検出されました。\n\n";
            $message .= "最後の試行情報:\n";
            $message .= "IPアドレス: {$ip}\n";
            $message .= "ユーザー名: {$username}\n";
            $message .= "日時: " . date('Y-m-d H:i:s') . "\n";
            $message .= "\nWordPressダッシュボードを確認し、不審な活動がないか確認してください。";
            
            wp_mail($to, $subject, $message);
            
            // 記録をリセット(連続通知を防ぐため)
            update_option('login_failure_history', array());
        }
    }
    
    add_action('wp_login_failed', 'detect_brute_force_attack');

    このコードは10分間に10回以上のログイン失敗があった場合にメール通知を送る。数値は必要に応じて調整しよう。

    まとめ

    以上のようにプラグインなしでもかなり堅牢なセキュリティ対策が実装できる。基本的な対策をまとめると

    • 管理者アカウントの保護(強力なパスワード、二要素認証)
    • wp-config.phpのセキュリティ強化(移動、セキュリティキー、ファイル編集無効化)
    • .htaccessによるセキュリティ設定(ディレクトリ保護、セキュリティヘッダー)
    • データベースのセキュリティ(テーブルプレフィックス変更、クリーンアップ)
    • ログイン試行の制限とブルートフォース検知
    • ファイルパーミッションの最適化
    • 自動更新と定期的なバックアップ

    これらの対策を実装することで、プラグインに頼らずともセキュリティレベルを大幅に向上させることができる。もちろん、絶対的な安全はないので、常に最新の脅威に注意を払い、定期的に設定を見直すことも大切。

    最後に、これらの対策を実装する前に必ずバックアップを取ること。特に本番環境では、一つの変更が思わぬ障害を引き起こすこともある。安全に実装して、WordPressサイトを守っていこう。


    この記事はあくまで備忘録として作成したものです。実装の際は自己責任でお願いします。特にサーバー設定やコードの変更は、サイトに影響を与える可能性があります。不明点があれば専門家に相談することをお勧めします。