JSONの最大ネスト深度
各種パーサーやプラットフォームにおけるJSONのネスト深度制限を理解しましょう。深くネストされたJSONがスタックオーバーフローを引き起こす理由と安全な深度制限の設定方法を解説します。
詳細な説明
JSON仕様(RFC 8259)は、objectやarrayの最大ネスト深度を定義していません。理論上、JSONドキュメントは数千レベルの深さにネストできます。しかし実際には、すべてのパーサーには利用可能なスタックメモリによって決まる制限があり、深くネストされたJSONはスタックオーバーフローエラー、サービス拒否攻撃の脆弱性、パフォーマンスの低下を引き起こす可能性があります。
深度が重要な理由:
ほとんどのJSONパーサーは再帰下降パースを使用しており、ネストの各レベルがコールスタックにフレームを追加します。10,000レベルの深さにネストされたドキュメントは10,000個のスタックフレームを必要とし、ほとんどの環境のデフォルトスタックサイズを超えます。JavaScriptでは、V8エンジンは通常10,000〜15,000レベルの再帰を許可した後、RangeError: Maximum call stack size exceeded をスローします。Pythonではデフォルトの再帰制限が1,000であり、深くネストされた入力で json.loads() がこれに達します。
パーサー固有の制限:
- JavaScript(V8): スタックオーバーフローまで約10,000レベル
- Python(jsonモジュール): 約1,000レベル(デフォルトの再帰制限、
sys.setrecursionlimit()で調整可能) - Java(Jackson): 設定可能、デフォルト1,000(
StreamReadConstraints) - Go(encoding/json): 約10,000レベル(動的に成長するgoroutineスタックによる制限)
- PHP(json_decode): 設定可能な
depthパラメータ、デフォルト512 - .NET(System.Text.Json): 設定可能な
MaxDepth、デフォルト64
セキュリティへの影響:
深くネストされたJSONは典型的なサービス拒否攻撃のベクターです。攻撃者は [[[[[[[...(開き括弧のみ)のような小さなペイロードを送信し、サーバーのJSONパーサーのスタックを枯渇させてクラッシュさせることができます。これは「billion laughs」または「ネストボム」攻撃として知られています。WebアプリケーションファイアウォールやAPIゲートウェイは、受信JSONペイロードに深度制限を強制すべきです。
深くネストされたJSONの生成原因:
深くネストされた構造は通常、設計上の選択ではなくバグから生じます。自身を参照する再帰的なデータ構造、無制限のツリー走査、シリアライズのループなどが、予期せず深いJSONを生成する可能性があります。正当なケース(ファイルシステムツリーや深くネストされた組織階層のシリアライズなど)はまれですが、そのような場合は参照IDによる構造のフラット化を検討してください。
開発者がよくやるミス:
最も重大なミスは、信頼できないJSONをパースする際に明示的な深度制限を設定しないことです。デフォルトのパーサー設定を使用すると、サービスがスタックオーバーフロー攻撃に対して脆弱なままになります。もうひとつのミスは、シリアライズ中に深くネストされたデータを暗黙に切り詰めることで、エラーなしに出力を破損させる可能性があります。また、深度問題の「修正」としてスタックサイズや再帰制限を増やす開発者もいますが、これは根本的な問題に対処せずに上限を引き上げるだけです。
ベストプラクティス:
明示的な最大深度制限(ほとんどのアプリケーションでは32または64が適切)を設定し、それを超える入力を明確なエラーメッセージで拒否してください。高セキュリティのコンテキストでは反復型(非再帰型)パーサーを使用してください。不要なネストを避けるようデータモデルを設計し、深度が4〜5レベルを超える場合は参照IDを使用して階層をフラット化しましょう。拒否されたペイロードを監視・ログに記録し、潜在的な攻撃を検出してください。
ユースケース
Web APIのJSONパーサーに最大深度32レベルを設定し、悪意を持って作成されたネストペイロードによるスタックオーバーフローのサービス拒否攻撃を防止する。