タグアーカイブ マルウェア駆除

functions.phpが自己修復するマルウェアに感染 WordPressの持続的再感染を完全に駆除する方法

functions.phpが自己修復するマルウェアに感染 WordPressの持続的再感染を完全に駆除する方法

functions.php に SC_TH_BEGIN 〜 SC_TH_END で囲まれた難読化コードが注入され、完全削除したはずなのに数週間後に自己修復して再出現する感染は、単一ファイルの削除では解決しない。この種のマルウェアは、複数の隠れた感染拠点から自己を再生し、クリーンアップの試みを検知して適応する高度な仕組みを持つ。根本的な駆除には、侵入経路の遮断と全ファイルの網羅的スキャンが欠かせない。

SC_TH_BEGIN型マルウェアの感染メカニズムと自己修復の仕組み

SC_TH_BEGIN型マルウェアの感染メカニズムと自己修復の仕組み

この手の持続的感染は、単純なワンライナーではなく、組織化されたキャンペーンの一環として設計されている。コードは functions.php の末尾に注入され、SC_TH_BEGIN と SC_TH_END という独自タグで囲まれる。内部にはバージョン番号とハッシュ値が含まれ、これが改ざん検知と自己修復のトリガーになる。

感染のライフサイクルは次の3段階で進行する。まず初期侵入時に、難読化された本体コードが mu-plugins ディレクトリに隠しファイルを書き込む。このファイルがバックドアとして機能し、定期的に functions.php の状態を監視する。次に、functions.php から感染コードが削除されると、mu-plugins の隠しファイルがハッシュ不一致を検知し、自身のロジックで functions.php を再感染させる。さらに、アップロードディレクトリには一見無害なアーカイブファイルが置かれ、これが外部からの指令受け取り口や、別の復旧ポイントとして機能する。

感染コードの隠蔽パターン
Before(感染状態)
/* SC_TH_BEGIN v2.1 a3f8c... */
$abc = base64_decode('UEhO...');
eval($abc);
/* SC_TH_END v2.1 a3f8c... */
After(クリーンアップ直後)
// functions.php のクリーンな終了
After(2週間後 再感染)
/* SC_TH_BEGIN v3.0 b7d2e... */
// this file previously had malicious content but it's been removed and is safe now
/* SC_TH_END v3.0 b7d2e... */
感染状態  クリーン状態
感染が持続する3つの隠れ場所
① mu-plugins の隠しファイル
ハッシュ監視と自己修復ロジックを実行する
② アップロードディレクトリのアーカイブ
外部からの指令受け取りや復旧に使われる
③ 改ざんされた正規のプラグイン/テーマファイル
最初の侵入経路として残り続けることが多い

3つの感染拠点のうち、最も見落とされやすいのは mu-plugins の隠しファイルだ。このディレクトリはプラグイン管理画面に表示されないため、手動での確認が必須になる。また、コメント行だけが残る偽装パターンは、マルウェアがクリーナーの動作を学習し「おとり」として置いている可能性が高い。

自己修復するマルウェアを根本から除去する駆除手順

自己修復するマルウェアを根本から除去する駆除手順

単に functions.php から感染コードを削除するだけでは、裏で動く監視機構が再び書き込んでしまう。以下の手順では、感染のサイクルを断ち切るために、すべての拠点を同時に無効化する。

全ファイルのバックアップと感染範囲の特定

サーバー全体のファイルをローカルにダウンロードし、安全な環境でスキャンする。この段階ではまだサーバー上のファイルには手を加えず、何がどこに潜んでいるかを把握することが目的だ。隠しファイルは先頭にドット(.)が付くものや、ランダムな文字列のファイル名になっていることが多い。

mu-plugins ディレクトリの全ファイルを精査する

wp-content/mu-plugins/ に存在するファイルのうち、自身で設置した覚えのないものはすべて疑う。特に、ファイル名が意味不明な文字列だったり、PHP ファイルでありながらプラグインヘッダーがないものはマルウェアの可能性が高い。正常な mu-plugin も一時的に退避させ、ディレクトリを空にしてから必要なものだけ戻す方法が確実だ。

アップロードディレクトリ内の不審なアーカイブとPHPファイルを削除する

wp-content/uploads/ 以下に .zip や .tar.gz などのアーカイブファイルが存在した場合、それが正規のバックアップやプラグイン由来でない限り削除する。PHPファイルも画像などに偽装されて存在することがあるため、拡張子に関係なくファイルの先頭数行を確認し、PHPタグが含まれていないか検査する。

テーマとプラグインを公式ソースと比較して復元する

改ざんの可能性があるテーマやプラグインは、公式リポジトリからダウンロードしたクリーンなファイルで上書きする。子テーマの functions.php だけが標的になっていたとしても、親テーマや他のプラグインに仕込まれたバックドアが感染を再開させることがあるため、疑わしい拡張機能はすべて置き換える。

STEP 1 全ファイルをローカルにバックアップし、感染範囲をスキャンする
STEP 2 mu-plugins ディレクトリを空にし、正規のファイルだけ戻す
STEP 3 uploads 内の不審なアーカイブとPHPファイルをすべて削除
STEP 4 テーマ・プラグイン全ファイルを公式版で上書き
STEP 5 全パスワードを再変更し、侵入経路を遮断する

クリーンアップ後に再感染を防ぐための恒久対策

クリーンアップ後に再感染を防ぐための恒久対策

ファイル改ざん監視を導入する

WordPress のコアファイルやテーマ、プラグインの変更をリアルタイムで検知するセキュリティプラグインを導入する。改ざんが発生した瞬間に通知を受け取れるため、感染の早期発見につながる。ファイル整合性チェック機能を持つものを選び、既知のクリーンな状態との差分を定期的に比較する設定にしておく。

書き込み権限の厳格化

functions.php や mu-plugins ディレクトリに対して、Web サーバーの実行ユーザーが書き込みできないようにパーミッションを設定する。通常、PHP ファイルは 644、ディレクトリは 755 が基本だが、特に標的になりやすいファイルは 444 に設定して変更を防止する。ただし、テーマやプラグインの自動更新を利用している場合は、更新時に権限を一時的に戻す運用が必要になる。

使用していないプラグインとテーマの完全削除

無効化されているだけのプラグインやテーマも、ファイル自体がサーバー上に残っていれば攻撃の入り口になる。WordPress の管理画面から完全に削除し、ディレクトリごと消去する。休眠中の拡張機能は更新が止まっていることが多く、既知の脆弱性を放置することになる。

よくある質問

functions.php の感染コードを手動で削除するだけではなぜダメなのか

感染コード自体が別の場所にバックドアを設置しており、そのバックドアが functions.php の状態を監視しているためだ。削除を検知すると自動的に再書き込みが行われ、さらにバージョン番号を上げて「対策済み」を装うケースもある。感染コードの削除と同時に、すべてのバックドアを無効化しなければ根本的な解決にはならない。

mu-plugins ディレクトリに心当たりのないファイルがあるが、削除しても問題ないか

mu-plugins は「マストユースプラグイン」と呼ばれ、有効化操作なしで自動的に読み込まれる特殊なディレクトリだ。正規のファイルはプラグイン名や機能がわかる名前になっていることが多い。ランダムな文字列や .php 以外の拡張子を持つファイルはマルウェアの可能性が高い。まずすべてを退避させ、サイトが正常に動作することを確認してから、必要なものだけ戻す手順が安全だ。

アップロードディレクトリ内の .zip ファイルはすべて削除すべきか

自身でアップロードした覚えのないアーカイブファイルは削除する。正規のプラグインやテーマが生成するバックアップファイルもあるが、マルウェアがアーカイブを設置する場合、ファイル名が日付とは無関係な文字列だったり、設置日時が不自然に新しいことが多い。不安な場合は、ファイルをダウンロードして中身を確認し、PHPコードや難読化されたスクリプトが含まれていないか検査する。

感染を完全に駆除したかどうかをどう確認すればいいか

セキュリティスキャナーを複数かけ、感染の痕跡が検出されないことを確認する。さらに、functions.php のハッシュ値を記録し、1週間後、2週間後と定期的に比較して変化がないことを検証する。サーバーのアクセスログも確認し、不審な POST リクエストや、管理画面外からの PHP ファイルへの直接アクセスがないかを監視する。

SC_TH_BEGIN タグは特定のマルウェアファミリーの特徴か

このタグは、標的のサイトを識別し、感染状況を管理するためのキャンペーン固有のマーカーと考えられる。バージョン管理とハッシュ検証の仕組みから、手動での駆除を想定した設計になっている点が特徴的だ。未知のマルウェアファミリーである可能性もあり、一般的なマルウェアスキャナーの定義ファイルが追いついていない場合がある。

この記事のポイント

  • functions.php だけの削除では自己修復型マルウェアの再感染を防げない
  • mu-plugins の隠しファイルと uploads 内のアーカイブが感染の復旧ポイントになる
  • 全テーマ・プラグインを公式ソースで上書きし、バックドアを一掃する
  • パーミッションの厳格化とファイル改ざん監視で恒久的な防御を敷く
  • 駆除後はハッシュ値の定期比較で再感染の兆候を早期発見する
WPManageNinjaプラグイン更新で不正コード混入 全亜種を検出するSQLと駆除手順

WPManageNinjaプラグイン更新で不正コード混入 全亜種を検出するSQLと駆除手順

WPManageNinja のプラグインを更新したら不正なコードが紛れ込んだ場合、最も確実な検出方法はデータベースのオプション値を直接検索することだ。apii.observer というドメイン名を探す SQL クエリを実行すれば、影響を受ける全13プラグインの亜種を一網打尽にできる。

なぜ通常のプラグイン更新でバックドアが入り込んだのか

なぜ通常のプラグイン更新でバックドアが入り込んだのか

2026年7月31日、WordPress 用プラグインを多数提供する WPManageNinja 社の旧アップデートサーバーが侵害された。同社は以前に販売プラットフォームを移行しており、本来は停止しているはずの旧サーバーが生き残り、かつプロキシが一部の更新トラフィックをそちらに転送し続けていた。この時間帯に管理画面で「更新」ボタンを押したユーザーは、正規の更新チャネルを通じて悪意あるパッケージを受け取ってしまったのだ。

パスワード突破でも脆弱性攻撃でもなく、ただの更新作業が侵入口になった。このサプライチェーン攻撃の怖さは、更新ボタンを押したこと自体はまったく正常な運用であり、疑いようがない点にある。

影響を受ける13のプラグインを確認する

影響を受ける13のプラグインを確認する

WPManageNinja 社が公開したインシデント対応資料には13のプラグインプロファイルが含まれている。一方、当初の告知では一部しか公表されていなかった。注意すべきなのは以下の全リストだ。

  • azonpress
  • fluent-affiliate-pro
  • fluent-boards-pro
  • fluent-booking-pro
  • fluent-community-pro
  • fluent-player-pro
  • fluent-support-pro
  • fluentcampaign-pro(FluentCRM)
  • fluentform-signature
  • fluentformpro
  • ninja-tables
  • wp-payment-form-pro(Paymattic)
  • wp-social-ninja-pro

これら13種類のいずれかを利用しているサイトは、更新履歴の有無にかかわらず直ちに調査が必要だ。WPManageNinja 社からドメインリストがメールで送られていたとしても、それを鵜呑みにしてはいけない。実際に、リストに載っていないサイトからも感染が確認されている。

侵入されたサイトに見られる具体的な症状と隠蔽の仕組み

侵入されたサイトに見られる具体的な症状と隠蔽の仕組み

バックドアは、正規プラグインのフォルダ内に PHP ファイルを1つ追加し、既存のファイルの末尾に小さなローダーコードを追記する形で設置される。Fluent Forms Pro の例では、以下のようになっていた。

Before(感染状態)
fluentformpro/libs/ に class-license-sync.php(39,743バイト)が存在
fluentformpro.php の22〜24行目に不正な require_once とクラス呼び出しが追記
After(駆除後)
正規のプラグインファイルのみ存在し、不正ファイルは削除済み
fluentformpro.php の行数がクリーンな状態に戻っている
感染状態   駆除後

このデモは Fluent Forms Pro におけるバックドアの有無を視覚化したものだ。

データベースには _wp_update_meta_cache_site_transient_update_meta といったオプション名が書き込まれる。これらの名称は WordPress コアが使う一時データ(Transient)に酷似しており、ひと目見ただけでは異常と気づきにくい。オプションの値には攻撃者のコマンド&コントロールサーバーである apii.observer が含まれ、さらにサイト固有のトークンとログインキーが保存されていた。このログインキーはパスワードなしで WordPress 管理画面にログインできる「万能鍵」であり、ファイルを削除するだけでは不正アクセスのリスクは消えない。

全亜種を一発で検出するデータベースクエリ(WP-CLI)

全亜種を一発で検出するデータベースクエリ(WP-CLI)

63個のオプション名や26個の cron フックを個別に調べる必要はない。すべての亜種に共通する特徴は、C2 サーバーアドレス apii.observer をオプションの値として持っていることだ。したがって、次の1行のクエリで全13プラグインの感染を一括検出できる。

PREFIX=$(wp db prefix)
wp db query "SELECT option_name FROM ${PREFIX}options \
WHERE option_value LIKE '%apii.observer%'" --skip-column-names

cron ジョブまで同時に調べたい場合は以下のように拡張する。

wp db query "SELECT option_name FROM ${PREFIX}options \
WHERE option_value LIKE '%apii.observer%' \
OR option_value LIKE '%wp_update_check_schedule%'" --skip-column-names

wp cron event list --fields=hook,next_run_relative \
| grep -E "wp_update_check_schedule|wp_license_verify_schedule"

このアプローチの本質は「マルウェアが自らのサーバーと通信しなければならない」という不変の事実を突く点にある。シグネチャリストは古くなるが、通信先のドメインはそう簡単には変わらない。覚えておいて損はない手法だ。

sFTP しか使えない場合のファイルチェック方法

sFTP しか使えない場合のファイルチェック方法

レンタルサーバーによっては SSH が提供されず、WP-CLI も使えないことがある。その場合は sFTP 経由でファイルを確認する。Python の paramiko ライブラリを使った検出スクリプトが有効だが、重要なのは「確実に読めたと言える状態だけをクリーンと判定する」ことだ。

STEP 1 sFTP で接続し、プラグインフォルダにアクセスできるか確認する
STEP 2 class-license-sync.php や NinjaTableDataSync.php が存在するか確認
STEP 3 ローダーが追記される fluentformpro.php などに不正マーカーが含まれるかテキスト検索
STEP 4 フォルダが存在したのに読み取れなかった場合は「CLEAN」と判定せず、必ず「ERROR」を返す

この判定フローは、読み取り失敗を「感染していない」と誤認させないための安全策だ。

より確実な方法として、販売元のアカウントからクリーンなプラグイン ZIP をダウンロードし、サーバー上のファイル群とファイルサイズを比較する手段もある。手元のクリーンコピーに存在しないファイルや、サイズが異なるファイルがあれば、それが不正コードの証拠になる。

安全かつ完全な駆除手順(ファイルとデータベースの順序が重要)

安全かつ完全な駆除手順(ファイルとデータベースの順序が重要)

駆除で最も失敗しやすいのが「データベースから先に削除する」手順だ。残ったファイルの cron が再度データベースに不正な行を書き込んでしまう。必ず以下の順序で実施する。

1. 感染したプラグインフォルダを削除し、クリーンな ZIP から再インストールする

wp plugin delete fluentformpro
wp plugin install /path/to/fluentformpro-clean.zip --activate
wp plugin get fluentformpro --field=version

バージョン番号を必ず確認し、6.2.8 や 6.2.9 といったアップデータ経由の古い番号が表示されたら、ZIP からの再インストールが正しく行われていない可能性がある。

2. データベースから不正なオプションと cron を削除する

PREFIX=$(wp db prefix)
# まず内容を確認
wp db query "SELECT option_name FROM ${PREFIX}options \
WHERE option_value LIKE '%apii.observer%'"
# 削除実行
wp db query "DELETE FROM ${PREFIX}options \
WHERE option_value LIKE '%apii.observer%'"
# cron イベントも削除
wp cron event delete wp_update_check_schedule
wp cron event delete wp_license_verify_schedule

3. ソルトを変更し、すべてのセッションを無効化する

wp config shuffle-salts で wp-config.php に定義されたソルト(8つのキー)を新しい値に置き換える。これにより、攻撃者が入手したログインキーを含むすべての既存セッションが強制ログアウトされる。

wp config shuffle-salts

その後、管理者パスワードを手動で変更する。ソルトの更新はパスワードそのものを上書きしないからだ。

4. 駆除後は状態を読み取って必ず検証する

削除コマンドの成功メッセージを信用してはいけない。cron 削除が正常に受け付けられても、実際には実行されずにスケジュールが残っているケースがある。再度クエリを実行し、返却行数がゼロであることを目視確認する。

wp db query "SELECT option_name FROM ${PREFIX}options \
WHERE option_value LIKE '%apii.observer%'" --skip-column-names
# 出力が完全に空であることを確認
wp cron event list --fields=hook,next_run_relative \
| grep -E "wp_update_check_schedule|wp_license_verify_schedule"
# 同じく出力が空であることを確認

最後に wp option get blogname やサイトのトップページ表示で WordPress が正常に起動していることを確かめる。

WP-CLI が使えない環境でのソルト変更と安全策

WP-CLI が使えない環境でのソルト変更と安全策

レンタルサーバーの制限や独自コネクターの仕様で wp config shuffle-salts が使えない場合は、FTP 経由で wp-config.php を直接編集する。このとき、次の点を徹底する。

  • 現在の wp-config.php を必ずローカルにバックアップする。
  • WordPress.org のソルト生成 API から新しい値を取得し、8つの define 文すべてを置換する。
  • 8つすべてを見つけられなかった場合は作業を中断する。部分的な置換はサイトを破壊する。
  • 置換後のファイルサイズが数百バイト以上変化していないか確認する。
  • アップロード後にサーバーからファイルを読み戻し、意図した内容かをバイト単位で比較する。
  • データベースパスワードを含むため、ファイル内容をログやターミナルに絶対に出力しない。

複数サイトをスクリプトで一括処理する場合は、1サイトごとに別プロセスで実行し、数秒の待機を挟む。連続したリクエストはサーバーのアンチボット機能にブロックされる原因になる。HTTP 202 や 429 が返ったら即座に全処理を停止する。

よくある質問

プラグインを更新していないのに感染する可能性はあるか

今回の経路は更新操作に限られる。ただし、過去に更新したタイミングが問題の時間帯と重なっていれば、更新していないつもりでも感染している場合がある。cron による自動更新が有効なら、手動更新していなくても該当する。

wp-config.php のソルトを変更するとどうなるか

そのサイトにログインしているすべてのユーザーが強制的にログアウトされる。パスワードは変わらないため、同じパスワードで再ログインは可能だ。「Remember Me」で保存されたセッションも無効になる。

感染したかどうか管理画面から判断できるか

見た目にはまったく変化がない。管理画面の表示や動作に異常が出ないよう設計されているため、プラグイン一覧や更新画面から気づくことはほぼ不可能だ。

データベースのバックアップから復元しても大丈夫か

バックアップの中に不正オプションが含まれていれば、復元で再感染する。リストア前に必ずバックアップの SQL を確認し、apii.observer を含む行がないか検索しておく必要がある。

WAF やセキュリティプラグインで防げたか

この攻撃は正規の更新チャネルを経由しているため、一般的な WAF やマルウェアスキャナーでは検知できない。実際に複数のセキュリティプラグインが稼働している状態でも、バックドアファイルを無害と判断した事例が報告されている。今回の経験から、セキュリティプラグインの「異常なし」を鵜呑みにしないことが重要だ。

この記事のポイント

  • WPManageNinja の当該13プラグインを利用しているサイトは、更新の有無にかかわらず即座に調査する。
  • 全亜種の検出は apii.observer を含むオプション値を SQL で LIKE 検索するのが最速かつ確実。
  • 駆除は「プラグインフォルダの再インストール」→「データベース削除」→「ソルト変更とパスワード変更」の順序厳守。
  • 削除後は成功メッセージを信じず、再度クエリを実行してゼロ件を目視確認する。
  • ソルト変更だけではパスワードは変わらない。管理者パスワードも忘れずに変更する。
WordPressサイトがハッキングされた時の難読化PHPコードの見つけ方と完全駆除手順

WordPressサイトがハッキングされた時の難読化PHPコードの見つけ方と完全駆除手順

WordPressサイトのindex.phpなどのコアファイルに難読化された悪意あるPHPコードが埋め込まれ、不審なサイトへリダイレクトされる被害が発生した場合、感染ファイルの完全削除とコアファイルの再インストール、全認証情報の変更を同時に進める必要がある。

この種の改ざんは「goto文」や16進数エンコードした文字列、外部サーバーとの通信機能などを使って巧妙に隠されている。残骸を残さず駆除し、再侵入経路をすべて塞ぐ手順を具体的に追っていく。

なぜWordPressサイトに難読化された悪意あるコードが仕込まれるのか

なぜWordPressサイトに難読化された悪意あるコードが仕込まれるのか

攻撃者が難読化コードを埋め込む目的は大きく3つある。不正なリダイレクトによるアフィリエイト収益の横取り、サイト訪問者の個人情報やパスワードの窃取(フィッシング)、そしてサイト自体を踏み台にして別のサーバーを攻撃するスパム配信やDDoS攻撃の中継だ。

コードの難読化は、セキュリティプラグインや手動の目視検査をすり抜けるために施される。変数名や関数名をランダムな文字列にし、goto文で処理の流れを複雑に飛ばすことで、実際に何を実行しているのかを読み取りにくくしている。この手のコードは、単独のファイルに留まらず、複数のテーマファイルやアップロードディレクトリに分散して設置されることが多い。

ハッキングの兆候(BEFORE)
  • トップページが別ドメインにリダイレクトされる
  • index.phpの先頭に見慣れないgoto文や16進数文字列のブロックがある
  • 管理画面が重くなり、プラグインの一覧に覚えのない項目が増えている
駆除後の正常状態(AFTER)
  • サイトのURLが正しく表示され、意図しない転送が発生しない
  • コアファイルが公式のチェックサムと完全一致する
  • 管理者アカウントやプラグインに不審な追加がない
感染中  駆除後

上記の例は、攻撃者が検索エンジンのボットを判別し、Googleなどのクローラーには元の正常なページを見せ、一般の訪問者だけに不正サイトへ飛ばす「クローキング」という手法の典型的な挙動を示している。

感染したWordPressサイトを手動で完全に駆除する手順

感染したWordPressサイトを手動で完全に駆除する手順

感染が疑われるファイルとディレクトリを優先して検査する

改ざんされたコードは、WordPressのルートディレクトリ直下のindex.phpwp-config.phpだけでなく、テーマやプラグインのディレクトリ、アップロードディレクトリにも潜む。次の場所を重点的に確認する。

  • ルートディレクトリの全.phpファイル(index.phpwp-load.phpwp-settings.phpなど)
  • /wp-content/themes/ 配下の全テーマ(特にアクティブなテーマのfunctions.php
  • /wp-content/plugins/ 配下の全プラグインファイル
  • /wp-content/mu-plugins/(もし存在すれば)
  • /wp-content/uploads/ 配下のPHPファイル(本来PHPファイルが存在すべきではないディレクトリ)
  • /wp-includes/ 配下のコアファイルの改ざん有無(後述のチェックサム検証で判別可能)
  • .htaccess ファイル(リダイレクトルールや不正なコードブロックが追記されていないか)

WordPressコアファイルの再インストールとチェックサム検証

wp-content ディレクトリと wp-config.php を除き、WordPressのコアファイルをすべて公式の配布ファイルで上書きするのは安全かつ最も確実な駆除方法だ。管理画面の「ダッシュボード」→「更新」から「再インストール」を実行するか、手動で行う場合は次の手順を踏む。

STEP 1 WordPress公式サイトから最新のzipファイルをダウンロードし展開する
STEP 2 現在のサイトから wp-content ディレクトリと wp-config.php をローカルにバックアップ(退避)する
STEP 3 サーバー上の wp-adminwp-includes を完全に削除し、展開した公式ファイルの同名ディレクトリをアップロード
STEP 4 ルートディレクトリの全 .php ファイル(wp-config.php を除く)を公式ファイルで上書き
STEP 5 退避しておいた wp-contentwp-config.php をサーバーに戻し、wp-config.php に手動で追記された不正な行がないか目視確認
STEP 6 wp-includes/version.php を確認し、バージョン番号が正しいか検証。その後、プラグイン「WordPress Core Verify」などでチェックサムを検証する

管理画面にアクセスできるなら「ダッシュボード」→「更新」の「WordPressを再インストール」ボタンを押すだけで同等の処理が完了する。手動で行う場合も、wp-contentwp-config.php を保護している限り、データ消失の心配はない。

感染源を特定するためにサーバーログとタイムスタンプを照合する

どの脆弱性から侵入されたかを特定するには、改ざんされたファイルのタイムスタンプとサーバーのアクセスログ、エラーログを突き合わせる。ログに「POST /wp-admin/admin-ajax.php」や「POST /xmlrpc.php」への不自然な連続リクエストが記録されていれば、ブルートフォース攻撃やXML-RPCを経由した侵入が疑われる。

ホスティングのコントロールパネルから「Raw Access Logs」や「エラーログ」をダウンロードし、改ざん発生日時の前後を中心に調べる。プラグインやテーマのアップデートを長期間放置していた場合は、脆弱性情報データベース(CVE)で該当バージョンの既知の脆弱性を検索し、侵入経路の仮説を立てる。

適切なファイルパーミッションに設定し直す

ディレクトリは755、ファイルは644を基本とし、wp-config.phpだけは440または400に設定して読み取り権限を厳格にする。特にwp-content/uploads/ 配下にPHPファイルが置かれている場合は、そのディレクトリでPHPの実行を.htaccessで明示的に拒否しておくべきだ。

# .htaccess を wp-content/uploads/ に設置する場合
<FilesMatch "\.(php|php\.)$">
    Require all denied
</FilesMatch>

プラグインとテーマをクリーンな状態に置き換える

すべてのプラグインとテーマを、公式ディレクトリまたは購入元から再ダウンロードした完全に新しいファイルで上書きする。非公式サイトから入手したプラグインやテーマ、長期間更新が止まっているものは、この機会に削除するか代替に切り替える。

とくに mu-plugins ディレクトリは、手動で設置しない限り通常は存在しない。見慣れないディレクトリ名やPHPファイルがあれば、即座に削除する。

データベース内の不正なコードや管理者アカウントを検査する

phpMyAdminやWP-CLIを使って、wp_options テーブルの「siteurl」や「home」、wp_posts の投稿内容に不審なスクリプトタグやiframeが埋め込まれていないかを調べる。同時に、wp_users テーブルで身に覚えのない管理者アカウントが追加されていないかも必ず確認する。

データベース内の隠し管理者を一括で探すには、WP-CLIで次のコマンドを実行すると早い。

wp user list --role=administrator

全認証情報を変更しセキュリティキーを再生成する

駆除作業が完了したら、WordPress管理画面の全ユーザーパスワード、データベースパスワード、SFTP/SSHパスワード、ホスティングのコントロールパネルパスワードをすべて変更する。wp-config.php の「AUTH_KEY」「SECURE_AUTH_KEY」などのセキュリティ用ソルトも、WordPress.orgの公式ソルト生成ツールで新しいものに置き換える。

よくある質問

クリーンなバックアップがない場合、復旧の優先順位はどう決めればよいか

バックアップが存在しない、またはバックアップ自体が感染後のものである場合は、コアファイルの再インストールとプラグイン・テーマの全置き換えを最優先する。データベースの修復はその後で、不正な投稿やユーザーを手動で削除する。サイトのダウンタイムを最小限にするため、メンテナンスモードを有効にして作業するのが安全だ。

無料のプラグインやテーマが感染の原因になることはあるか

公式ディレクトリで配布されている無料プラグインでも、深刻な脆弱性が発見されて更新が滞れば攻撃の糸口になりうる。とくに、公式ディレクトリ以外のサイトから配布されている「nulled(クラック)」版の商用プラグインやテーマは、ほぼ確実にバックドアが仕込まれているため、絶対に使用してはいけない。

データベースを直接編集する際の注意点は何か

phpMyAdminなどでデータベースを直接操作する場合は、必ず事前にデータベース全体のダンプ(エクスポート)を取っておく。wp_options テーブルの「active_plugins」行を誤って削除すると全プラグインが無効化されるため、行の値を直接編集するよりも、WP-CLIのwp optionコマンドを使うほうが安全に操作できる。

cronジョブに疑わしいタスクが仕込まれていないか調べる方法は

WordPressの疑似cron(WP-Cron)はwp_options テーブルの「cron」オプションに格納されている。WP-CLIでwp cron event listを実行すると登録されている全タスクを一覧できる。ホスティング側の本物のcronジョブ(crontab)に不正なエントリが追加されていないかも、ホスティングの管理画面またはSSHで確認する。

攻撃者に検索エンジンのボット判定をすり抜けられた場合の追加対策は

難読化コードがボット判定を行っている場合、駆除後もクローキング用のキャッシュがCDNや検索エンジンに残っている可能性がある。サイト駆除後にGoogleサーチコンソールで「URL検査」→「インデックス登録をリクエスト」を実行し、CDNの全キャッシュをパージする。HTTPヘッダーを改ざんされていないかも、curlコマンドなどで確認しておくべきだ。

この記事のポイント

  • 難読化PHPコードは複数ファイルに分散して設置されるため、ルート直下、テーマ、プラグイン、アップロードディレクトリをすべて検査する
  • WordPressコアファイルは wp-contentwp-config.php を保護したうえで公式ファイルで完全に上書きする
  • 改ざんファイルのタイムスタンプとサーバーログを照合し、侵入経路を特定して永続的な対策を講じる
  • 全認証情報の変更とソルトの再生成を駆除と同時に行い、再侵入を防ぐ
  • クリーンなバックアップがある場合は、全ファイルとデータベースの削除後にバックアップから復元するのが最も確実な方法である