Criticalが4件並んだ日にやったこと
朝、通知を見たらCriticalが4件並んでいました。どれも同じ相手、Next.js本体です。
この日にやったことを順に書きます。結論から言うと、上げました。ただ、上げる前にやったことのほうが本題です。
脆弱性の通知が来たときに、何をどの順で確かめたか。自分のサイトが実際に影響を受けるのかを切り分ける手順と、それでも上げた理由です。
まず、中身を読む
4件は、実は2種類でした。同じものが2つのファイルで検出されているので、件数が倍に見えていました。使っているものを書いた一覧と、実際に入った版を固定した一覧の2つです。
| 種類 | 内容 |
|---|---|
| A | Windowsで動かしているサーバーでの、認証不要の遠隔コード実行 |
| B | 画像最適化の機能で、AVIF形式を扱ったときの遠隔コード実行 |
どちらも「認証不要」で「遠隔からコードを実行される」。言葉としては、これ以上ないくらい重い部類です。
Aは、条件に当てはまらなかった
Aの説明には、条件がはっきり書いてありました。WindowsのファイルシステムでNext.jsのサーバーが動いている場合に成立する、と。
そして、LinuxとmacOSは影響を受けないと明記されていました。
このサイトの本番はVercelで動いています。Linuxです。開発しているのもMacです。どちらも対象外でした。
重大度は、その脆弱性が最悪の場合にどれだけひどいかを表しています。自分の環境で成立するかどうかは、また別です。
色と数字だけ見て慌てると、条件を読まずに動くことになります。読めば1分でした。
Bは、入口の広さを自分で確かめた
Bのほうは、条件が「AVIF形式の画像を最適化したとき」です。攻撃者が用意した画像を最適化させられると成立します。
このサイトは画像の最適化機能を使っています。ここで確かめるのは、「誰でも好きな画像を最適化させられる状態か」です。
設定を見ると、最適化を許可している場所が2つだけ指定してありました。
remotePatterns: [
{ hostname: "localhost", port: "8080", ... },
{ hostname: "cms.kisaku.site",
pathname: "/wp/wp-content/uploads/**" },
]
つまり、CMSの画像置き場にあるファイルしか最適化できません。任意のURLを渡しても弾かれます。
そこに置くには、CMSにログインして画像をアップロードする必要があります。入口はかなり狭いと言えます。
ここで止めたくなりますが、狭いだけで塞がってはいません。
CMSのログインが破られれば成立します。そしてCMSは共用サーバー上のWordPressで、攻撃対象としてはありふれた構成です。「入口が狭いから大丈夫」は、入口が破られない前提に乗っています。
結局、上げた
Aは対象外、Bは入口が狭い。それでも上げました。
理由は単純で、上げない理由のほうが薄かったからです。
| 上げる | 上げない | |
|---|---|---|
| 作業量 | 修正版への更新1回 | 0 |
| 壊れる可能性 | 細かい版の更新なので低い | — |
| 放置した場合 | — | 入口が破られた時点で成立する |
大きな作り替えを伴う更新なら、慎重になる理由があります。今回は末尾の数字が1つ上がるだけの修正版で、その中身も「問題のある機能を止める」というものでした。
本番に入るものと、そうでないものを分けて数える
更新したあと、残りを確かめました。ここで1つ、見方を変えています。
そのまま数えると12件残っていました。ところが「本番に配るものだけ」で数え直すと0件です。
npm audit --omit=dev
found 0 vulnerabilities
残っていた12件は、全部開発中にしか動かない道具のものでした。型を生成するツールが使っている、さらにその中のライブラリです。
これらはビルドのときに自分のマシンで動くだけで、配信されるファイルには入りません。訪問者のブラウザにも、サーバーにも届きません。
0件にはしたいのですが、直すには型生成ツールを大きく更新する必要があり、そちらのほうが壊れる可能性が高い。今回は見送って、理由を書き残しました。
数字を0にすることが目的になると、危険度の低いもののために動いているものを壊します。分けて数えると、どちらを急ぐかが決まります。
やったことをまとめると
| 順番 | やったこと | かかった時間 |
|---|---|---|
| 1 | 4件が実は2種類だと確認 | 1分 |
| 2 | 条件を読んで、対象外を1つ外した | 2分 |
| 3 | 残りの入口の広さを設定で確かめた | 5分 |
| 4 | 更新して、型検査を通した | 5分 |
| 5 | 本番に入るものだけで数え直した | 1分 |
手を動かしたのは4だけで、残りは読んで確かめる時間でした。
今回の学び
まず、重大度は「最悪の場合」の話であって、自分への影響ではないということ。条件が書いてあるので、読めば当てはまるかどうかが分かります。今回は片方が最初から対象外でした。
次に、「入口が狭い」で止めないこと。狭いのは事実でしたが、狭さはこちらの設定に依存しています。設定を変えた日に広がるかもしれません。狭いことを理由に放置すると、その理由を覚えている人がいなくなった時点で危なくなります。
そして、数える範囲を先に決めること。全部で12件と、配信されるものの中で0件では、判断がまったく変わります。件数を減らすこと自体を目的にすると、動いているものを壊しにいくことになります。
この記事を共有する
関連記事

再試行は入れていた。それでもデプロイが落ちた
再試行の処理は、半年前から入れていました。CMSが500を返したら、少し待ってもう一度投げる。3回まで。 それでも、デプロイが落ちました。 この記事で扱うこと 再試行を入れてあったのに効かなかった話です。何が返ってきてい […]

ソース解説(5) 文字列を自分で組み立てる場所
このサイトで、HTMLを文字列として自分で組み立てている場所は2つあります。RSSフィードとサイトマップです。 画面に出るページは、部品を組み合わせれば勝手に出来上がります。この2つは違います。1行ずつ並べて、最後に文字 […]

測れるもので代用すると、測りたいものから離れる
記事を最後まで読んだ人を数えたいだけでしたが、判定を4回作り直しました。そのたびに別の壊れ方をした経緯と、そもそも何を測ろうとしていたのかを整理します。