In this article
CVEを読み解く:セキュリティリスクの評価と軽減に役立つ実践ガイド
概要
Common Vulnerabilities and Exposures(CVE)の世界を、ステップごとの例を交えながら見ていきましょう。CVEがプロジェクトに影響するかどうかを評価する方法と、効果的にリスクを軽減するための実践的な戦略を紹介します。このガイドを活用して、セキュリティ脆弱性に正面から取り組みましょう。CVEの警告を見過ごさず、自信を持って効率的に対処する方法を学びましょう。
本文
開発者は、複数のプロジェクトを同時に進めながら、バグ修正や機能改善の優先順位をつける日々を送っています。しかし、慌ただしい業務の中で、プロジェクトがCommon Vulnerabilities and Exposures(CVE)の影響を受ける可能性があるという不安な現実に直面することも少なくありません。報告されるCVEは日々増加しており、プロジェクトのセキュリティを維持するうえで大きな課題となっています。でもご安心ください。この問題に正面から取り組むための実践的な方法をご紹介します。この記事では、CVEのレビューを開発ワークフローにスムーズに組み込むための方法をいくつか解説し、自信を持ってプロジェクトを守れるよう支援します。
CVEを軽減する明確な戦略がなければ、毎日発見されるCVEの数に圧倒されてしまいます。特にNode.jsエコシステムでは、アプリケーションが多数の依存関係を持つことが一般的です。 Ulises Gascón『Node.js for Beginners』(第15章 Webアプリケーションのセキュリティ)
CVEをレビューする方法
例としてCVE-2020-8203を見てみましょう。このプロトタイプ汚染の脆弱性は、広く使われているライブラリLodashに影響します。
プロジェクトの脆弱性を通知するために使うツールによって、同じ情報を多かれ少なかれ含む、異なるレポートが見つかる場合があります。
ご覧のとおり、どちらの画像にも脆弱性に関する類似の情報(主にメタデータ)が記載されています。ただし、それぞれの説明には異なる情報が含まれており、分析を深めるのに役立ちます。

深刻度を確認する
まず、深刻度を使って作業の優先順位を決められます。これは、Common Vulnerability Scoring System(CVSS)に基づいて1から10までのスケールで評価されます。スコアの内訳から多くの背景情報を得ることもできます。複数の脆弱性をレビューする場合は、深刻度に基づいてパッチ適用の優先順位を決めることが重要です。
情報提供元によって、評価結果が異なる場合がある点に注意してください。下の画像では、Snykは8.2と評価していますが、Red HatとNVDは7.4と評価しています。

どのスコアリングシステムを使うかは重要ではありません。常に同じものを使うことが大切です。一般にクリティカルまたは高深刻度のCVEは優先的に確認し、深刻度が低いCVEにはもう少し時間をかけられます。
明確にしておきたいのは、CVEが公開されると世界中の人がその情報を知ることになるという点です。そのため、悪意ある攻撃者はこうした重大なCVEを簡単に攻撃手段に加え、システムを悪用する方法が見つかれば攻撃を始める可能性があります。
攻撃者がどれほど迅速に行動するかご存じない場合は、こちらのハニーポットレポートをご覧ください。
詳しい情報を集める
次に、脆弱性と悪用される可能性について、できる限り多くの情報を集めます。CVE公式レポートページの参考情報に含まれるリンクを確認すると、多くの情報を得られる場合があります。

今回のケースでは、HackerOneで元のレポートを読むこともでき、報告者とメンテナーのやり取りも確認できます。これらは多くの情報をもたらします。今回のケースでは、SnykとGitHubのレポートから有用な情報を見つけられます。
攻撃対象となる範囲を明確にする
前の手順から、影響を受けるライブラリのバージョン(>=4.1.0 <4.17.20)と、この脆弱性の対象となるメソッド(pick、set、setWith、update、updateWith、zipObjectDeep)を確認できているはずです。また、プロトタイプ汚染の脆弱性であることも忘れないでください。
リポジトリを簡単に確認し、影響を受けているかどうかを評価できます。特定のバージョンでこれらのメソッドを使っていなければ、コードは影響を受けておらず、今回のケースは誤検知だと判断できます。
この評価は、想像以上に複雑な場合があります。JavaScriptエコシステムでは、リンター、テストフレームワーク、各種ユーティリティなど、本番環境に実質的な影響を与えない開発ツールのdevDependenciesがよく使われています。これはプロジェクトの性質によって大きく異なります。たとえば、ローカルではPM2を使って本番環境をシミュレートし、本番環境へのデプロイにはコンテナとKubernetesを使うことがあります。この場合、ほとんどのケースで完全に管理できる「安全な環境」でPM2を実行するため、PM2に関連するCVEの攻撃経路は大幅に制限されます。ただし、パッケージが本番環境で使われていなくても、侵害されれば開発環境に悪影響を及ぼす可能性があります。たとえば、2018年にeslintが侵害された事例があります。
プロジェクトがこの脆弱性の影響を受けないと明確に説明できない場合は、影響を受ける可能性があると判断し、有効な軽減策が必要だと考えるのが安全です。
説明に記載された情報に加えて、攻撃手法をより深く理解するには、CVEで言及されているCommon Weakness Enumeration(CWE)を確認するのも効果的です。

今回言及されているCWEは、CWE-1321:オブジェクトのプロトタイプ属性の不適切な制御(「プロトタイプ汚染」)です。これを手がかりに、類似のCVEを見つけたり、どのように悪用されるのかを理解したりできます。
簡単に言うと、CWEは考えられる弱点のリストであり、CVEは実際に発見された脆弱性のリストです。 Ulises Gascón『Node.js for Beginners』
悪用可能なPOCはあるか
報告されたすべてのCVEに、簡単に悪用できる概念実証(POC)が含まれているわけではありません。しかし、今回のケースには明確なPOCがあります。
const _ = require('lodash');
_.zipObjectDeep(['__proto__.z'],[123]);
console.log(z); // 123
問題をより深く理解するだけでなく、これを使ってコードをテストし、脆弱性があることを確認できます。ここでは、アプリケーションに次のコードがあるとします。
const _ = require('lodash');
const normalizeData = (users, ages) => {
return _.zipObjectDeep(users,ages);
}
module.exports = { normalizeData }
プロトタイプ汚染攻撃に対して脆弱だとすぐに判断できますが、将来同じ脆弱性が発生しないようにする仕組みがあるか、テストを作成して確認することもできます。ライブラリの更新によって脆弱性が再発することもあります。次のような簡単なテストを追加すれば、そうした事態を防げます。
const { normalizeData } = require('../utils');
describe('normalizeData behaviour', () => {
it('should return the data structured properly', () => {
const users = ['John', 'Doe'];
const ages = [30, 40];
const result = normalizeData(users, ages);
expect(result).toEqual({ John: 30, Doe: 40 });
});
it('should not be affected by a prototype pollution (CVE-2020-8203)', () => {
normalizeData(['__proto__.z'], [123]);
expect(global.z).toBe(undefined);
});
});
現時点ではプロトタイプ汚染に対して脆弱であり、軽減のためにプロジェクトを変更していないため、このテストは当然失敗します。

図:Node.js@21およびlodash@4.17.0を使用
それでは、この脆弱性を軽減する方法を見ていきましょう。
CVEを軽減する戦略
CVEを軽減する方法はいくつかあります。ここでは、必要な労力が少ない順にいくつか紹介します。
バージョンをアップグレードまたはダウングレードする
CVEを軽減する最も推奨される方法は、影響を受けるライブラリをアップグレードすることです。今回のケースでは、lodash@4.17.20以降にアップグレードできます。ライブラリのメンテナーが脆弱性の修正を提供し、報告者とともにテスト済みであるため、多くの場合これが最善の方法です。つまり、パッチが機能することを確認できます。
このパッチ(npm i lodash@4.17.20)を適用したら、先ほど作成したテストを再実行して、脆弱性が軽減されたことを確認できます。

図:Node.js@21およびlodash@4.17.21を使用
簡単そうに見えますが、非常に複雑になる場合があります。新しいライブラリのバージョンと互換性のない他の依存関係もアップグレードまたはダウングレードする必要があったり、新しいバージョンにコードと互換性のない変更が含まれていたりする可能性があります。
また、パッチがリリースされる前にCVEが公開された場合は、正式なパッチを待つ間に脆弱性を軽減するため、別の方法を検討する必要があります。
移行する
ライブラリのメンテナンスが終了している場合や、メンテナーが脆弱性のパッチを提供していない場合は、同等または類似の機能を持つ別のライブラリに移行する必要があるかもしれません。大きな作業ではありますが、新しいライブラリのほうが安全で、適切にメンテナンスされているなら、価値のある投資となるでしょう。
そうすれば、今後レビューが必要なCVEを減らし、アップグレードもより簡単に管理できます。
手動でパッチを適用する(DIY)
ライブラリをアップグレードできず、別のライブラリにも移行できない場合は、自分でパッチを適用する必要があるかもしれません。CVEがパッチのリリース前に公開され、正式なパッチを待つ間にできるだけ早く脆弱性を軽減する必要がある場合にも、有効な方法です。
手動でパッチを適用するには、ライブラリと脆弱性について十分に理解している必要があります。CVEレポートに含まれるPOCを参考に独自のパッチを作成し、作成したテストを使ってパッチが機能することを確認できます。それでは、この脆弱性に対するパッチを作成してみましょう。
const _ = require('lodash');
const normalizeData = (users, ages) => {
const safeUsers = users.filter(user => !/__proto__|prototype|constructor/.test(user));
return _.zipObjectDeep(safeUsers, ages);
}
module.exports = { normalizeData }
これは脆弱性の軽減方法を示す簡単なパッチですが、テストケースは非常に基本的なものなので、コンプライアンスを確保するには公式パッチを確認してください。テストを再実行して、脆弱性が軽減されたことを確認できます。

図:Node.js@21、lodash@4.17.0およびパッチを使用
CVEに関する判断を記録する方法
チームで作業する場合は、CVEに関する判断を記録することが重要です。これにより、軽減済みの脆弱性と、まだ対応が必要な脆弱性を追跡できます。作業の優先順位を決め、脆弱性を見落とさないためにも役立ちます。
レビュー対象のCVE、深刻度、ステータス、軽減策、脆弱性を軽減した日付を簡単な表に記録できます。これにより、軽減済みの脆弱性と、まだ対応が必要な脆弱性を追跡できます。
CVE | 深刻度 | ステータス | 軽減策 | 日付 |
CVE-2020-8203 | 8.2 | 軽減済み | lodash@4.17.21にアップグレード | 2024-04-15 |
この表はCVEをレビューするたびに更新し、チームで共有できます。これにより、現在直面している脆弱性と、軽減済みの脆弱性を全員が把握できます。
さらに、この表をプロジェクトのドキュメントに含めれば、コードの配布プロセスの一部として共有できます。
CVEのレビューにツールを使っている場合、誤検知と判断したCVEや手動でパッチを適用したCVEのステータスを明確にするのが難しいことがあります。たとえばSnykでは、CVEを無視対象としてマークできます。
今回のケースでは、手動でパッチを適用した場合、次のようにSnykレポートでCVEを無視対象としてマークできます。snyk ignore --id=SNYK-JS-LODASH-567746 --reason="manual patch in place with tests"。
これにより、.snykファイルに次の内容が生成されます。
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.25.0
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
ignore:
SNYK-JS-LODASH-567746:
- '*':
reason: manual patch in place with tests
expires: 2024-05-15T17:27:20.339Z
created: 2024-04-15T17:27:20.340Z
patch: {}
このプロセスには手作業が必要ですが、偏りや人的ミスをできるだけ避けるため、チームで取り組むことを強くおすすめします。
チェックリスト
CVEをレビューする際に必要な手順をまとめると、次のとおりです。
CVEの深刻度を確認する
攻撃ベクトルについて詳しい情報を集める
攻撃対象となる範囲を明確にする
悪用可能なPoCがあるか確認する
プロジェクトへの影響を評価する
必要に応じて脆弱性を緩和する
CVEに関する判断内容を記録する
結論をチームと共有する
その他の参考情報
Node.jsのセキュリティについてさらに詳しく知りたい方は、書籍Node.js for Beginnersをご覧ください。Node.jsのセキュリティに関するさまざまなトピックを取り上げています。