ロックファイルに脆弱性が隠れていないか?
ロックファイルは、リポジトリの中で最も正直な文書です。ビルドが実際にインストールするすべてのパッケージの正確なバージョンを、範囲も「latest」もなく、驚きもなく列挙します。同時にそれは、静かに攻撃対象面の一覧でもあります。固定されたバージョンはすべて、固定してから誰かが穴を見つけたかもしれないバージョンです。この記事では、スキャナーがその一覧をどのように答えに変えるかを説明します。OSVデータベースがバージョンを既知の脆弱性と照合する仕組み、CISA KEVフラグがCVSSスコアより重要な理由、そして「脆弱性は見つかりませんでした」がデータベースについての記述であって、コードについての記述ではない理由です。チェックはSBOM脆弱性スキャナーで自分で実行できます。ロックファイルをブラウザ内で解析し、アップロードすることはありません。
ロックファイルが固定するもの — そしてそれが攻撃対象面である理由
現代のソフトウェアは、ほとんどが他人のコードでできています。典型的なウェブアプリケーションは数百のパッケージを取り込み、それぞれがさらに多くのパッケージを取り込みます。ウェブフレームワークへの直接の依存関係が、選んだことも見たこともない200もの推移的なパッケージになることは珍しくありません。ロックファイルはそのツリーの記録です。npmのpackage-lock.json、完全に固定されたPythonのrequirements.txt、Goのgo.modとgo.sumがあり、どのマシンでも毎回同じツリーがインストールされるように書き留められています。これがなければ、「自分のマシンでは動く」は事実ではなく希望です。
その同じ正確さこそが、ロックファイルを既知の脆弱性を探すのに適した場所にする理由です。脆弱性データベースは、パッケージ名、エコシステム、正確なバージョンをキーとしています。lodash 4.17.20は脆弱で、lodash 4.17.21は脆弱ではなく、その違いは1回の照会でわかります。変動する範囲はそのようには判定できません。スキャナーは実際にインストールされたバージョンを推測しなければならず、推測はセキュリティの実践ではありません。したがってロックファイル衛生の第一のルールは、地味ですが譲れません。ロックファイルをコミットし、固定したままにし、マニフェストの希望リストではなく、固定されたツリーをスキャンすることです。
OSVがバージョンを既知の脆弱性と照合する仕組み
OSV.devは、GitHub、NVD、各言語エコシステムからのアドバイザリを1つの照会可能なインデックスに集約した公開の脆弱性データベースです。スキャナーは名前、エコシステム、バージョンの3つ組のバッチ(lodash、npm、4.17.20)を送信し、その正確なバージョンに影響する脆弱性IDを受け取ります。IDはその後すべて取得されます。概要、修正バージョン付きの影響範囲、CVSSベクトルとしての深刻度、CVEエイリアスです。照会にプロジェクトの情報は一切含まれません。公開パッケージの公開座標だけです。
照合は意図的に単純で、それが強みです。コードを読むこともなく、脆弱な関数を呼び出しているかどうかを判断することもなく、パッケージの使われ方も気にしません。アドバイザリが4.17.21より前のバージョンが影響を受けると言い、あなたが4.17.20を固定していれば、一致します。たとえコードが脆弱な関数にまったく触れていなくてもです。その過剰な報告は機能です。到達可能性の分析は、より難しく、より遅い問いであり、安価な問い(「このバージョンは既知の不良か?」)だけでも、3年前にアップグレードし忘れた依存関係を捕まえることができます。
スキャナーが見えないもの
バージョン照合のチェックには明確な境界があり、はっきり述べておく価値があります。見つけられるのは、誰かがすでに報告した脆弱性で、データベースが知っているバージョンに対するものだけです。昨日発見され、まだ公開されていない脆弱性は見えません。脆弱ではなかったが悪意のあるパッケージ(人気のある名前のタイポスクワット、侵害されたリリース、外部に通信する依存関係)にはCVEがまったくなく、この種のスキャナーはそれを検出しません。自分のコードも同じです。スキャナーはインストールするパッケージを判定するのであって、あなたが書くコードを判定するのではありません。
だからこそ、「脆弱性は見つかりませんでした」という結果は慎重に読む必要があります。その意味は、これらの正確なバージョンについて、このデータベースにおいて、この時点で、何も記録されていないということです。有用な否定です。新鮮なロックファイルのクリーンなスキャンは本当に良い知らせです。しかし証明書ではありません。スキャナーが見えないものへの防御は別のツールです。新しい依存関係がツリーに入る前に固定して見直すこと、新しいアドバイザリを四半期ごとではなく継続的に注視すること、静かだったパッケージの突然の新リリースを疑うことです。スキャナーは煙探知機であり、消防隊ではありません。
KEVフラグがCVSSスコアの高さより重要な理由
CVSSスコアは仮定の問いに答えます。この脆弱性が悪用されたらどれほど深刻か?CISAの既知の悪用された脆弱性(Known Exploited Vulnerabilities)カタログは事実に答えます。現実の世界で、今この瞬間に、誰かが悪用しているか?この2つの答えは日常的に食い違います。9.8と評価されても誰も武器化していない脆弱性は、次の計画的なアップグレードまで待てます。一方で5.3と評価され、KEVリストに載り、CISAが設定した修正期限のある脆弱性は今週中にパッチを当てるべきです。エクスプロイトはすでに存在し、すでに使われているからです。
その逆転こそが、スキャナーがKEVの行を深刻度の並び替えに組み込むのではなく、別にバッジ表示する理由です。並び順は、何が悪いかを教えます。KEVバッジは、何が緊急かを教えます。実際のトリアージは短いものです。まずKEVの項目にパッチを当て、次に公開された修正バージョンのあるクリティカルなものにパッチを当て、残りは次のアップグレードサイクルに組み込みます。当サイトのBulk CVE Triageでは、同じCVEを悪用の文脈に置きます。1つのバッジだけではチームに作業を正当化できない場合に、KEVステータス、EPSS確率、CVSSを合わせて示します。
ロックファイルをコミットし、CIでスキャンする — 四半期に1回ではなく
コミットされていないロックファイルは、存在しないロックファイルです。1人の開発者のマシンにしか存在しなければ、他のすべてのマシンはその日に「latest」が解決するものをインストールし、スキャンしたツリーは出荷するツリーではありません。コミットすることは、最も安価なサプライチェーン管理です。インストールを再現可能にし、スキャナーの答えを全員にとって真実にし、依存関係のアップグレードを静かなドリフトではなく、レビュー可能な差分に変えます。
後半は頻度です。脆弱性は毎日公開されるため、四半期に1回のスキャンは数日で古くなるスナップショットです。有用な形は、ロックファイルに触れるすべてのプルリクエストでのスキャンに加え、メインブランチの定期スキャンです。テストスイートと同じループで、同じ性質を持ちます。新しいアドバイザリが出たその日にグリーンのビルドがレッドになり、そのとき修正はバージョンの上げ下げであって、事後の検証ではありません。当サイトのブラウザスキャナーは、そのループの手動版です。dependabotの更新後やリリース前にロックファイルを貼り付け、出荷しようとしているツリーを確認してください。
修正バージョン、破壊的変更、パッチと固定の使い分け
スキャンのすべての一致には、修正バージョン(脆弱性がなくなった最初のリリース)が付くか、「公開なし」という正直な表示が付きます。一般的なケースはパッチまたはマイナーリリースで、アップグレードはロックファイルの1行の変更です。しかし修正バージョンが常に安全に採用できるバージョンとは限りません。メジャーバージョンのジャンプは、コードが依存している動作を変える可能性があり、昨日リリースされた修正は実世界でのテストが1日しか経っていません。判断は他の依存関係のアップグレードと同じです。変更履歴を読み、テストを実行し、実際にインポートしているパッケージから先にアップグレードします。
修正バージョンがない場合、選択肢は3つに絞られます。必要がなければ依存関係を削除する、保守されている代替品に置き換える、または脆弱なコードパスを緩和する(信頼できない入力を影響を受ける関数に渡さない、またはアドバイザリが説明するチェックの背後に呼び出しを置く)ことです。そしてアドバイザリの更新を注視します。してはいけないのは、脆弱なバージョンを固定して忘れることです。固定は決定であり、ロックファイルや課題トラッカーに理由と見直す日付をコメントとして残す価値があります。変更後は再スキャンしてください。スキャナーは数秒で終わります。2回目のスキャンだけが、1回目のスキャンの結果がなくなったことの唯一の証明です。