utsuroi graphql labs知識ベース 01 / 01
GraphQL · Knowledge Base
01

GraphQL の脆弱性

When the schema becomes the attack surface

GraphQL は柔軟で強力ですが、REST と前提が違うため新しい攻撃面が生まれます。Introspection、ネスト深さ、認可不備、バッチクエリなど、REST API では考えなくてよかった問題が出てきます。

1. GraphQL とは #what-is

クライアントが「欲しいフィールドだけ」を1つのエンドポイントで指定して取得できる API スキーム。REST のように「リソースごとにエンドポイント」ではなく、単一エンドポイント(/graphql)に対してクエリ言語でリクエストする。

POST /graphql
{
  "query": "{ user(id: 123) { name email orders { product price } } }"
}

2. Introspection の漏洩 #introspection

スキーマ全体が公開されると、隠しフィールドや管理者専用 mutation が露出する。

GraphQL には「自分自身のスキーマを問い合わせる」Introspection 機能がある。本番環境で有効のままだと、API のすべてのフィールド・mutation・引数が攻撃者に公開されてしまう。

// Introspection クエリで全スキーマを取得
{
  __schema {
    types {
      name
      fields { name type { name } }
    }
    queryType { fields { name } }
    mutationType { fields { name args { name } } }
  }
}

// 隠していたつもりの管理機能が露出:
// - deleteAllUsers(confirm: String): Boolean
// - exportCustomerData(format: String): String
// - resetUserPassword(userId: ID!): String

3. 認可がフィールド単位で必要 #authz

REST だとエンドポイント単位で認可をかければよかった。GraphQL では1つのクエリで複数の型・フィールドが取得されるため、各フィールドで認可チェックが必要。

// 一見無害なクエリ
{
  product(id: 1) {
    name
    price
    supplier {        ← 一般会員に見せていいか?
      contractPrice    ← 卸売原価(社外秘)
      contactEmail
    }
  }
}

resolver でフィールドごとに認可チェックを実装する必要がある。

4. DoS — ネスト深さとバッチクエリ #dos

5. 正しい防御 #defense

  1. 本番環境では Introspection を無効化 — Apollo Server なら introspection: false
  2. resolver でフィールド単位の認可 — フィールドリゾルバの先頭で context.user の権限チェック
  3. クエリ複雑度・深さ制限graphql-depth-limitgraphql-cost-analysis
  4. レート制限 — リクエスト数に加えて、複雑度ベースの制限を併用
  5. 許可リスト方式の API — 既知のクエリだけを受け付ける(persisted queries)。柔軟性は失われるが攻撃面は最小化