Under the Hood of WP Cerber 9.9
English version: Under the Hood of WP Cerber 9.9
今回のリリースは、WP Cerber の、問題が発生するまで気づかない部分に焦点を当てたものです。設定の保存と復元方法、トラフィック インスペクターによる難読化 JavaScript の読み取り方法、古いホスティング環境でのプラグインの動作などを強化しました。また、WordPress 設定 API からの移行作業も完了しました。内部で変更された内容と、それがお客様のサイトにどのような影響を与えるかを以下に示します。
破損したデータベースでも保持される設定
WP Cerber は、設定を単一の保存オプションCERBER_CONFIGに保持しています。これまで、この値が破損してデシリアライズできなくなった場合、プラグインは破損した結果をそのままarray_merge()に渡していました。PHP 8 では、プラグインの読み込み時に致命的なTypeErrorが発生していました。このエラーは読み込み中に発生したため、プラグインの管理画面だけでなく、サイト全体がダウンしてしまいました。
crb_get_settings()は、 crb_unserialize()が返す値を検証してから使用するようになりました。保存されたデータを配列に解析できない場合、プラグインはクラッシュする代わりにデフォルト設定に戻ります。この失敗は、 CRB_Issues::add()を通じて、コードcorrupted_settingsの下に永続的な重大な問題として記録されます。この問題は、 cerber_settings_update()内で実行される次回の設定保存時に自動的に解消されます。
見た目は似ているが実際は異なる2つのケースを慎重に区別しました。空の保存値、または偽の保存値は、設定がまだ存在しないことを意味します。この値はデシリアライズされることはなく、プラグインは何もエラーを発生させることなく、静かにデフォルト値にフォールバックします。これを報告しても誤報になってしまいます。corrupted_settings corrupted_settings問題は、空でない保存値が配列にデシリアライズされなかった場合にのみ発生するようになりました。これは、元の本番環境でのクラッシュの原因となった状況と全く同じです。
同じ機能内の近接する2つのブランチに同じ規律が適用されました。Cloudflareアドオンの互換性マージでは、マージ前に配列が必要になりました。CERBER_WP_OPTIONSブランチはCERBER_WP_OPTIONS特定の設定が要求されていない場合、常に配列を返します。
ガードの上に自己修復バックアップを搭載
デフォルト設定に戻すことでサイトは稼働し続けますが、管理者の設定は破棄されてしまいます。そこで、ガードの上にリカバリーレイヤーを追加しました。新しいクラスCRB_Settings_Backup 、 CERBER_CONFIGの最後に有効だったコピーをプラグイン独自のキーバリューストレージに保持します。
バックアップは、作成者のユーザーID、タイムスタンプ、および作成時のコンテキストとともに、生のJSON形式で保存されます。設定の更新が成功した後、設定のインポート後、プラグインのアップグレード後、および日々のメンテナンス作業によって更新されます。既存のバックアップを置き換えることができるのは、有効な構成と既知のコンテキストコードのみです。
破損検出機能が読み取り不能なCERBER_CONFIG検出すると、自動的に復旧処理が実行されます。復旧が成功すると、プラグインは発生した事象を説明し、設定を確認して保存するよう求める警告を表示します。この警告は、ユーザーが設定を確認して保存すると解消されます。使用可能なバックアップが存在しない場合、または復元した値を書き込めない場合は、プラグインは既存の最終手段の動作を維持し、デフォルト設定に戻ります。この場合、警告ではなく重大な問題として記録されます。

WP Cerber settings recovery flow. Missing settings quietly fall back to defaults, while corrupted settings trigger automatic recovery from the last-known-valid backup.
リカバリを正しく行うには、WordPress のキャッシュを尊重する必要があります。recover recover()復元された値を書き込む前に、破損したCERBER_CONFIGオプションを削除します。この手順がないと、古いオプション キャッシュによってupdate_site_option()が値が変更されていないとみなしてデータベースへの書き込みをスキップしてしまう可能性があります。バックアップ自体はオブジェクト キャッシュをバイパスして読み書きされるため、永続的なcerber_setsレコードのみが信頼されます。受け入れられるコンテキスト コードのセットは閉じられています。ライター、 sync() 、およびペイロード バリデーターは、宣言された 4 つのコードのみを受け入れるため、保存されたすべてのペイロードは、リカバリが後で実行するのと同じ検証に合格できます。
mysqlnd がインストールされていないホストでは、致命的なエラーは発生しなくなりました。
WP Cerber は mysqli を介してデータベースからデータを読み取ります。取得パスの 1 つであるCRB_Database::fetch_result_set() 、 mysqli_result::fetch_all()を使用して、結果セット全体を 1 回の呼び出しで取得します。このメソッドは、PHP の mysqli 拡張機能が mysqlnd ドライバに基づいて構築されている場合にのみ存在します。mysqli が古い libmysqlclient ライブラリに対してコンパイルされているホストでは、このメソッドは存在せず、呼び出すと致命的なエラーになります。
この修正はcerber-common.phpの他の箇所ですでに使用されているパターンに従っています。高速パスを実行する前に、コードはfunction_exists('mysqli_fetch_all')チェックします。この関数が利用できない場合は、 MYSQLI_ASSOCとMYSQLI_NUM両方の形状について、 mysqli_result::fetch_array()を使用して結果セットを 1 行ずつ読み取ります。行の順序、結果の構造、クリーンアップ、および既存のRevalt戻り値契約はすべて保持されます。フォールバックでは、高速パスと同じデータが返されます。
また、管理者がフォールバック状態になっていることを把握できる方法も提供しました。CRB_Issue_Monitor の新しい検出器は、mysqli 拡張機能がロードされているにもかかわらずdb_driver_no_mysqlnd mysqli_fetch_all()が見つからない場合に、 CRB_Issue_Monitorというアドバイザリ問題を提起します。この通知はシステム準備状況ウィジェットに表示されます。これは情報提供のためのもので、プラグインがフォールバック状態でも動作し続けることを確認し、互換性とパフォーマンス向上のために mysqlnd を有効にすることを推奨しています。
難読化されたJavaScriptの検出精度向上
CRB_JS_Detector evalデフォルトで有効になっている Traffic Inspector の一部としてリクエスト フィールドを検査します。eval、 script 、 XMLHttpRequestなどのプリミティブを隠すために難読化された JavaScript を検出します。設計目標は、誤検出率を低く抑えつつ、高い信頼性で検出することです。検出器は、エンコードされた文字列であることが明確な入力のみをデコードし、それ以外はそのままにします。今回のリリースでは、実際のバイパスを修正し、検出器が読み取れる範囲を拡大するなど、いくつかの変更が加えられています。
完全に16進数エスケープされた文字列を通過させてしまう不具合
今回のリリースでは、バグ修正から始まりました。16進エスケープのヒューリスティックは、一致した各文字列trim( $m, "\\'\"" )で正規化していました。PHP のtrim() 2 番目の引数をリテラルの接頭辞ではなく、削除する文字のセットとして扱います。このセットに含まれるバックスラッシュは、最初の\xNNエスケープの先頭のバックスラッシュと開始引用符を削除しました。残った文字列の長さは2N + 1という奇数でした。奇数長ガードは、文字列が16進数で正しくエンコードされていないと判断し、デコードを完全にスキップしました。その結果、 \xNNエスケープのみで構成された文字列が検査をすり抜け、 eval 、 script 、 XMLHttpRequestなどの16進数エスケープされたプリミティブがデフォルトの request-field パスで検出されませんでした。今回の修正により、完全に16進数エスケープされた文字列の正しいデコードが復元されます。
エスケープシーケンスと文字コードの対応範囲の拡大
次に、検出器の理解範囲を拡張しました。以前は\xNNのみを認識していたエスケープヒューリスティックは、 \uNNNNおよび\u{...}エスケープも処理するようになりました。これらの形式が混在する文字列も処理しますが、完全にエスケープされた文字列のみが検査されるというルールは維持されます。デコードは専用のヘルパー関数cerber_decode_js_escapes()に移動しました。この関数は ASCII コードポイントをデコードし、それ以外のすべてを保持します。内部 PCRE エラーが発生した場合、このヘルパー関数は空の文字列ではなく元の入力を返すため、デコード失敗によってチェック対象の値が消去されることはありません。
文字コードヒューリスティックも変更されました。以前は、デコードされた出力を外部URLとIPアドレスのみを対象にしていましたが、現在は実行プリミティブとDOMプリミティブも対象としています。数値は、任意の数値配列ではなく、明示的なfromCharCode(...)構文からのみデコードされます。両方のヒューリスティックは、識別子境界を持つトークン認識プリミティブパターンを共有するようになりました。この変更により、 descriptionやevaluationといった通常の単語が、その中のevalやscriptサブストリングに一致しなくなりました。
ラップされた整数リテラル
fromCharCode 、各引数に ToUint16 を適用するように定義されています。これは、ASCII コードに 65536 の倍数を加えると、同じ文字が生成されることを意味します。以前のバージョンのヒューリスティックでは、6 桁までのリテラルしか受け入れられなかったため、7 桁のラップされた値が見落とされていました。たとえば、 1048677コード ユニット 101、文字eにデコードされます。検出器は、 Number.MAX_SAFE_INTEGERまでの符号なし 10 進数および 16 進数のリテラルを受け入れ、PHP の整数サイズに関係なく、それぞれを ToUint16 コード ユニットに還元します。先頭にゼロが付いた 10 進数は、従来の 8 進数とあいまいになるため拒否されます。桁ごとの作業に制限があるため、公開リクエストで非常に長いリテラルを使用しても、無制限の計算が発生することはありません。
後続の修正により、関連するギャップが解消されました。サポートされていない最初の引数に対して空の文字列を返すと、デコードされた呼び出し全体が破棄されます。攻撃者は、検出可能なペイロードに範囲外のトークンを1つ追加することで、検出を抑制できます。そのようなトークンの1つが9007199254741024で、JavaScriptではToUint16を介してスペースにマッピングされます。現在、構造的に有効だがサポートされていないトークンはアンダースコアの番兵に置き換えられ、呼び出しの残りの部分は引き続きデコードされます。アンダースコアは単語文字であるため、偽造された識別子境界がキーワードの隣に形成されるのも阻止します。
コメントで区切られた引数
以前の回避策では、JavaScriptのコメントが使用されていました。この手法では、入力から空白を削除して圧縮していたため、コメントがそのまま残っていました。数値引数の間にコメントが挿入されると、数値リストのマッチングが壊れてしまい、 String.fromCharCode(101,/*x*/118,97,108,40,49,41,59)のような呼び出しは検出を回避していました。今回の検出では、 fromCharCode引数リストを検証およびデコードする前に、ブロックコメントと行コメントを削除し、引用符で囲まれた文字列の内容は保持します。また、キャプチャはドットオールモードで実行されるため、複数行にわたる引数リストは単一の呼び出しとして読み取られます。
WordPress設定APIの廃止
WP Cerber の設定ページは、WordPress の設定 API をベースに構築されていました。フォームは/wp-admin/options.phpに送信され、プラグインはregister_setting() 、 add_settings_section() 、 add_settings_field() 、 settings_fields() 、 do_settings_sections()を使用してフォームを登録およびレンダリングしていました。この方法は機能していましたが、プラグインの管理画面の UI が、手続き型の WordPress サブシステムとその慣習に依存していました。今回のリリースでは、プラグイン独自のフォームエンジンへの移行が完了しました。この移行は、一括変更ではなく段階的に実施しました。
まず、境界線を設定しました。Settings APIへの直接呼び出しはすべて、 CRB_Legacy_Settings_Manager静的クラスに集約しました。コードベースをスキャンした結果、Settings APIの10個の関数のうち、2つのファイルにある6つの呼び出し箇所で使用されているのはわずか5個であることがわかりました。残りの5個は一度も呼び出されていなかったため、境界線クラスには意図的にそれらの関数に対応するメソッドは設けませんでした。この手順により、動作は完全に維持されました。オプション名、オプショングループ、セクションIDとフィールドID、コールバック、フックのタイミング、出力はすべて変更されていません。
そこで、代替となるクラスを作成しました。新しいクラスCRB_Settings_Rendererは、 cerber_settings_config()によって返される宣言型構成からセクションとフィールド行を直接レンダリングします。設定フォームは、 options.phpではなく、プラグインの管理ページにPOSTで返されるようになりました。送信内容は、 admin_initで既存のパイプラインによって処理され、その後、設定ページへのPOSTリダイレクトGETが行われます。レンダリングされたマークアップは、見出し、セクションブロック、フォームテーブルの行に至るまで、以前のdo_settings_sections()が生成したものとバイト単位で同一です。
これに伴い、nonceの検証方法も変更されました。設定APIオプショングループに対してcheck_admin_referer()を使用する代わりに、プラグインは独自のcerber_nonceフィールドを検証するようになりました。このフィールドは、以前のバージョンでレンダリングされたフォームを含め、すべての設定フォームに既に存在していました。nonceが無効または期限切れの場合でもwp_die()でリクエストが停止することはなくなりました。代わりに、管理者通知がキューに追加され、フォームに戻ります。
options.phpから変更したことで、これまであまり注目されてこなかった古い動作も削除されました。以前のパスでは、各フォームグループの生のコピーがcerber-{group}という名前のグループごとのオプションに書き込まれていました。これらのコピーは、9.3.4 より前の移行とアンインストール時のクリーンアップでのみ読み取られ、実行時には読み取られることはありませんでした。現在は書き込まれていません。
Cerber.Hubと管理サイト
2つのコンテキストは、どちらも設定APIのワイヤフォーマットに依存していたため、しばらくの間古いパスを維持していました。1つはCerber.Hubのリモートレンダリングで、メインサイトと管理対象サイト間のプロトコルでoption_pageと_wpnonceフィールドが伝送されていました。もう1つは管理対象サイトの編集画面で、 options.phpによって実行されるpre_update_optionフィルターを通して保存されていました。私たちはそれぞれを順番に移行しました。
管理対象サイトの編集フォームは、 CRB_Settings_Rendererを介してレンダリングされ、新しい関数nexus_save_client_data_form()を介して保存されるようになりました。この関数は、グループ解決、所有者の詳細のstrip_tagsによるサニタイズ、未使用グループのクリーンアップなど、以前のコールバックのデータ処理を完全に再現します。また、明示的なnexus_is_main()およびis_super_admin()ガードにより、同じセキュリティ境界を維持します。その後、Cerber.Hub クライアントは Settings API エミュレーションも削除しました。さらに、 CRB_Legacy_Settings_Managerと、現在では使用されていないいくつかのヘルパー関数も削除されました。
リモート設定フォームは、 option_page 、 action=update 、 _wpnonce 、 _wp_http_refererなどの Settings API の非表示フィールドを出力しなくなりました。ローカルフォームと同じ内部フィールドに加え、リモートコンテキストで Nexus シールが渡されるようになりました。Nexus トランスポート認証は変更されておらず、管理対象サイトのリクエストの外側の境界として残っています。nexus_is_valid_request nexus_is_valid_request() 、 nexus_is_granted() 、 cerber_nexus_sealラウンドトリップはすべて以前と同様に動作します。転送された送信では、設定処理が実行される前にプラグイン nonce が引き続き検証されます。エントリポイントのチェックも追加しました。既知の画面に解決されない送信された設定画面は、処理が開始される前にWP_Errorで閉じられ、失敗メッセージには診断のために送信された値が含まれます。
ハードな切り替え、そしてアップグレード後に知っておくべきこと
これは意図的なハードカットオーバーでした。移行用のデュアルフォーマットパスは残さず、 options.php処理も一切行いませんでした。この決定に伴う例外的なケースが1つあり、明確に述べておく価値があります。以前のバージョンの WP Cerber でレンダリングされた設定フォームをアップグレード後に送信すると、一度だけ保存に失敗する可能性があります。その場合は、設定ページを再度開き、もう一度送信してください。新しくレンダリングされたフォームには新しい内部契約が適用され、正常に保存されます。管理サイトのリモート設定フォームにも同じことが当てはまります。
移行に伴い、命名規則の整理も行われました。オーバーロードされていた用語group 、定数、非表示のフォームフィールド値、パイプライン関数のシグネチャ、構成キーなど、 settings_screen_id画面を識別する箇所すべてで settings_screen_id に名前が変更されました。リモートエラーコードはunknown_settings_groupからunknown_settings_screenに名前が変更されました。アドオン開発者にとって重要な互換性の詳細が 1 つあります。 cerber_add_handler()は公開されており、外部ハンドラが読み取る可能性があるため、 update_settingsイベントのペイロードには、 settings_screen_idのエイリアスとして古いgroupキーがまだ含まれています。
UIファクトリーの基礎作業
管理画面では、WP Cerber はインライン マークアップではなく、内部の UI ファクトリを使用して HTML をレンダリングします。今回のリリースでは、2 つの構造的な構成要素が追加されました。crb_ui_fragment crb_ui_fragment()ラッパー タグのない、順序付けられた子要素の混合コレクションを生成します。crb_ui_element_set crb_ui_element_set() 、強制された子タイプを持つ同種のコレクションに対して同じことを行います。新しいフラグメント ノードとの混同を避けるため、流暢なCRB_UI_Fragment_BuilderをCRB_UI_Content_Builderに名前変更し、既存のコードが引き続き動作するようにクラス エイリアスを保持しました。
その結果、いくつかの呼び出しサイトが簡素化されました。crb_ui_message_box crb_ui_message_box()は、プレーンな文字列と数値を受け入れ、エスケープされた段落要素で囲むようになったため、呼び出し元は段落を手動で作成する必要がなくなりました。ダッシュボードとトラフィックログ画面に重複して表示されていたフェッチエラー通知は、1つの共有ヘルパーに移動されました。環境診断パネルは、新しいノード上で再構築されました。いずれの場合も、レンダリングされる HTML は変更されていません。これは、レンダラーに依存しない管理 UI の基礎となるものであり、現時点ではページ上には表示されません。
アップグレード後
管理者向けの簡単なチェックリスト:
- アップデート直後に最初の送信で設定フォームが保存されない場合は、設定ページを再度開き、もう一度保存してください。
- WP Cerberがデフォルト設定に戻ったり、バックアップから復元したりした場合は、管理画面に通知が表示されます。設定を確認して保存すると、通知が消えます。
- ホストがmysqlndドライバなしでPHPを実行している場合は、システム準備状況ウィジェットに表示されるアドバイザリを確認してください。プラグインは引き続き動作し、通知には推奨される変更内容が説明されています。