GraphQL APIで「一部だけの個人情報露出」が重大インシデントになり得る理由
はじめに
GraphQLは柔軟なAPI設計が可能である一方で、
「本来返却されるべきではないフィールド」が露出しやすい構造でもある。
今回は、バグバウンティ調査中に遭遇した「firstNameのみが完全表示されていたケース」をベースに、
なぜ“たった一部の情報”でもセキュリティリスクになり得るのかを解説する。
※ 本記事は実際の調査経験を元に一般化・匿名化した内容である。
対象サービス名・ユーザー情報・識別子等はすべてマスクしている。
発見した問題
あるGraphQL APIにおいて、本来はイニシャル表示のみを想定していたユーザー情報が、
APIレスポンス上では完全な firstName として返却されていました。
想定されていた表示
{
"firstName": "H.",
"lastName": "D."
}
実際のレスポンス
{
"firstName": "Hogehoge",
"lastName": "D."
}
lastName側はマスクされていた一方で、firstNameのみ完全表示されていたため、
privacy masking が片側だけ実装されている状態でした。
なぜ危険なのか?
一見すると、
「firstNameだけなら大したことないのでは?」
と思われるかもしれない。
しかし実際の攻撃では、“単体情報”ではなく“情報の相関”が重要になる。
今回のケースでは、以下のような情報を組み合わせることが可能であった。
- firstName
- ユーザーUUID
- avatar URL
- レビュー履歴
- 投稿店舗情報
これにより、同一人物のレビュー活動を追跡できる状態となっていた。
攻撃者視点で見るとどうなるか
1. 同一人物の追跡
UUIDが固定で返却されていたため、複数レビューを横断して同一人物を追跡可能であった。
例:
- 東京の高級レストラン
- 横浜のバー
- 大阪旅行時の店舗
などを時系列で紐付け可能になります。
2. 実在人物の特定
avatar画像やレビュー内容を組み合わせることで、SNSアカウントやGoogleプロフィールと関連付けられる可能性がある。
特に:
- ニックネーム
- 顔写真
- 行動パターン
- 利用地域
が重なると、現実の人物特定に繋がるケースがあります。
3. ソーシャルエンジニアリング
以下のような情報が揃うと、標的型攻撃の材料になります。
- よく利用する地域
- 趣味嗜好
- 食事傾向
- 活動時間帯
つまり、単なる「名前漏洩」ではなく、“ユーザープロファイリング”が可能となる。
なぜGraphQLで起きやすいのか
GraphQLでは、クライアント側が必要フィールドを自由に指定できます。
例えば:
query {
reviews {
reviewer {
id
firstName
lastName
}
}
}
この柔軟性は便利である一方、サーバー側で適切なフィールド制御を行わない場合、
「フロントでは非表示想定だった情報」がAPI経由で露出する。
よくある実装ミス
フロントエンド側のみでマスク
const masked = user.firstName[0] + ".";
この場合、API自体はフルデータを返却しているため、
GraphQL ExplorerやDevToolsから容易に確認可能となる。
どうすれば防げるのか
今回のような問題を防ぐためには、フロントエンド側ではなく、APIレベルでの制御が必要となる。
1. APIレスポンス側でマスキングを行う
フロントエンド側のみでマスキングを実施すると、GraphQL API自体からはフルデータが取得できてしまう。
そのため、以下のようにサーバー側で加工済みデータを返却するべきである。
{
"firstName": "H.",
"lastName": "D."
}
2. 不要なフィールドを返却しない
GraphQLでは「取得可能であること」自体がリスクになる。
そのため:
- avatar URL
- internal UUID
- tracking可能な識別子
- internal metadata
などは、本当に必要な場合のみ返却すべきである。
3. Field-level authorization を導入する
GraphQLでは、Query単位だけでなくField単位での認可制御が重要となる。
例えば:
- 未認証ユーザー
- 一般ユーザー
- 管理者
で取得可能フィールドを分離する必要がある。
4. 「組み合わせ」を前提に設計する
セキュリティレビューでは、単体データだけではなく、
「他データと組み合わせた時に何が起こるか」を考慮する必要がある。
特に:
- UUID
- avatar
- 行動履歴
- location情報
などは相関分析されやすい。
privacy-by-designの観点からも、最小限の情報返却を徹底すべきである。
実際の評価で印象的だったこと
今回興味深かったのは、露出していた情報自体は一見すると限定的に見えた点である。
{
"firstName": "Hogehoge",
"lastName": "D."
}
しかし実際には、
- UUID
- avatar URL
- review履歴
- restaurant情報
などと組み合わせることで、ユーザーの行動追跡や実在人物との関連付けが可能な状態となっていた。
結果として、この問題は実際のバグバウンティプログラム上で高い優先度(P2)として扱われた。
ここで重要だったのは、
「漏えいしている情報の量」
ではなく、
「その情報が攻撃者にどう利用されるか」
である。
セキュリティレビューでは、
単体では些細に見える情報でも、
相関分析やOSINTと組み合わせることで重大インシデントにつながるケースが存在する。
学んだこと
今回のケースで重要だったのは、
「単体では弱く見える情報でも、組み合わせると高リスクになる」という点である。
セキュリティでは、
- 情報の量
よりも、
- 情報同士の関連性
- 攻撃者がどう利用できるか
の方が重要になるケースがある。
特にGraphQLでは、
- 過剰なフィールド返却
- hidden field exposure
- フロント依存のマスキング
が発生しやすいため注意が必要である。
まとめ
今回のような問題を防ぐためには、フロントエンド側ではなく、APIレベルでの制御が必要となる。
1. APIレスポンス側でマスキングを行う
フロントエンド側のみでマスキングを実施すると、GraphQL API自体からはフルデータが取得できてしまう。
そのため、以下のようにサーバー側で加工済みデータを返却するべきである。
{
"firstName": "H.",
"lastName": "D."
}
2. 不要なフィールドを返却しない
GraphQLでは「取得可能であること」自体がリスクになる。
そのため:
- avatar URL
- internal UUID
- tracking可能な識別子
- internal metadata
などは、本当に必要な場合のみ返却すべきである。
3. Field-level authorization を導入する
GraphQLでは、Query単位だけでなくField単位での認可制御が重要となる。
例えば:
- 未認証ユーザー
- 一般ユーザー
- 管理者
で取得可能フィールドを分離する必要がある。
4. 「組み合わせ」を前提に設計する
セキュリティレビューでは、単体データだけではなく、
「他データと組み合わせた時に何が起こるか」を考慮する必要がある。
特に:
- UUID
- avatar
- 行動履歴
- location情報
などは相関分析されやすい。
privacy-by-designの観点からも、最小限の情報返却を徹底すべきである。
まとめ
今回のケースでは:
- firstName
- UUID
- avatar
- review history
を組み合わせることで、単なる表示ミスではなく、
「個人追跡・プロファイリング」に繋がるリスクが存在していました。
GraphQLを利用する場合は、- APIレベルでのマスキング
- 必要最小限のフィールド返却
- フィールド単位の認可制御
- privacy-by-design
を徹底することが重要である。
おわりに
バグバウンティを始めた当初は、
「Criticalな脆弱性を見つけないと意味がない」と考えていました。
しかし実際には、
- 情報をどう組み合わせるか
- 攻撃者視点でどう見えるか
- その情報が何に悪用されるか
を考えることの方が、はるかに重要であると感じた。
特にGraphQLは、
「便利さ」と「情報露出」が非常に近い場所に存在するため、
今後も継続的に研究していきたい分野である。