タグアーカイブ WP Activity Log

WP Activity Log v5.6.4でアプリパスワード削除時に500エラーが出る原因と回避策

WP Activity Log v5.6.4でアプリパスワード削除時に500エラーが出る原因と回避策

WP Activity Log v5.6.4を有効化したWordPressサイトで、プロフィール画面からアプリケーションパスワードを取り消すと「このサイトで重大なエラーが発生しました」と表示される場合、プラグインのアップデート待ちでは解決しない。原因はWP Activity Logが汎用のupdate_user_metaフックに対するメタキーの検証を怠っている点にあり、PHP 8ではcount()に文字列を渡すとTypeErrorが発生して致命的エラーになる。この記事では、エラーの仕組みから、手動での回避策、上位互換のためのコードパッチまでを具体的に示す。

エラーの全容と発火する条件

エラーの全容と発火する条件

WP Activity Log v5.6.4に収録されたWP_User_Profile_Sensor::event_application_password_added()は、汎用のupdate_user_metaアクションにフックされている。関数シグネチャには$meta_keyが渡されているが、コールバック内でこれを一度も検証しない。代わりにHTTPリファラとREQUEST_URIだけを見て、アプリケーションパスワード変更のリクエストかどうかを判定している。

このため、ユーザーのプロフィール画面からアプリケーションパスワードを取り消すときに、REST APIへのDELETEリクエストが発行される。リファラチェックはプロフィールページを指し、URIチェックも/wp/v2/users/{id}/application-passwords/...を含むため、両方の条件を通過する。ここでBuddyBoss Appのようなプラグインが、同じリクエストのディスパッチ中にlast_activityというユーザーメタを更新すると、本来アプリケーションパスワードのメタを想定していたセンサーが誤ってそのtimestamp文字列を処理してしまう。

致命的エラーの発生箇所とスタックトレース

致命的エラーの発生箇所とスタックトレース

実運用のログには、次のような未捕捉のTypeErrorが記録される。PHP 8ではcount()の引数が配列またはCountableでない場合、警告ではなくTypeErrorがスローされる。WP Activity Logのコードは文字列をcount()に渡しているため、リクエストが500エラーになる。

エラーの発火経路
1. プロフィール画面で操作 アプリパスワードの取り消しをクリックする
2. REST APIリクエスト発行 DELETE /wp/v2/users/{id}/application-passwords/{uuid}
3. 別プラグインがメタを更新 BuddyBoss App が last_activity のtimestamp文字列を書き込む
4. センサーが誤発火 アプリパスワードと無関係なメタ書き込みでもコールバックが動く
5. count()が文字列を受けてTypeError 500エラーになり、操作が完了しない
PHP 8のTypeErrorを引き起こす誤発火  無関係なメタ更新

スタックトレースを追うと、クラスの203行目でcount( $_meta_value )が呼ばれている。ここで$_meta_valueはtimestampの文字列であり、$old_value_application_passwordsの配列である。文字列をcount()に渡すとPHP 8ではTypeErrorが発生し、アプリケーションパスワードの取り消しは実行されないまま処理が失敗する。

第一の対処方法、プラグインの停止で被害を止める

第一の対処方法、プラグインの停止で被害を止める

まず、サイトが利用者から見て壊れている状態を早く解消するには、WP Activity Logを停止する。管理画面にアクセスできる場合は「プラグイン」画面からWP Activity Logを無効化する。アクセスできない場合はFTPでwp-security-audit-logディレクトリをリネームする。リネームするとWordPressがプラグインを検出できなくなり、自動的に無効化される。

第二の対処方法、一時的にPHPのエラー表示を止める

第二の対処方法、一時的にPHPのエラー表示を止める

WP Activity Logを使い続けたい場合は、PHPのcount()エラーが致命的にならないよう回避策を施す。だが、これは根本解決ではなく症状を隠すだけだ。むしろ、エラー自体を修正するパッチを適用する方が安全である。ただし、プラグインのアップデートで上書きされることを理解した上で、運用上の措置として行うかどうかを判断してよい。

ソースコードを直接修正する根本対応

ソースコードを直接修正する根本対応

WP Activity Logの該当ファイルはwp-security-audit-log/classes/WPSensors/class-wp-user-profile-sensor.phpである。コールバックの先頭で、メタキーが_application_passwordsでない場合は早期returnするようガードを追加する。これが今回のエラーを確実に止める最小の修正だ。

public static function event_application_password_added( $meta_id, $user_id, $meta_key, $_meta_value ) {
    if ( '_application_passwords' !== $meta_key ) {
        return;
    }
    // 以下、既存の処理
}

加えて、防御的措置としてcount()に渡す前に配列キャストを行うと、将来何らかの形で配列以外の値が混入した場合にも致命的エラーを防げる。メタキーガードだけでも今回の症状は解消するが、運用中のサイトでは両方の修正を施しておく方が堅牢だ。

} elseif ( count( (array) $_meta_value ) < count( (array) $old_value ) ) {

修正後、コード上の同じパターンが他に残っていないか、deleted_user_metaadded_user_metaへの登録も確認するとよい。同じクラス内で複数のフックが同様のリファラ+URIチェックだけに頼っている場合、似た条件のリクエストで誤発火する可能性がある。

PHP 8環境で注意すべきcount()の挙動

PHP 7.xでは、count()に文字列を渡しても警告が出るだけで処理は継続されていた。しかしPHP 8ではTypeErrorとしてスローされるため、これまで表面化しなかったコードの前提ミスが致命的エラーとして現れる。WP Activity Logに限らず、WordPressプラグインの旧コードでは、更新順にこうした潜在バグが発覚することがある。

サイトをPHP 8系へ更新した直後に、特定の操作でのみ500エラーが出る場合は、エラーログにcount(): Argument #1 ($value) must be of type Countable|array, string givenのような記録が残っていないか確認する。該当する場合は、エラーを起こしているプラグインのコードが文字列をcount()に渡している可能性が高い。

PHP 7.x
  • count()に文字列を渡すと警告のみ
  • 処理は継続される
  • 潜在的バグが表面化しない
PHP 8.x
  • TypeErrorがスローされる
  • 致命的エラーで500応答
  • 操作が完了しない
緩やかなエラー  致命的エラー

よくある質問

WP Activity Logを無効化すると監査ログは消えますか?

無効化しても記録済みの監査ログはデータベースに残る。ただし、無効化している間に発生したイベントは記録されない。復旧後にプラグインを再度有効化すれば、既存のログを閲覧できる。

BuddyBoss Appが原因なのでBuddyBoss側を停止すれば直りますか?

BuddyBoss Appがユーザーメタを更新するタイミングが発火に重なっているが、根本的な誤動作はWP Activity Log側のフック設計にある。BuddyBoss Appが動いていなくても、別のプラグインが同様にRESTリクエスト中にユーザーメタを更新すれば同じエラーが起きる。

WP Activity Logのバージョンを下げれば回避できますか?

過去のバージョンが同じコードを含んでいる場合、単純なダウングレードでは解消しない。今回のクラスのコールバックを変更せずに添付された報告では、v5.6.5にも修正が含まれていないと指摘されている。信頼できる最新の修正が入るまでは、上記のコードパッチを適用するか、プラグインを停止する方が確実だ。

プロフィール画面でアプリパスワードを管理できる権限がないユーザーも影響を受けますか?

このエラーはアプリケーションパスワード管理を行えるユーザーに限らず、同じリクエスト経路でユーザーメタが更新される状況であれば発火しうる。ただ、発火条件としてプロフィール画面からの操作と、REST APIのURIにアプリケーションパスワードのパターンが含まれることが必要になる。

プラグインをアップデートしたら勝手に直りますか?

プラグインの開発者がメタキーガードを実装したバージョンをリリースすれば直る。ただし、現時点で修正が含まれているかはリリースノートとソースを確認する必要がある。修正が見つからないうちは、手動パッチか無効化で運用を守る。

この記事のポイント

  • WP Activity Log v5.6.4の500エラーはPHP 8のcount()エラーが原因
  • コールバックがメタキーを検証せず、アプリパスワードと無関係なユーザーメタで誤発火する
  • メタキーガードを追加するコードパッチが根本解決になる
  • 配列キャストを加えるとPHP 8の厳格なエラーに強いコードになる
  • 修正版が出るまでは、プラグイン停止かパッチ適用で被害を防ぐ
WordPress会員データが大量削除された時の原因と復旧手順

WordPress会員データが大量削除された時の原因と復旧手順

WordPress サイトで会員データが突然数千件単位で削除され、WP Activity Log に「Unknown user」として記録される事態は、データベースへの直接アクセスか管理者権限の乗っ取りによる攻撃の可能性が高い。即座にサイトをメンテナンスモードに切り替え、被害拡大を防ぎつつバックアップから復旧し、侵入経路を特定して再発を防ぐ必要がある。

なぜ突然会員データが大量削除されたのか

この事象の最大の特徴は、削除を実行したユーザーが「Unknown user」と表示される点にある。通常、WordPress の操作ログにはユーザー名かユーザー ID が記録されるが、ここに名前が出ないということは、wp_users テーブルを経由しない削除が行われていることを示している。考えられる原因は大きく3つに絞られる。

データベースへの直接操作

最も深刻なケースとして、攻撃者が SQL インジェクションや phpMyAdmin への不正アクセス、あるいはサーバーに仕込んだマルウェア経由でデータベースに直接 DELETE 文を実行している状況だ。この場合、WordPress のアクションやフィルターフックを一切通過しないため、WP Activity Log を含むどの監査プラグインもユーザーを特定できず「Unknown」となる。

管理者アカウントの乗っ取り

管理者権限を持つアカウントが乗っ取られ、攻撃者がそのアカウントで MemberPress の会員一括操作機能やデータベースクリーニング系プラグインを実行したケースだ。ただし、この場合は通常ユーザー名がログに残る。残っていないということは、攻撃者が操作後にユーザーごと削除したか、別の手段を使った可能性が高い。

REST API や XML-RPC の悪用

WordPress の REST API や XML-RPC に認証の弱点があると、外部から会員データの削除リクエストを送信される場合がある。特に、API 経由で直接データベース操作を行うカスタムエンドポイントがテーマやプラグインに存在すると、認証をすり抜けて大量削除が実行される危険がある。

大量削除が発生する3つの経路
A. データベース直接操作 SQL インジェクションやサーバー侵入により、DELETE 文を直接実行。WordPress のログを一切通過しないため「Unknown」になる。
B. 管理者アカウント乗っ取り 乗っ取った管理者で管理画面から一括削除。通常はユーザー名が残るが、アカウントごと削除された可能性もある。
C. REST API や XML-RPC の悪用 認証の脆弱性を突き、API 経由で削除リクエストを送信。プラグイン独自のエンドポイントに注意が必要。
最も深刻な経路  緊急調査が必要な経路

同時に多数のプラグイン更新が表示され、WordPress 7.0.1 への更新も同日にリリースされたという状況は、実際に重大な脆弱性が公表され、各開発者が一斉にパッチを配布している可能性を示唆している。更新が突如10件以上積み上がるのは、テーマやプラグインに含まれる共通ライブラリに脆弱性が見つかった場合によく見られるパターンだ。

攻撃を受けた直後にとるべき緊急対応の手順

攻撃を受けた直後にとるべき緊急対応の手順

被害の発覚から時間が経つほど、データ消失や情報流出の範囲は広がる。以下の手順を発見後30分以内に実行することで、二次被害を最小限に抑えられる。

STEP 1 サイトをメンテナンスモードに切り替え、外部からの全アクセスを遮断する
STEP 2 全プラグインとテーマを強制無効化し、攻撃者が仕込んだマルウェアの実行を止める
STEP 3 WP Activity Log をエクスポートし、攻撃元 IP と操作内容の全記録を保全する
STEP 4 直近の正常なバックアップからデータベースを復元する

メンテナンスモードでアクセスを遮断する

まず、攻撃が継続している可能性を想定し、サイト全体をメンテナンスモードに切り替える。管理画面にアクセスできる状態であれば、.maintenance ファイルを手動で作成する方法が最も確実だ。WordPress のルートディレクトリに .maintenance ファイルを配置し、以下の内容を記述すると、全訪問者にメンテナンス画面が表示される。

<?php
$upgrading = time();
?>

FTP やサーバーのファイルマネージャーでアクセスできない場合は、サーバー管理パネルから Web サーバー(Apache や Nginx)自体を停止するか、.htaccess に IP 制限をかけて特定の IP 以外をすべて拒否する方法も有効だ。

プラグインとテーマを強制的に無効化する

管理画面からプラグインを一括停止できない、あるいは管理画面自体が攻撃者にロックされている場合、FTP で /wp-content/plugins/ ディレクトリごとリネームする(例: plugins_backup)。これにより、WordPress は全プラグインを無効化した状態で起動する。同様に、/wp-content/themes/ から現在のテーマを除く全テーマを別ディレクトリに退避し、標準テーマ(Twenty Twenty-Five 等)のみを残す。

ログを保全して侵入経路を特定する

WP Activity Log のデータは攻撃者に消去される前にエクスポートする。CSV または JSON でダウンロードし、ローカルに保存する。このとき、サーバーのアクセスログ(access.log)とエラーログ(error.log)も必ず取得する。ログから得るべき情報は「攻撃元 IP」「削除が実行された正確な日時」「リクエスト URL(特に POST リクエスト)」「User-Agent」の4点だ。

「Unknown user」による削除操作が大量に記録されている場合、リクエスト URL が wp-admin/admin-ajax.phpwp-json/ を含んでいないかを重点的に確認する。これらが含まれていれば、AJAX や REST API 経由の攻撃である可能性が高い。

バックアップからの復旧とデータ整合性の確認

バックアップからの復旧とデータ整合性の確認

攻撃発生前の正常な状態がわかっているバックアップがあれば、それをリストアする。このケースのように2日前のバックアップが存在する場合、消失した会員データはその時点のものに戻るが、2日間に新規登録した会員や更新されたデータは失われるため、復旧後に差分を手動で補完する必要がある。

復旧の Before / After
Before(攻撃を受けた状態)
MemberPress 会員テーブルが数千件消失し「Unknown user」の削除ログのみが残っている。プラグイン更新が20件積み上がり、WP 7.0.1 が未適用。
After(復旧完了後)
バックアップから会員データをリストア。全プラグインと WP 本体を最新に更新し、不正ファイルを除去した状態でサイトを再公開。
攻撃直後の状態  復旧後の安全な状態

データベースをリストアする手順

バックアップが SQL ダンプファイル(.sql または .sql.gz)の場合、phpMyAdmin またはサーバーのコマンドラインからインポートする。WordPress のデータベース全体を置き換える場合は、まず現在の全テーブルを削除(DROP)してからインポートする。この操作は取り返しがつかないため、必ず現在のデータベースも別名でエクスポートしてから実行する。

# コマンドラインでのリストア例
mysql -u ユーザー名 -p データベース名 < backup.sql

リストア後、MemberPress の会員数が期待通りに戻っているか、会員のサブスクリプション状況や支払い履歴が正しく関連付けられているかを必ず確認する。特に、WooCommerce と連携している場合は wp_usermeta テーブルの整合性もチェックする必要がある。

再発を防ぐための恒久対策

再発を防ぐための恒久対策

復旧が完了したら、同じ攻撃を二度と受けないようにするための対策を即座に実施する。サーバー侵入を許した原因が残ったままだと、再度同じ経路から攻撃される危険が極めて高い。

全ファイルの改ざんチェックとマルウェアスキャン

WordPress のコアファイル、プラグイン、テーマのすべてについて、オリジナルの配布ファイルと比較して改ざんされていないかを確認する。WordPress の管理画面から「サイトヘルス」→「情報」→「ファイルの整合性」でコアファイルのチェックが可能だが、プラグインとテーマは手動かセキュリティプラグイン(Wordfence や Sucuri 等)を使ってスキャンする。特に、wp-content/uploads/ 内に .php ファイルが存在していないかを重点的に調べる。

管理者アカウントの総点検とパスワード再設定

すべての管理者アカウントについて、覚えのないユーザーが追加されていないか、権限が勝手に昇格されていないかを確認する。wp_users テーブルと wp_usermeta を直接確認し、不審な管理者を削除する。その上で、全管理者と編集者のパスワードを強制的にリセットし、可能であれば二要素認証(2FA)を導入する。WordPress 6.8 以降は標準でパスキー認証が利用できるため、セキュリティキーを使ったログインも有効な選択肢だ。

データベースのアクセス制限と監査の強化

データベースへの外部からの直接アクセスを遮断するため、phpMyAdmin などの管理ツールを公開ディレクトリに置かない、または IP 制限と HTTP 認証をかける。また、wp-config.php に以下の定数を追加して、WordPress のファイル編集機能を無効化しておくことで、管理画面を乗っ取られた場合でもテーマやプラグインのコードが書き換えられることを防げる。

define('DISALLOW_FILE_EDIT', true);
define('DISALLOW_FILE_MODS', true); // 必要な場合のみ

XML-RPC と REST API のアクセス制限

XML-RPC を使用していない場合は .htaccess でブロックするか、プラグインで完全に無効化する。REST API は多くのプラグインが依存しているため完全には無効化できないが、認証が必要なエンドポイントに対して適切な権限チェックが実装されているかを確認する。特に、MemberPress やカスタムプラグインが提供する REST API エンドポイントは、開発元のドキュメントでセキュリティ設定を再確認する。

よくある質問

WP Activity Log に「Unknown user」と表示されるのは必ずハッキングか

必ずしもそうとは限らないが、高い確率で不正アクセスが疑われる。プラグインのバグやデータベースの不整合でも発生しうるが、同時に大量のデータが削除されている場合は攻撃の可能性を第一に考えるべきだ。特に、管理者権限を持たない IP からの操作が記録されている場合はほぼ確実に侵入されている。

プラグインの更新が突然大量に表示されたのはなぜか

共通ライブラリやフレームワークに重大な脆弱性が見つかり、複数のプラグインが一斉にセキュリティアップデートをリリースした可能性が高い。WordPress 本体の緊急アップデートが同日にリリースされていることも、何らかの広範囲に影響する脆弱性が公表されたことを示唆している。これらの更新は速やかに適用すべきだ。

バックアップから復旧した後、消えたデータは完全に戻るのか

バックアップ取得時点のデータは戻るが、それ以降に追加・変更されたデータは復元されない。MemberPress の会員情報の場合、新規登録者やサブスクリプションの変更履歴が失われるため、復旧後に会員からの問い合わせをもとに手動で補完する必要がある。決済情報は決済代行サービス側に残っていることが多いため、そちらを参照して復元できる場合もある。

WP 7.0.1 への更新はすぐに適用すべきか

重大なセキュリティ修正を含むマイナーアップデートの場合、速やかな適用が推奨される。ただし、大量削除が発生した環境では、まずバックアップからの復旧と侵入経路の遮断を完了させてから更新を行う。更新前に全ファイルの改ざんチェックを済ませ、マルウェアが残っていない状態で適用するのが安全だ。

この記事のポイント

  • 「Unknown user」による大量削除はデータベース直接操作か管理者乗っ取りが原因の可能性が高い
  • 発見後は即座にメンテナンスモードでサイトを遮断し、プラグインを強制無効化して攻撃を止める
  • WP Activity Log とサーバーログを直ちにエクスポートし、攻撃元 IP と侵入経路を特定する
  • バックアップからデータベースをリストアし、復旧後に全ファイルの改ざんチェックとマルウェアスキャンを実施する
  • XML-RPC の無効化、REST API の権限確認、二要素認証の導入で再発を防ぐ