GraphQL のセキュリティ課題

GraphQL は、REST の代替として使用できるオープンソースのクエリ言語です。急速な進化を続ける最先端 API のニーズに応える柔軟性を実現する GraphQL は、現在多くの開発者によって着々と導入されています。GraphQL には、必要なデータをピンポイントでリクエストしたり、継続的な API の進化を容易にするといったメリットがあります。

このように多くのメリットがある GraphQL ですが、攻撃者によって悪用される可能性のある機能や、クエリの柔軟性によって意図せず引き起こされてしまう問題、攻撃者にとって恰好のターゲットとなる脆弱性など、その使用に伴うセキュリティの課題についてはまだよく知られていません。

今回は、これらの課題を探るとともに、GraphQL のより安全な導入を可能にするデフォルトやコントロール設定を、リスクを伴う設定、悪意のあるクエリ、そして Web API の脆弱性の3つのカテゴリーに分けてご紹介します。では、さっそく見ていきましょう。

リスクを伴う設定

GraphQL には、イントロスペクション、フィールドの提案、デバッグモードといった、攻撃者によって悪用される可能性のある正規の機能があります。悪用を防ぐためにも、これらの機能の使用には注意が必要です。

イントロスペクション

課題 : GraphQL にはイントロスペクションと呼ばれる機能があります。つまり、引数、フィールド、型、説明、型の非推奨ステータスなど、データ構造に関する詳細情報を取得するために GraphQL スキーマをクエリすることができます。このような情報が漏えいしてしまうと、攻撃対象領域が拡大し、攻撃者にとって恰好の的となる脆弱性を提供してしまう可能性があります。

例えば、スキーマの全ての型と各型の詳細を取得したい場合、以下のようにイントロスペクションクエリを実行します。

{
__schema {
types {
name
kind
description
fields {
name
}
}
}
}

また、GraphQL の統合開発環境 (IDE) である GraphiQL (i に注意) では、ユーザーフレンドリーなインターフェイスを通じてフィールドやインプットをクリックしてクエリを構築することが可能です。GraphiQL を通じてサポートされているスキーマの情報が得られるため、存在するクエリ型やミューテーション型などがさらけ出され、さらなる攻撃対象領域がリスクにさらされてしまう場合があります。

このようにクエリを送信してサーバーを尋問することで、攻撃者は深刻度の高い複雑な攻撃を仕掛けるのに必要な情報を取得することができます。例えば、イントロスペクションを通じて UploadFile というオブジェクトを見つけたとしましょう。

{
"name": "UploadFile",
"kind": "OBJECT",
"description": null,
"fields": [
{
"name": "content"
},
{
"name": "filename"
},
{
"name": "result"
}
]
}

このオブジェクトを悪用して、Web ルートフォルダ外に保管されているファイルやディレクトリにアクセス・変更するトラバーサル攻撃という攻撃を仕掛けることができます。では、filename の引数には文字列の制限はなく、サーバーのファイルシステム内の全てのロケーションへの記述が可能であるという想定で、poc.php というファイルを記述する目的でクエリを構築してみましょう。

mutation {
uploadFile(filename:”../../../../var/www/html/app/poc/poc.php”, content: “<?php
phpinfo(); ?>”){
result
}
}

攻撃ベクトルを通じてコールを行うと、任意のコードが実行されたことが分かります。ここから、リバースシェルを利用して基盤となるサーバー環境との通信を試みることができます。

解決法 : イントロスペクションは開発においては便利ですが、保護されている機密情報へのアクセスを提供する場合は避けた方が良いでしょう。

ユーザーに API をクエリする方法を教えるためにイントロスペクションを使いたくなるかもしれませんが、readthedocs などの別のドキュメントを使用する方が安全です。イントロスペクションを無効にすることで脆弱性が修正されるわけではありませんが、攻撃者に対して攻撃の近道を与えることを避けることができます。

GraphQL を導入する際、デフォルトでイントロスペクションが有効になっていることがよくありますが、システム全体でイントロスペクションを無効にすることが最も安全なアプローチです。幸い、RubyNodeJSJavaPythonPHP などの人気のフレームワークやプログラミング言語においてイントロスペクションを制限する方法に関する便利なリソースも多く存在します。また、Nuclei が提供するテンプレートを使ってご利用の GraphQL のイントロスペクションをテストすることもできます。

フィールド提案機能

課題 : イントロスペクションが無効な場合、攻撃者はフィールド提案機能を利用して GraphQL スキーマにブルートフォース攻撃を仕掛ける可能性があります。フィールド提案機能は、誤ったフィールド名が入力されたクエリに対するエラーレスポンスで、似た名前を持つフィールドを公開する仕組みになっています。

例えば、name (query { name }) のクエリに対して、次のようなレスポンスが返されます。

{
"errors": [
{
"message": "Cannot query field \"name\" on type \"Query\". Did you mean \"node\"?",
"locations": [
{
"line": 2,
"column": 3
}
]
}
]
}

解決法 : フィールド提案機能は、開発者が API を GraphQL と統合する、またはパブリック API に対して統合する際に役に立つ便利な機能ですが、使用には十分注意が必要です。保護された機密情報へのアクセスを提供する環境においては、この機能を無効にすることをおすすめします。

過剰なエラー

課題 : エラー通知は、問題が発生した際にインサイトを提供する便利な機能です。しかし、エラーが適切に処理されない場合、GraphQL においてさまざまなセキュリティ問題が引き起こされることがあります。

GraphQL は、デバッグモードで実装することが可能です。デバッグモードの主要機能は、開発に役立つ詳細なリクエストエラーを表示することです。このデバッグモードを有効にしたまま本番環境で GraphQL の実装を行うのは非常に危険です。この場合、スタックトレースなどの過剰なエラー通知が発生し、レスポンスにて機密情報が公開されてしまい、セキュリティ以外にもコンプライアンスの問題が生じてしまいます。

スタックトレースの例

graphql error

解決法 : 本番環境ではデバッグモードが無効になっていることを確認し、クライアントに情報が返される前スタックトレースを除外するようにしましょう。

スタックトレースをログしたい場合は、ユーザーに返さず、開発者のみアクセス可能にする方法がいくつかあります。例えば、リクエストの検査・変更を通じてエラーのマスキングや編集を可能にするミドルウェアの導入は、エラーをより効果的にコントロールする方法の一つです。

悪意のあるクエリ

攻撃者は、悪意のある GraphQL クエリを作成し、DoS 攻撃、ブルートフォース攻撃、列挙攻撃などを仕掛けることができます。

クエリの深さと複雑性

課題 : GraphQL の各クエリの深さは、ネストされたフィールド数およびネストされたフィールド内のオブジェクト数で表されます。攻撃者は、普通のリクエストを送信する代わりに、容易に指数関数的に複雑化するクエリを作成し、システムに過負担をかけることで DoS を引き起こさせます。またこの問題は、クエリの適切な作成方法を知らない正当なユーザーによって引き起こされてしまうこともあります。

GraphQL で型同士が参照し合う場合、指数関数的に拡大す循環クエリが発生し、それによってリソースが拘束され、サーバーがダウンする可能性があります。

例えば、GraphQL の実装に次のように定義された相互関係があるとします。

type Blog {
comments(first: Int, after: String)
}
Type Comment {
blog: Comment
}
Type Query {
blog(id: ID!): Blog
}

この場合、ブログのコメントと、コメントのブログ両方をクエリできるようになっています。攻撃者はこれを利用して、読み込まれるオブジェクト数を指数関数的に増加させる、次のような高コストのネスト化されたクエリを作成することができます。

query nefariousQuery {
blog(id: “some-id”) {
comments(first: 9999) {
blog {
comments(first: 9999) {
blog {
# ... repeat
}
}
}
}
}
}

解決策 : クエリの深さを制限することで、深さや複雑性を利用した GraphQL 攻撃を阻止することができます。とはいえ、クエリの深さだけでは、クエリのコストを予測することができない場合があります。クエリ内に解決に時間のかかるフィールドがあれば、コンピューティングコストがかさむことがあります。

クエリコスト分析という方法を使用することで、フィールドにコストを割り当て、コストが高すぎるフィールドをサーバーが拒否するように設定することができます。しかし、導入前にこの対策が本当に必要であるか見極めることが重要です。高コストのネスト化された関係がサービスにない場合、またはサービスで負荷の処理が可能と予想される場合は、この対策は必要ないかもしれません。

バッチ

課題 : GraphQL の主なメリットの一つは、クエリをバッチ化できることです。つまり、複数のリクエストを単一のリクエストにまとめることができます。しかし、事前にセキュリティを考慮した制限を確立しなければ、GraphQL でのクエリのバッチ化が攻撃者に有利な機会を与えてしまう可能性があります。

例えば、ユーザーオブジェクトの複数のインスタンスをリクエストするためのバッチ化されたクエリを示す以下のコードスニペットは、ブルートフォース攻撃に悪用されてしまう恐れがあります。

query {
user(id: "101") {
name
}
second:user(id: "102") {
name
}
third:user(id: "103") {
name
}
}

攻撃者はクエリを拡張し、単一のリクエストを介して全てのユーザーを列挙することができます。これは GraphQL 特有のブルートフォース攻撃の手口で、単一のリクエストで複数のオブジェクトインスタンスをリクエストできるため、検出されにくいという特徴があります。一方 REST API では、攻撃者は各オブジェクトインスタンスを取得するために個別にリクエストを送信しなければなりません。

バッチ化されたクエリは一見単一のリクエストのように見えるため、悪用された場合、Web アプリケーションファイアーウォール (WAF)、ランタイムアプリケーション自己保護 (RASP)、不正侵入検知・防御システム (IDS/IPS)、セキュリティ情報 / イベント管理システム (SIEM) などの一般的なアプリケーション・セキュリティ・ツールをすり抜けてしまう可能性があります。

解決策 : GraphQL のクエリバッチ攻撃を効果的に阻止する一つの方法は、オブジェクトにレート制限を追加することです。例えば、リクエストされるオブジェクトインスタンス数を追跡し、リクエストされたオブジェクト数が制限を超えた場合、ブロックすることができます。

この種の攻撃を回避するもう一つの方法は、機密性の高いオブジェクトがバッチ化されないように制限をかけることです。そうすることで攻撃者は、各オブジェクトごとにリクエストを送信しなくてはならない REST API など、他の方法を利用せざるを得なくなります。

また、一度にバッチ化・実行可能な操作数を制限するのも、効果的な対策です。

Web API脆弱性

課題 : GraphQL は、他の API アーキテクチャとはさほど変わりません。GraphQL を使用するアプリケーションにも、私たちのよく知る一般的な脆弱性が存在する可能性があります。悪質なリクエストを防止するためには、開発者は入力をしっかりと検証・サニタイズする必要があります。

例えば、GraphQL を通じて OS コマンドインジェクションの脆弱性を悪用するとします。

例として、特定の商品の在庫を確認するためのオペレーションを実行するオンライン小売店をターゲットにするとしましょう。以下のように、itemIdvendorIdcolor の引数を使い、サーバーがシェルスクリプトを使ったレガシーシステムをクエリする仕組みになっているとします。

inventorycount.sh 80 200 red

このスクリプトは、各商品の在庫数を出力し、リクエストで返します。このアプリケーションでは防御対策が実装されていないため、攻撃者によって以下のように任意のコマンドが入力される恐れがあります。

red ; env ;

それに対する GraphQL クエリは次のようになります。

query {
inventoryCount(itemId:80, vendorId:200 color:”red; env ;”,)
}

このクエリによって、シークレットや機密情報を含む可能性がある環境変数の内容が返されてしまいます。

レスポンスの一例

attack output

解決策 : 原則として、入力内容の検証は、データフローの初期の段階にて行われなければいけません。入力がサニタイズ・検証されたとしても、それを利用してユーザーがデータフローをコントロールできるようではいけません。受信されるデータは、GraphQL のスカラーと列挙型によって検証する必要があります。

また、より高度な検証を行うために、GraphQLカスタムバリデーターを記述することも可能です。GraphQL クエリの許可リストも、許可済みでないクエリを通さないようサーバーに命じることで、潜在的な脅威による影響を軽減することができます。それでも不安が解消されない場合や、複雑性が増大してしまう場合は、Fastly (旧 Signal Sciences)次世代 WAF の使用をおすすめします。

今後の対策

GraphQL は、API との通信における新たな技術標準となっています。そのため、GraphQL の使用に伴うセキュリティの課題や攻撃対象領域についても注意が必要です。

安全性の高いソフトウェアを構築するには、そのテクノロジーの根底にあるセキュリティの原理を理解することが欠かせません。開発サイクルのあらゆる段階において、起こりうるセキュリティの欠陥を認識し対策を講じることで、将来の悩みの種や問題を大幅に削減することが可能です。これは GraphQL 以外の言語を使用しているプロジェクトについても言えることです。

多層防御アプローチのセキュリティが最も効果的であることを、Fastly はよく理解しています。セキュリティコントロールが機能しなかった場合や、新たな脆弱性が現れた場合などに備えて、Fastly次世代 WAF では GraphQL のサポートをベータ版でご利用いただけます。

Fastly Security Research Team
Fastly セキュリティリサーチチーム
投稿日

この記事は1分で読めます

興味がおありですか?
エキスパートへのお問い合わせ
この投稿を共有する
Fastly Security Research Team
Fastly セキュリティリサーチチーム

Fastly Security Research Team は、お客様が必要なツールやデータを利用してシステムを安全に保つことができる環境づくりに注力しています。同リサーチチームは Fastly ならではのスケールで攻撃を分析し、防止します。Fastly Security Research Team は、常に進化し続けるセキュリティランドスケープの最先端を行く技術を駆使し、裏方としてお客様を支えるセキュリティエキスパートです。


チームスタッフ



  • Simran Khalsa、Staff Security Researcher

  • Arun Kumar、Senior Security Researcher

  • Kelly Shortridge、Senior Principal、Product Technology

  • Xavier Stevens、Staff Security Researcher

  • Matthew Mathur、Senior Security Researcher