2026年5月20日

2026年5月20日

WordPressの定期的なセキュリティスキャンを設定する方法

はじめに

セキュリティスキャンを定期的に実行することで、マルウェア感染・改ざん・脆弱なプラグインを早期発見できます。問題が発生してから対処するより、定期スキャンで予防的に検出する体制を整えることが重要です。

症状・原因

  • サイトがいつの間にか改ざんされていた
  • マルウェアに感染しているかどうか確認したい
  • セキュリティ状態を定期的に把握したいがツールの設定がわからない
  • 脆弱なプラグインが放置されて攻撃を受けた

解決手順

ステップ1:現在のセキュリティ状態を確認する

# WordPress コアファイルの整合性チェック
wp core verify-checksums

# プラグインの整合性チェック
wp plugin verify-checksums --all

# 既知の脆弱性があるプラグインを確認
wp plugin list --fields=name,version,status

# 更新が必要なプラグインを一覧表示
wp plugin list --update=available --format=table

# コアの更新確認
wp core check-update

# 管理者アカウントを確認(不審なアカウントがないか)
wp user list --role=administrator --fields=ID,user_login,user_email,user_registered

ステップ2:Wordfence でスキャンを設定する

# Wordfence をインストール・有効化
wp plugin install wordfence --activate

# Wordfence の自動スキャンを有効化(ダッシュボードから設定)
# Wordfence → Scan → Scheduling → Enable Scheduled Scans
// functions.php: Wordfence スキャン完了時に通知を強化
add_action('wordfence_security_issue', function(array $issue): void {
    $message = "Wordfence セキュリティ問題を検出:\n\n";
    $message .= "種別: " . ($issue['type'] ?? '不明') . "\n";
    $message .= "詳細: " . ($issue['shortMsg'] ?? '') . "\n";
    $message .= "ファイル: " . ($issue['file'] ?? '') . "\n";
    $message .= "時刻: " . current_time('Y-m-d H:i:s') . "\n\n";
    $message .= "管理画面で確認: " . admin_url('admin.php?page=Wordfence') . "\n";

    wp_mail(
        get_option('admin_email'),
        '[緊急] Wordfence セキュリティアラート',
        $message
    );
});

ステップ3:WP-CLI で自動スキャンを実装する

#!/bin/bash
# /usr/local/bin/wp-security-scan.sh
# 毎日実行するセキュリティスキャンスクリプト

WP_PATH="/var/www/html"
LOG_FILE="/var/log/wp-security-scan.log"
ADMIN_EMAIL="admin@example.com"
DATE=$(date +%Y-%m-%d)

echo "=== WordPress セキュリティスキャン開始: ${DATE} ===" >> $LOG_FILE

# コアファイルの整合性チェック
echo "--- コアチェック ---" >> $LOG_FILE
wp --path=$WP_PATH core verify-checksums 2>&1 >> $LOG_FILE

# プラグインの整合性チェック
echo "--- プラグインチェック ---" >> $LOG_FILE
wp --path=$WP_PATH plugin verify-checksums --all 2>&1 >> $LOG_FILE

# 更新が必要なプラグインを記録
echo "--- 未更新プラグイン ---" >> $LOG_FILE
OUTDATED=$(wp --path=$WP_PATH plugin list --update=available --format=csv 2>&1)
echo "$OUTDATED" >> $LOG_FILE

# PHP ファイルの改ざん検出(uploads ディレクトリ内)
echo "--- アップロードディレクトリの PHP ファイル ---" >> $LOG_FILE
find ${WP_PATH}/wp-content/uploads -name "*.php" 2>/dev/null >> $LOG_FILE

# 最近変更されたファイルを確認(24時間以内)
echo "--- 最近変更されたコアファイル ---" >> $LOG_FILE
find ${WP_PATH} -name "*.php" -newer ${WP_PATH}/wp-config.php \
    -not -path "*/wp-content/*" 2>/dev/null | head -20 >> $LOG_FILE

# 問題があればメール通知
if grep -q "Error\|Warning\|error\|failed" $LOG_FILE; then
    mail -s "[警告] WordPress セキュリティスキャン結果: ${DATE}" \
        $ADMIN_EMAIL < $LOG_FILE
fi

echo "=== スキャン完了 ===" >> $LOG_FILE

ステップ4:WordPress cron でスキャンを自動化する

// functions.php: 毎日セキュリティチェックを実行

// ① スケジュール登録
add_action('wp', function(): void {
    if (!wp_next_scheduled('daily_security_check')) {
        wp_schedule_event(
            strtotime('today 03:00:00'),
            'daily',
            'daily_security_check'
        );
    }
});

// ② スキャン実行
add_action('daily_security_check', function(): void {
    $issues = [];
    $admin_email = get_option('admin_email');

    // 管理者権限のユーザー数確認(急増していないか)
    $admins = get_users(['role' => 'administrator']);
    if (count($admins) > 3) {
        $issues[] = '管理者アカウントが' . count($admins) . '件あります。不審なアカウントを確認してください。';
    }

    // 最近インストールされたプラグイン(7日以内)
    $plugins = get_plugins();
    foreach ($plugins as $plugin_file => $plugin_data) {
        $plugin_path = WP_PLUGIN_DIR . '/' . $plugin_file;
        if (file_exists($plugin_path)) {
            $mtime = filemtime($plugin_path);
            if ((time() - $mtime) < 7 * DAY_IN_SECONDS) {
                $issues[] = '最近変更されたプラグイン: ' . $plugin_data['Name'];
            }
        }
    }

    // WordPress のバージョンが最新か確認
    $core_updates = get_core_updates();
    if (!empty($core_updates) && $core_updates[0]->response === 'upgrade') {
        $issues[] = 'WordPress コアのアップデートが利用可能です: ' . $core_updates[0]->version;
    }

    // 問題があればメール通知
    if (!empty($issues)) {
        $body = "定期セキュリティチェックで以下の問題を検出しました:\n\n";
        foreach ($issues as $issue) {
            $body .= "・{$issue}\n";
        }
        $body .= "\n確認先: " . admin_url() . "\n";
        $body .= "確認日時: " . current_time('Y-m-d H:i:s');

        wp_mail($admin_email, '[WordPress] 定期セキュリティチェック結果', $body);
    }
});

ステップ5:スキャン結果をダッシュボードウィジェットで確認する

// functions.php: セキュリティ状態をダッシュボードに表示

add_action('wp_dashboard_setup', function(): void {
    wp_add_dashboard_widget(
        'security_status_widget',
        'セキュリティ状態',
        'render_security_status_widget'
    );
});

function render_security_status_widget(): void {
    $status = [];

    // SSL確認
    $ssl = is_ssl() || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https');
    $status[] = ['name' => 'SSL/HTTPS', 'ok' => $ssl];

    // ファイル編集の無効化
    $status[] = ['name' => 'ファイル編集無効', 'ok' => defined('DISALLOW_FILE_EDIT') && DISALLOW_FILE_EDIT];

    // デバッグモード
    $status[] = ['name' => 'デバッグOFF', 'ok' => !WP_DEBUG];

    // 管理者ユーザー名が"admin"でないか
    $admin_user = get_user_by('login', 'admin');
    $status[] = ['name' => '"admin"ユーザーなし', 'ok' => !$admin_user];

    echo '<table style="width:100%">';
    foreach ($status as $item) {
        $icon = $item['ok'] ? '✅' : '❌';
        $color = $item['ok'] ? '#00a32a' : '#d63638';
        echo '<tr>';
        echo '<td>' . esc_html($item['name']) . '</td>';
        echo '<td style="color:' . $color . ';font-weight:bold">' . $icon . '</td>';
        echo '</tr>';
    }
    echo '</table>';
}

注意事項

  • Wordfenceなどのスキャンプラグインはサーバーリソースを消費します。スキャンはトラフィックの少ない深夜に実行するよう設定してください
  • wp core verify-checksumsはコアファイルの改ざんを検出しますが、wp-content内のカスタムコードはチェックしません。カスタムプラグイン・テーマには別途チェックが必要です
  • スキャン通知メールが届かない場合はSMTPプラグイン(WP Mail SMTP等)でメール設定を確認してください

まとめ

定期セキュリティスキャンは①wp core verify-checksumsでコアファイルの整合性確認、②Wordfenceの自動スキャンを深夜にスケジュール、③シェルスクリプトをcronで毎日実行してアップロードディレクトリの.phpファイルを検出、④WordPress cronのdaily_security_checkフックで管理者アカウント急増・未更新プラグインを監視、⑤ダッシュボードウィジェットでSSL・ファイル編集無効・デバッグOFFを一目確認します。

お気軽にご相談ください

お見積りへ お問い合わせへ