2026年8月25日

2026年8月25日

WordPressのXML-RPC攻撃を防ぐセキュリティ対策

はじめに

「WordPressサーバーへのリクエストが異常に多くサーバー負荷が高い」「アクセスログにxmlrpc.phpへの大量アクセスが記録されている」「WordPressへの不正ログイン試行をブロックしたい」——XML-RPC攻撃はWordPressサイトへの最も一般的な攻撃経路の一つです。

症状・原因

xmlrpc.phpはJetpackやモバイルアプリの接続に使用されますが、system.multicallメソッドを悪用すると1リクエストで数百回のログイン試行ができます。使用していない場合は完全に無効化することを推奨します。

解決手順

ステップ1:xmlrpc.phpをWordPressのフックで無効化する

// functions.php: XML-RPCを完全に無効化
// (Jetpack・WP モバイルアプリを使用していない場合)

// XML-RPCを無効化
add_filter( 'xmlrpc_enabled', '__return_false' );

// XML-RPCのメソッドリストを空にする(さらに強固)
add_filter( 'xmlrpc_methods', function(): array {
    return [];
} );

// ピンバック機能も無効化(DDoS増幅攻撃に悪用される)
add_filter( 'xmlrpc_methods', function( array $methods ): array {
    unset( $methods['pingback.ping'] );
    unset( $methods['pingback.extensions.getPingbacks'] );
    return $methods;
} );

// ピンバックを送受信とも無効化
add_action( 'pre_ping', function( array &$links ): void {
    $links = [];
} );
add_filter( 'pings_open', '__return_false' );

ステップ2:.htaccessでxmlrpc.phpへのアクセスをブロックする

# .htaccess: xmlrpc.php への全アクセスをブロック
# (Jetpack を使用している場合は Automattic の IP のみ許可)

# 方法1: 全ブロック(Jetpack未使用の場合)
<Files xmlrpc.php>
    Order Deny,Allow
    Deny from all
</Files>

# 方法2: 特定IPのみ許可(Jetpack使用の場合)
<Files xmlrpc.php>
    Order Deny,Allow
    Deny from all
    # Automattic / Jetpack のIPレンジ(要最新確認)
    Allow from 192.0.64.0/18
    Allow from 198.181.116.0/22
</Files>

# 方法3: mod_rewrite でブロック(RewriteRule使用)
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^xmlrpc\.php$ - [F,L]
</IfModule>

ステップ3:Nginxでブロックする

# /etc/nginx/conf.d/wordpress.conf または server ブロック内

server {
    # xmlrpc.php への全アクセスを403で拒否
    location = /xmlrpc.php {
        deny all;
        access_log off;
        log_not_found off;
        return 403;
    }

    # wp-login.php へのブルートフォース対策(レート制限)
    location = /wp-login.php {
        # 1分間に10リクエストまで
        limit_req zone=login burst=5 nodelay;
        include fastcgi_params;
        fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

# http コンテキストでレート制限ゾーンを定義
http {
    # IPアドレスごとに毎分10リクエストまで
    limit_req_zone $binary_remote_addr zone=login:10m rate=10r/m;
    limit_req_zone $binary_remote_addr zone=xmlrpc:10m rate=1r/m;
}

ステップ4:ログイン試行回数を制限する

// functions.php: ログイン失敗時にIPをロックアウト
// (Limit Login Attempts Reloaded プラグインの代替)

add_filter( 'authenticate', function( $user, string $username, string $password ) {
    if ( empty( $username ) && empty( $password ) ) {
        return $user;
    }

    $ip         = $_SERVER['REMOTE_ADDR'] ?? '';
    $cache_key  = 'login_attempts_' . md5( $ip );
    $attempts   = (int) get_transient( $cache_key );
    $max        = 5;   // 最大試行回数
    $lockout    = 15 * MINUTE_IN_SECONDS; // ロックアウト時間

    if ( $attempts >= $max ) {
        return new WP_Error(
            'too_many_retries',
            sprintf(
                'ログイン試行回数が多すぎます。%d分後に再試行してください。',
                ceil( $lockout / 60 )
            )
        );
    }

    return $user;
}, 20, 3 );

// ログイン失敗時に試行回数を記録
add_action( 'wp_login_failed', function( string $username ): void {
    $ip        = $_SERVER['REMOTE_ADDR'] ?? '';
    $cache_key = 'login_attempts_' . md5( $ip );
    $attempts  = (int) get_transient( $cache_key );

    set_transient( $cache_key, $attempts + 1, 15 * MINUTE_IN_SECONDS );

    // 不審なIPをログに記録
    if ( $attempts >= 3 ) {
        error_log( sprintf(
            'Suspicious login attempt: IP=%s Username=%s Attempts=%d',
            $ip,
            sanitize_user( $username ),
            $attempts + 1
        ) );
    }
} );

// ログイン成功時にリセット
add_action( 'wp_login', function( string $user_login ): void {
    $ip        = $_SERVER['REMOTE_ADDR'] ?? '';
    $cache_key = 'login_attempts_' . md5( $ip );
    delete_transient( $cache_key );
} );

ステップ5:攻撃IPをCloudflareやfail2banでブロックする

# fail2ban: WordPress ログイン失敗をブロック
# /etc/fail2ban/filter.d/wordpress.conf
# [Definition]
# failregex = ^<HOST> .* "POST /wp-login.php
#             ^<HOST> .* "POST /xmlrpc.php
# ignoreregex =

# /etc/fail2ban/jail.local
# [wordpress]
# enabled  = true
# port     = http,https
# filter   = wordpress
# logpath  = /var/log/nginx/access.log
# maxretry = 5
# bantime  = 3600
# findtime = 600

sudo systemctl restart fail2ban
sudo fail2ban-client status wordpress

# アクセスログからXML-RPC攻撃IPを確認
grep "xmlrpc.php" /var/log/nginx/access.log | \
    awk '{print $1}' | sort | uniq -c | sort -rn | head -20

# 特定IPを手動ブロック
sudo fail2ban-client set wordpress banip 203.0.113.0
// CloudFlare Workers または Cloudflare Rules で xmlrpc.php をブロック
// (Cloudflare ダッシュボード → セキュリティ → WAF → カスタムルール)
// 条件: URI パス = /xmlrpc.php
// アクション: ブロック

// WordPress で攻撃者IPをブラックリスト化
function block_malicious_ips(): void {
    $blocked_ips = get_option( 'blocked_ips', [] );
    $client_ip   = $_SERVER['REMOTE_ADDR'] ?? '';

    if ( in_array( $client_ip, $blocked_ips, true ) ) {
        status_header( 403 );
        exit( 'Access denied.' );
    }
}
add_action( 'init', 'block_malicious_ips', 1 );

注意事項

  • JetpackプラグインはXML-RPCを使用します。Jetpackを使用している場合は完全ブロックではなくAutomatticのIPのみ許可するホワイトリスト方式を採用してください。
  • wp_login_failedフックでのIPブロックは、同一NAT配下のユーザーをまとめてブロックする可能性があります。クラウドサービスや企業ネットワークのIPブロックには注意が必要です。
  • サーバーレベル(.htaccess/Nginx)でブロックするほうがWordPressのPHP実行前に処理されるため、サーバー負荷の観点で効果的です。

まとめ

XML-RPC攻撃対策は「xmlrpc_enabledフィルターで無効化→.htaccessで全アクセスブロック→Nginxのlimit_reqでwp-login.phpにレート制限→authenticateフックでIPロックアウト→fail2banとCloudflare WAFで多層防御」の流れで整備します。関連記事:WordPressにセキュリティヘッダーを設定する方法WordPressのwp-config.phpを安全に設定する方法

お気軽にご相談ください

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