10月28日、WordPress 7.1.4がセキュリティ修正版として公開されました。公式の版ごとの解説ページによると、直した脆弱性は22件で、17項目に分けて記載されています。3週間前の7.1.3で直したのは7件だったので、件数だけを見てもかなり大きな修正版です。
ただ、今回の中身は数の多さより、どこに集まっているかのほうが興味深いものでした。ひとつはWordPressが自分を更新する仕組みそのもの、もうひとつは「投稿者」や「寄稿者」といった、記事を書ける権限から始まる不具合です。ここでは公式の記載と、Patchstackや海外のホスティング事業者が修正のコードを読んで書いた解説をもとに、論点を整理してみます。
短い間隔で続く7.1系の修正版
まず背景から。7.1系は9月に公開されてから、セキュリティ修正版が続いています。ある事業者のまとめでは、9月17日の7.1.1、9月22日の7.1.2、10月6日の7.1.3、そして今回の10月28日と、6週間で4本目です。月に1回まとめて更新する運用だと、修正が公開済みなのに古い版のまま過ごす期間が長くなる、という指摘もされていました。
また、同じ解説によると、10月29日の時点で今回の22件にはCVE番号や深刻度の点数がまだ付いていません。7.1.3の7件も同様だそうです。点数が出るのを待って対応を決める運用だと、判断が遅れることになります。この解説は、点数ではなく「攻撃する側が先に何を持っている必要があるか」で優先順位を考えるよう勧めていて、これは現場でもそのまま使える考え方だと思います。
更新作業の数秒間だけ開いていた入口
今回もっとも目を引くのは、アカウントがなくても狙える唯一の不具合が、WordPressの更新処理の中にあったことです。公式の記載では「コア更新中の競合状態(race condition)により、遠隔からコードを実行されるおそれ」とだけ書かれています。
Patchstackの解説によると、WordPressは本体やプラグインを更新するとき、配布ファイルをwp-content/upgrade/の下に展開してから所定の場所へ写します。このフォルダーはウェブサーバーから普通に見える場所にあり、しかも展開先の名前は推測しやすいものでした。さらに、事前の片付けの処理が、フォルダーの中身を一覧表示させないためのindex.phpまで消していたといいます。
その結果、更新にかかる数秒のあいだ、完全なWordPress一式が推測できる場所に置かれ、条件によっては一覧からも名前が読める状態になっていました。展開された一式には設定前のインストーラーも含まれるため、そこへ間に合えば、他人のサイトの中で自分の用意したデータベースに向けて導入を走らせることができた、というのがPatchstackの見立てです。
修正では、展開先のフォルダー名に16文字のランダムな文字列を付け、index.phpを作り直して消さないようにし、展開したフォルダーからwp-config.phpを写さないようにしました。Patchstack自身は、狙える時間が非常に短いことから「実用的な攻撃とは言いにくい」と評価しています。一方で、アカウントも操作も要らず、更新のたびに同じ時間帯が訪れる点は軽く見ないほうがよさそうです。
実務的に大事なのは、PHPで動くファイアウォール系のプラグインではこれを防げないという指摘です。展開された一式は自分のファイルからWordPressを起動するため、サイト側の防御の仕組みがそもそも読み込まれません。対策はウェブサーバーの側で行うもので、wp-content/upgrade/への外からのアクセスを断ち、フォルダーの一覧表示が無効になっているかを確かめるよう勧められています。ApacheであればOptions -Indexesの設定が該当します。共用のレンタルサーバーでは事業者の設定次第なので、気になる場合は問い合わせてみるのが確実です。
寄稿者の下書きが管理者の画面で動く
残りの大半は、ログインできるアカウントを持つ人が起点になる不具合です。Patchstackの一覧では、17項目のうち多くが「寄稿者(Contributor)以上」で成り立つ保存型のクロスサイトスクリプティング(XSS)でした。
寄稿者は記事を書いて承認待ちに出せますが、公開はできません。WordPressは、こうした権限の人が保存する内容からスクリプトを取り除く仕組みを持っています。今回の修正の多くは、その取り除く処理か、表示する処理のどこかをすり抜けていたものです。保存型のXSSは、その内容を開いた人のブラウザーで動きます。寄稿者の下書きを開くのは、たいてい承認する編集者や管理者です。
中小企業のサイトでは、外部のライターや社内の別部署に寄稿者のアカウントを渡しているところも少なくありません。権限を絞っているから安心、と考えていた運用ほど、今回の修正の意味は大きいはずです。Patchstackは更新に加えて、寄稿者以上のアカウントを誰が持っているかを見直すよう勧めています。使っていない外部アカウントが残っていないか、この機会に棚卸ししておくとよさそうです。
動くブロックとdata属性の盲点
寄稿者起点の不具合のうち、Patchstackによると17項目中7項目が、Interactivity APIという同じ仕組みに集中していました。これは、画像のライトボックスやナビゲーションのモバイル用メニュー、ページを読み直さずに次の一覧を出すクエリーループなど、標準のブロックをブラウザー上で動かすための仕組みです。
根っこは共通しています。WordPressのHTML検査(KSES)は、どんなdata-属性でも値を問わず通していました。そしてInteractivity APIの指示は、data-wp-interactiveやdata-wp-bind--hrefのように、すべてdata-属性で書かれます。そのため寄稿者が記事の中に指示を書き込むと、検査を素通りし、訪問者がページを開いたときにブラウザー側の処理がそれを実行してしまう、という構図でした。
そこから、リンク先にjavascript:の値を差し込む、サーバー側では断っていたonclickなどの属性に値を結び付ける、JavaScriptの基本的なオブジェクトに手が届く名前を使う、といった複数の抜け道が見つかっています。訪問者のブラウザーのタブを固まらせる不具合も含まれていました。修正は、検査の段階、PHPの処理の段階、ブラウザー側の処理の段階の三か所で行われています。
テーマやブロックを自作している制作者にとっては、更新後の動作も気になるところです。ある解説によると、影響が出るのは、今回断られるようになった属性(イベントの属性、javascript:のURL、object要素の危険な属性)に値を結び付けている場合に限られます。サーバー側で取り除かれた場合はデバッグログに通知が出るので、更新後に一度ログを確かめておくと安心です。
二つの読み手が食い違うと検査はすり抜ける
今回の修正を並べて読むと、もうひとつ共通する型が見えてきます。同じ内容を二つの処理が別の解釈で読み、片方で確かめたはずのものが、もう片方では違う意味になってしまう、という型です。
たとえばブロックの区切りは<!-- wp:paragraph -->のようなHTMLコメントで保存され、サーバーのPHPと編集画面のJavaScriptの両方で読まれます。Patchstackによると、両者は「空白」とみなす文字の範囲が違っていて、特殊な空白を使うと、PHPにはただのコメント、編集画面には属性付きの本物のブロックとして見えていました。修正後は、すべての読み手が同じ空白の文字だけを認めるようにそろえられています。
カテゴリーブロックでは、検査を通したラベルを後から書式の文字列として展開していたため、検査が終わった後に中身が変わっていました。画像と再生リストのブロックでは、正規表現の置換で特別な意味を持つ記号が展開されていました。カスタマイザーでは、ページ内の値をjQueryに渡したところ、jQueryがそれを検索の条件ではなく作るべきHTMLとして扱っていました。どれも「確かめた後に、別の処理が別の読み方をする」という点で共通しています。
WordPressは少し前から、HTMLを文字列として扱うのをやめ、構造として読むHTML APIへの置き換えを進めています。今回も画像と再生リストのブロックはHTML APIで組み直されました。読み手を一つにそろえる方向の作り直しが、こうした食い違いを減らしていくのだろうと感じます。
古い版のサイトと日々の運用で確かめたいこと
公式の記載では、修正は4.7系までさかのぼって提供されるとされ、版ごとの番号も載っています。たとえば6.9系は6.9.11、7.0系は7.0.8です。7.1系に上げなくても、自分の系統の最新版にすれば修正は入ります。なお、公開から約3時間後に確かめた事業者によると、その時点では6.5系以前の修正版はまだ配布が始まっていなかったそうです。古い系統のサイトは、管理画面で実際に更新が届いているかを確かめておくのが確実です。そもそも公式に支援されているのは最新版だけで、古い系統への修正は好意によるものとされているので、新しい系統への移行も計画に入れておきたいところです。
このほか、各解説が勧めていた点をまとめると、更新が自動で当たらない設定のサイトがないか確かめること、ステージングや開発用の複製も同じ更新の対象にすること、カスタマイザーで公開前の変更を下書き保存していた場合は外から見えていた可能性を前提に考えること、などが挙がっていました。カスタマイザーの件は、ログインしていない訪問者でも、公開前の変更の中身を見られたという不具合です。
今回の修正で印象に残るのは、サイトを守るための更新そのものに隙があり、しかもプラグイン型の防御では届かない場所だったことです。更新を急ぐことと並んで、ウェブサーバーの設定やアカウントの持ち方といった、WordPressの外側の地味な基本が効いてくる修正版だったといえそうです。













