2026年10月26日

2026年10月26日

WordPressのパスワードポリシーを強化する方法

はじめに

「password123」や「wordpress」のような簡単なパスワードは数秒で解読されます。WordPressには標準でパスワード強度チェックがありますが、強力なパスワードを「強制」する機能はありません。独自のパスワードポリシーを実装することで、組織全体のセキュリティレベルを底上げできます。

症状・原因

  • ユーザーが簡単なパスワードを設定して不正ログインされた
  • パスワードを何年も変更していないユーザーがいる
  • 過去に使ったパスワードを再設定している
  • 漏洩したパスワードデータベースに載っているパスワードが使われている

解決手順

ステップ1:現在のパスワード状態を確認する

# パスワードが変更されていないユーザーを確認
wp user list --fields=ID,user_login,user_email,user_registered \
    --format=table

# 管理者アカウントのパスワード最終変更日を確認
wp eval "
\$users = get_users(['role' => 'administrator']);
foreach (\$users as \$user) {
    \$last = get_user_meta(\$user->ID, 'password_last_changed', true);
    \$days = \$last ? round((time() - \$last) / DAY_IN_SECONDS) : '未記録';
    echo \$user->user_login . ': ' . \$days . ' 日前' . PHP_EOL;
}
"

# よく使われるパスワードのリスト(確認用)
# https://haveibeenpwned.com/Passwords でチェック可能

ステップ2:パスワード強度要件を実装する

// functions.php: パスワードポリシーの強制

/**
 * パスワードが強度要件を満たすかチェック
 */
function validate_password_strength(string $password): array {
    $errors = [];

    if (strlen($password) < 12) {
        $errors[] = 'パスワードは12文字以上にしてください。';
    }
    if (!preg_match('/[A-Z]/', $password)) {
        $errors[] = '大文字を1文字以上含めてください。';
    }
    if (!preg_match('/[a-z]/', $password)) {
        $errors[] = '小文字を1文字以上含めてください。';
    }
    if (!preg_match('/[0-9]/', $password)) {
        $errors[] = '数字を1文字以上含めてください。';
    }
    if (!preg_match('/[!@#$%^&*()\-_=+\[\]{}|;:,.<>?\/]/', $password)) {
        $errors[] = '記号(!@#$ など)を1文字以上含めてください。';
    }

    return $errors;
}

// ユーザープロフィール更新時にパスワードポリシーを検証
add_action('user_profile_update_errors', function(\WP_Error $errors, bool $update, \WP_User $user): void {
    if (empty($_POST['pass1'])) return;

    $password = $_POST['pass1'];
    $strength_errors = validate_password_strength($password);

    foreach ($strength_errors as $error) {
        $errors->add('weak_password', $error);
    }
}, 10, 3);

// 新規ユーザー登録時にも適用
add_filter('registration_errors', function(\WP_Error $errors, string $sanitized_user_login, string $user_email): \WP_Error {
    if (empty($_POST['user_pass'])) return $errors;

    $strength_errors = validate_password_strength($_POST['user_pass']);
    foreach ($strength_errors as $error) {
        $errors->add('weak_password', $error);
    }
    return $errors;
}, 10, 3);

ステップ3:パスワード有効期限を設定する

// functions.php: パスワード有効期限の管理

// パスワード変更時に最終変更日を記録
add_action('password_reset', function(\WP_User $user): void {
    update_user_meta($user->ID, 'password_last_changed', time());
}, 10, 1);

add_action('profile_update', function(int $user_id, \WP_User $old_user_data): void {
    if (empty($_POST['pass1'])) return;
    update_user_meta($user_id, 'password_last_changed', time());
}, 10, 2);

// ログイン時に有効期限をチェック
add_action('wp_login', function(string $user_login, \WP_User $user): void {
    // 管理者・編集者のみ有効期限を適用
    $target_roles = ['administrator', 'editor'];
    if (empty(array_intersect((array) $user->roles, $target_roles))) return;

    $last_changed = (int) get_user_meta($user->ID, 'password_last_changed', true);

    // 記録がない場合は現在時刻を記録して通知
    if (!$last_changed) {
        update_user_meta($user->ID, 'password_last_changed', time());
        return;
    }

    $days_since_change = (time() - $last_changed) / DAY_IN_SECONDS;
    $expiry_days = 90;

    if ($days_since_change > $expiry_days) {
        // パスワード有効期限切れ → 変更を強制
        update_user_meta($user->ID, 'password_expired', true);
        wp_redirect(admin_url('profile.php?password_expired=1'));
        exit;
    }

    // 期限切れ7日前に警告
    if ($days_since_change > ($expiry_days - 7)) {
        $remaining = $expiry_days - (int) $days_since_change;
        set_transient('password_expiry_warning_' . $user->ID, $remaining, DAY_IN_SECONDS);
    }
}, 10, 2);

// 有効期限切れ警告を管理画面に表示
add_action('admin_notices', function(): void {
    $user_id = get_current_user_id();
    $remaining = get_transient('password_expiry_warning_' . $user_id);
    if ($remaining === false) return;

    echo '<div class="notice notice-warning is-dismissible">';
    echo "<p><strong>パスワードの有効期限まで{$remaining}日です。</strong>";
    echo ' <a href="' . admin_url('profile.php#password') . '">今すぐ変更</a></p>';
    echo '</div>';
});

ステップ4:漏洩パスワードチェック(Have I Been Pwned)

// functions.php: Have I Been Pwned API でパスワード漏洩を確認

function is_password_pwned(string $password): bool {
    // SHA-1 ハッシュの先頭5文字で k-Anonymity 検索
    $sha1 = strtoupper(sha1($password));
    $prefix = substr($sha1, 0, 5);
    $suffix = substr($sha1, 5);

    $response = wp_remote_get(
        "https://api.pwnedpasswords.com/range/{$prefix}",
        [
            'timeout' => 5,
            'headers' => ['Add-Padding' => 'true'],
        ]
    );

    if (is_wp_error($response)) return false;

    $body = wp_remote_retrieve_body($response);
    $hashes = explode("\r\n", $body);

    foreach ($hashes as $hash_line) {
        [$hash_suffix, $count] = array_pad(explode(':', $hash_line), 2, 0);
        if (strtoupper(trim($hash_suffix)) === $suffix) {
            return (int) $count > 0;
        }
    }

    return false;
}

// パスワード設定時に漏洩チェックを実行
add_action('user_profile_update_errors', function(\WP_Error $errors, bool $update, \WP_User $user): void {
    if (empty($_POST['pass1'])) return;
    if (!empty($errors->errors)) return; // 既にエラーがある場合はスキップ

    if (is_password_pwned($_POST['pass1'])) {
        $errors->add(
            'pwned_password',
            'このパスワードは過去のデータ漏洩で確認されています。別のパスワードを選択してください。'
        );
    }
}, 20, 3);

ステップ5:パスワードポリシー違反ユーザーを一括通知する

# WP-CLI で全ユーザーのパスワード最終変更日を確認
wp eval "
\$users = get_users();
\$now = time();
foreach (\$users as \$user) {
    \$last = get_user_meta(\$user->ID, 'password_last_changed', true);
    if (!empty(\$last) && (\$now - \$last) > 90 * DAY_IN_SECONDS) {
        echo \$user->user_login . ' (' . \$user->user_email . '): パスワード期限切れ' . PHP_EOL;
    }
}
"

注意事項

  • Have I Been Pwned APIは外部サービスです。パスワード全体ではなくSHA-1ハッシュの先頭5文字のみを送信する「k-Anonymity」方式なので、パスワード自体は外部に漏れません
  • パスワード有効期限を短くしすぎると、ユーザーが末尾の数字を変えるだけの「有効期限回避パスワード」を使うようになります。90日程度が現実的です
  • WordPress管理画面のパスワード変更ページ(プロフィールページ)のURLを記載したパスワード変更案内メールを送ると、ユーザーが対応しやすくなります

まとめ

パスワードポリシーの強化は①user_profile_update_errorsフックで12文字以上・大小文字・数字・記号を強制、②password_reset・profile_updateフックでpassword_last_changedメタを記録、③ログイン時に90日以上経過した管理者・編集者を強制変更ページにリダイレクト、④Have I Been Pwned APIのk-Anonymity方式でSHA-1先頭5文字を送信して漏洩パスワードをブロック、⑤WP-CLIで期限切れユーザーを抽出して一括通知します。

お気軽にご相談ください

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