タグアーカイブ サーバー負荷

admin-ajax.phpへのボット大量アクセスでCPUが100%になる時の遮断と対策

admin-ajax.phpへのボット大量アクセスでCPUが100%になる時の遮断と対策

admin-ajax.php へのボットの大量リクエストでサーバーの CPU 使用率が 100% に張り付く場合は、一括遮断ではなく攻撃対象の AJAX アクションをログから特定し、不要な処理を止めた上で正規アクセスを通すレート制限を導入するのが現実的な対策だ。

なぜボットは admin-ajax.php を集中的に叩くのか

admin-ajax.php は WordPress が全 AJAX リクエストを受け付ける入口にあたるファイルだ。フォーム送信、商品カートの更新、ハートビート API による自動保存など、フロントエンドと管理画面の両方で使われている。

ボットがここを狙う理由は、ログイン不要でアクセスできる公開エンドポイントでありながら、リクエストごとに WordPress 全体を読み込んで PHP を実行するため、少ないリクエスト数でもサーバー負荷を大きくできる点にある。

URL に action パラメータを付けて呼び出す構造になっており、たとえば問い合わせフォームの送信処理、EC サイトのカート更新、管理画面の自動保存など、あらゆる AJAX 処理がここを通る。ボットはこれを悪用し、処理の重い action を何度も叩いてサーバーを圧迫する。

攻撃対象の AJAX アクションをログから特定する

攻撃対象の AJAX アクションをログから特定する

対策の最初の一歩は、どの action が攻撃されているのかをアクセスログで確認することだ。闇雲に遮断ルールを増やしても、正規のフォームを巻き込むだけで根本解決にはならない。

アクセスログで action パラメータを調べる

Apache なら /var/log/apache2/access.log、nginx なら /var/log/nginx/access.log にリクエスト履歴が残っている。admin-ajax.php を含む行を抽出し、action= の後ろに続く値を集計すると、攻撃対象が見えてくる。

サーバー管理画面からログをダウンロードできる場合も多い。Cloudflare を利用しているなら、分析画面のセキュリティイベントから admin-ajax.php 宛てのリクエストを絞り込み、クエリ文字列を確認する手もある。

STEP 1 アクセスログで admin-ajax.php へのリクエストを抽出する
STEP 2 action パラメータの値を集計して攻撃対象を特定する
STEP 3 不要なアクションを WordPress 側で無効化する
STEP 4 正規アクセスを通すレート制限を導入する

攻撃対象を特定せずに全リクエストを遮断すると、問い合わせフォームが動かなくなる原因になる。必ずログ調査から始めるのが安全だ。

代表的な攻撃対象を把握しておく

攻撃対象になりやすいのは、外部から呼び出せる未ログインユーザー向けの AJAX アクションだ。問い合わせフォームの送信処理、ハートビート API の更新処理、EC サイトのカート更新などが代表例になる。自分のサイトに不要なプラグインが残した action がしばしば悪用される。

正規のフォームを壊さずにレート制限する

正規のフォームを壊さずにレート制限する

すべての admin-ajax.php リクエストにチャレンジ(CAPTCHA や JS チャレンジ)を適用すると、ブラウザ内の JavaScript が裏で送る AJAX リクエストまで巻き込まれ、フォームが正常に動作しなくなる。これが一括チャレンジ方式が失敗する理由だ。

有効なのは、攻撃対象の action だけを条件にしたレート制限だ。単位時間あたりのリクエスト数を IP アドレスごとに制限し、上限を超えたら一時的にブロックする。正規ユーザーのフォーム送信は頻度が低いため、まず引っかからない。

Before 全リクエストにチャレンジを適用
ブラウザ内の AJAX リクエストがチャレンジに引っかかり、問い合わせフォームが送信できなくなる
正規ユーザーまでブロックしてしまう
After 攻撃対象の action だけをレート制限
頻度の低い正規のフォーム送信は通過し、ボットだけが制限を超えてブロックされる
正規ユーザーへの影響を最小限に抑える
一括遮断の失敗例  レート制限による解決

レート制限の閾値は、通常のフォーム送信頻度を大きく上回る値に設定する。たとえば同一 IP から 1 分間に 30 回を超える admin-ajax.php へのリクエストを制限する場合、正規ユーザーが 30 回もフォームを送ることはない。

WordPress側で不要な AJAX アクションを無効化する

WordPress側で不要な AJAX アクションを無効化する

攻撃対象のアクションが自分のサイトで使われていないなら、WordPress のフックを使ってそのアクション自体を止めてしまうのが最も確実だ。リクエストが届いても処理が実行されず、サーバー負荷は大きく下がる。

未ログインユーザー向けのアクションを止める

wp_ajax_nopriv_ から始まるフックは未ログインの訪問者からの AJAX 処理を担当する。使っていない action を特定したら、テーマの functions.php に下のコードを追加して 403 を返すようにする。

add_action('admin_init', function() {
    if (defined('DOING_AJAX') && DOING_AJAX) {
        $action = isset($_REQUEST['action']) ? $_REQUEST['action'] : '';
        if (in_array($action, array('unused_action_1', 'unused_action_2'), true)) {
            wp_die('403 Forbidden', 'Forbidden', array('response' => 403));
        }
    }
}, 1);

action 名はログで特定した値に置き換える。これで、その action へのリクエストは WordPress の初期化直後に拒否され、プラグインのコードが呼ばれる前に処理が終わる。

ハートビート API の負荷を下げる

管理画面や投稿編集画面で自動保存・ロック通知のために heartbeat が一定間隔で動く。編集画面を開きっぱなしにするボットがいると負荷がかさむ。管理画面を普段使わないサイトなら、heartbeat の頻度を下げるか停止する手もある。

add_filter('heartbeat_settings', function($settings) {
    $settings['interval'] = 300;
    return $settings;
});

interval の単位は秒だ。300 にすると編集画面の自動保存間隔が 5 分になり、サーバーへの負荷が大きく減る。完全に止めるのは編集画面のロック機能に影響するため、まず間隔を延ばすところから始めるとよい。

サーバー設定でボットを直接遮断する

サーバー設定でボットを直接遮断する

Cloudflare やサーバーレベルの設定でも、条件付きの遮断やレート制限を入れられる。正規のフォームを止めずにボットだけを弾くには、リファラー情報やリクエスト頻度を条件に使うのがポイントだ。

リファラーを条件に弾く

正規の AJAX リクエストは、自分のサイトのページから送られるため Referer ヘッダーに自ドメインが入る。一方、ボットは Referer なしや偽装した値で送ることが多い。Apache なら .htaccess で Referer を条件に遮断できる。

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_URI} /wp-admin/admin-ajax.php
RewriteCond %{HTTP_REFERER} !^https?://example.com/ [NC]
RewriteRule ^ - [F,L]
</IfModule>

これは自ドメイン以外からの Referer を持つ admin-ajax.php リクエストを 403 で拒否する設定だ。example.com の部分は自分のドメインに置き換える。ただし一部のブラウザ設定で Referer が送られないケースもあるため、完全な遮断には向かない。あくまで補助的な対策として使う。

nginx でレート制限する

nginx を使っているなら limit_req ディレクティブで admin-ajax.php 専用のレート制限を定義できる。サーバーブロックに以下の設定を追加する。

limit_req_zone $binary_remote_addr zone=ajax:10m rate=1r/s;

location = /wp-admin/admin-ajax.php {
    limit_req zone=ajax burst=20 nodelay;
    limit_req_status 429;
    fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
    include fastcgi_params;
}

この設定では、同一 IP から 1 秒あたり 1 リクエストを超えると、バースト(突発的な超過)が 20 回まで許容される。超過分は 429(Too Many Requests)で拒否される。正規ユーザーのフォーム送信はこの制限内に収まるが、ボットの連打はすぐに上限を超える。

よくある質問

すべての admin-ajax.php リクエストを遮断してもよいか

やめておいたほうがよい。問い合わせフォーム、カート更新、検索機能など、多くのサイトで admin-ajax.php が使われている。全遮断すると重要な機能が動作しなくなる。

Cloudflare の Bot Fight Mode だけでは不十分なのか

Bot Fight Mode は既知のボットを自動判定して弾く基本的な防御で、未知のボットや単純なスクリプトには効果が薄い。特定のエンドポイントを狙う攻撃には、レート制限やアクション単位の対策を組み合わせる必要がある。

アクセスログの場所がわからない場合はどうするか

レンタルサーバーの管理画面にアクセスログの閲覧機能があることが多い。わからなければサーバー会社のサポートに問い合わせるか、Cloudflare の分析画面でリクエスト履歴を確認するとよい。

ボット対策プラグインだけでも解決できるか

Wordfence や Limit Login Attempts Reloaded のようなセキュリティプラグインは総合的な保護を提供するが、admin-ajax.php への大量アクセスには専用のレート制限やサーバー設定のほうが効果的な場合が多い。プラグインとサーバー設定の併用が望ましい。

この記事のポイント

  • admin-ajax.php は公開エンドポイントで、ボットが少ないリクエスト数でも負荷をかける
  • まずアクセスログで action パラメータを集計し、攻撃対象を特定する
  • 一括チャレンジは正規フォームを壊すため、action 単位のレート制限を使う
  • 使っていない AJAX アクションは functions.php から 403 で拒否する
  • サーバー設定(.htaccess や nginx)でも Referer 条件や limit_req で守れる