静的解析とSnyk CodeでGraphQLのセキュリティを強化する
Sam Sanoop
2022年4月12日
0 分で読めますGraphQLは、Facebookが2015年に開発したAPIクエリ言語です。独自の機能と特徴を備え、REST APIに代わる有力な選択肢となっています。セキュリティ面では、GraphQLサーバーにさまざまな設定ミスが存在し、データ侵害やアクセス制御の問題、その他の重大な脆弱性につながる可能性があります。
GraphQLのセキュリティ問題は広く知られているものの、動的解析以外でそれらを見つける方法に関する情報はほとんどありません。この記事では、コードベースに潜む一般的なGraphQLの脆弱性と、静的解析ツールおよびGraphQL Securityを使ってそれらを検出する方法について説明します。広く利用されているGraphQLフレームワークの多くはnpmに存在するため、Node.jsエコシステムの例を中心に取り上げます。
GraphQLフレームワークを通じた汚染解析
GraphQLエンドポイントにおけるSQLインジェクションやデシリアライゼーションの脆弱性は、数多くの調査で報告されています。その一例として、HackerOneのレポートでGraphQLパラメーターを介したSQLインジェクションが確認されています。
REST APIのパラメーターと同様に、GraphQLの引数もユーザー入力をアプリケーションに取り込む汚染源となります。静的解析ツールは、アプリケーション内のこれらのパラメーターを正確に特定し、モデル化できる必要があります。
GraphQLフレームワークでは、リゾルバーがスキーマ、関数、引数を対応付けてから、リゾルバー関数に渡します。次の例では、args引数が、GraphQLオペレーションによってフィールドに渡されたすべてのGraphQL引数を含むオブジェクトとして示されています。
これらのargs式は、ポインター解析と型状態解析を活用してプログラムの実行を正確に記録するSnyk Codeエンジンで追跡できます。最新のオープンソースNode.jsベースのコンテンツ管理システムであるSonicJSのSnyk Codeによる解析が、参考になる例です。
SonicJSでは、GraphQLエンドポイントを通じてCMS操作を実行できます。その操作の1つがfileUpdateミューテーションで、ユーザーがファイルを更新できます。
(ソースコード)
このミューテーションクエリは、ユーザーが指定したargs.filePathとargs.fileContentを受け取り、それらをfileServiceオブジェクトのwriteFile関数に渡します。fileServiceオブジェクトの関数はserver/services/file.service.jsに定義されており、以下のように確認できます。
(ソースコード)
この関数はGraphQLの引数を受け取り、fs.writeFile関数を使って指定されたパスのファイルを更新します。しかし、この関数を悪用すると、指定されたアプリケーションディレクトリを遡って、対象システム内の任意の場所に新しいファイルを作成できます。たとえば、次のGraphQLクエリのような方法です。
この脆弱性を悪用すると、SonicJSのサービスファイルの1つを、SonicJSの起動時または再起動時に読み込まれる悪意あるJavaScriptで上書きし、システム上でコードを実行できます。たとえば、次のステートメントはchild_process関数を使ってアプリケーションにバックドアを仕込み、対象システムにインストールされたncatプログラムを利用して、攻撃者が制御するIPアドレスに接続します。

この脆弱性に関するSnyk Codeのレポートを以下に示します。

SonicJSでは、クエリに認証と認可を必須とし、指定されたファイルパスを検証して../などの特殊文字を許可しないことで、今後この脆弱性を防止できます。
GraphQLのイントロスペクション
GraphQLのイントロスペクション機能を使うと、GraphQLサーバーがサポートするクエリを調べられます。対象となるのは、型、フィールド、クエリ、ミューテーションなど、GraphQLスキーマに関する情報です。
これは機能であり、直接的なセキュリティ問題ではありませんが、イントロスペクションは、攻撃者が悪用できる隠れた機能の発見に使われることがあります。
Node.jsエコシステムでは、JavaScriptフレームワークでイントロスペクションがデフォルトで有効になっていることが多く、開発者が気づかない場合があります。そのため、以下のexpress-graphqlのように、設定を指定せずGraphQLフレームワークを使うと、イントロスペクションが自動的に有効になります。
Snyk Codeでのこのレポートの例を以下に示します。

本番アプリケーションでもイントロスペクションが正当に必要となる場合があるため、この問題は低リスクとして報告されました。
express-graphqlでイントロスペクションを無効にするには、graphql-jsが提供するNoSchemaIntrospectionCustomRuleを使用します。
本番環境でイントロスペクションを有効にするとセキュリティ上の問題につながる可能性がありますが、開発時には必要となることがよくあります。ApolloServerの開発者は、本番環境かどうかに応じてイントロスペクションを有効または無効にすることで、この問題に対処しています。
別のGraphQLフレームワークを使用している場合は、graphql-disable-introspectionパッケージなどのサードパーティーライブラリを使って、GraphQLエンドポイントのルールを検証できます。
GraphQLによるサービス拒否攻撃
GraphQLエンドポイントが処理するクエリには、ネストされたオブジェクトの深さがあります。ほとんどのGraphQLフレームワークでは、デフォルトで深さの制限が設定されていません。深さに制限のないクエリは、以下の例のように、フレームワークをサービス拒否(DoS)攻撃に対して脆弱な状態にする可能性があります。
ただし、この問題を悪用するには、GraphQLサーバーに、双方向の関係を持つフィールド型の再帰的なパターンが必要です。Snyk Codeでのこのレポートの例を以下に示します。

このDoS脆弱性はmevn-cliで見つかりました。修正するには、npmで入手できるgraphql-depth-limitパッケージを次のように使用できます。
修正例については、mevn-cliリポジトリのコミットをご覧ください。
DoS攻撃を防ぐもう1つの方法は、GraphQLエンドポイントでリクエスト本文の長さを確認することです。クエリが大きい場合はDoS攻撃の兆候である可能性があるため、クエリの長さを監視することは、シンプルで効果的な検証方法です。
GraphQLインジェクション
GraphQLインジェクションでは、攻撃者がアプリケーションのクエリに干渉し、通常は取得できないデータ(ユーザーデータや、アプリケーションがアクセスできるその他のデータ)にアクセスできるようになります。多くの場合、データの変更や削除も可能となり、アプリケーションのコンテンツや動作に永続的な変更を加えられるおそれがあります。
@octokit/coreは、GitHubのREST APIとGraphQL APIを利用してGraphQLクエリを作成するための軽量なライブラリです。ユーザー入力を使って動的にGraphQLクエリを作成する場合、攻撃者がクエリを改変し、機密情報にアクセスできる可能性があります。この問題の例を以下に示します。
Snyk Codeでのこのレポートの例を以下に示します。

GraphQLインジェクションを防ぐには、ユーザーが入力したパラメーターをGraphQLクエリに直接渡さないでください。パフォーマンス上の理由からユーザー入力を直接使う必要がある場合は、許可する文字を厳格な許可リストで検証し、? & / < > ; -やスペースなどの特殊文字を除外してください。また、可能であればベンダー提供のエスケープ処理を使用してください。
まとめ
Snyk Codeは現在、さまざまな汚染解析ルールとセマンティックルールを通じて、以下のGraphQLフレームワークをサポートしています。
express-graphqlkoa-graphqlmercuriusapollo-servergraphql-js
今後は、安全でないオブジェクト参照、直接参照、アクセス制御の問題など、GraphQLの脆弱性への対応をさらに拡大したいと考えています。現在、GraphQLはJavaScriptに対応していますが、バッチ攻撃やクエリの複雑さなどの問題に対処するため、対応するプログラミング言語やコード品質ルールを増やすことを目指しています。
Capture the Flagを始めよう
オンデマンドのバーチャル入門ワークショップを見て、Capture the Flagの課題の解き方を学びましょう。


