GraphQLの脆弱性に関しても、インジェクションの一種だろうと診断入力値やシグニチャを調べ出す人が多いのですが原理的に理解していると
同じGraphQLの脆弱性スキャナ(Burp Suiteの拡張のInQL)を使用していても出してくる結果に大きな差異ができます。
「GraphQL脆弱性診断ガイドラインについて」 (2021年12月)はありますが、あまりに書きかけ感あるので日本語でもGraphQLの脆弱性参考情報がある状態すれば世のためになるだろうと本ブログを書くことにしました。
GraphQLとはFacebookによって開発されたAPI規格です。REST APIの6つの制約にも準拠しているREST思想に完璧なAPIであるということも支持される所以となっています。
リクエストを生成するためのクエリと仕様を記述するためのスキーマからなります。HTTPメソッドを利用してのCRUD機能の代わりにクエリ(読取り)とミューテーション(作成・更新・削除)が対応しています。
そして、定義済みのスキーマに照合して検証し、クエリを実行し、レスポンスにデータを載せJSON形式で応答してきます。
この仕様ゆえ一般的な監視が適用できず防御サイドは中々苦労するAPIでもあります。
検証の仕様が独特=ミスの温床=脆弱性の予感ということで、アクセス制御不備(broken access control)とIDOR(Insecure Direct Object Reference)の脆弱性を含め脆弱性が比較的良く見付けられるAPI規格ではあります。
「検証の仕様が独特」についてですが簡単に言えばGraphQL 関数の多くが送信者の認証のみをチェックするということです。この性質を知って当たりをつけると脆弱性は効率的に発見できます。例えば、パスワード復帰(リマインダー)は機能の性質上、認証のチェックさえないためここにクエリを追加するアプローチが有効です。登録のクエリを貼り付け当たりをつけると登録も非認証で行う性質なので制限が少なく実行が見込めます。実際、不正にアカウントを発行できるのも有効な攻撃手段になり得ます。
さて、もう少し具体的に脆弱性を見付けていけばいいか確認の手順を次項から述べていきたいと思います。
脆弱性発見のアプローチ
まずはエンドポイントを探します。各ホスト以下は確認して見ましょう。
/graph
/v1/graph
/graphql
/v1/graphql
/api/graphql
/v1/api/graphql
/console
/graphql/console
/query
5013:/graphiql
5013:/v1/graphiql
/altair
/playground
:4502/aem/graphiql.html
上記でgraphiqlがアクセス可能な場合、GraphQL IDEにアクセスすることを考えます。(ここでダメでもSSRF可能な場合は127.0.0.1:4502/aem/graphiql.html も確認の価値ありです)
GraphQL IDEにアクセスできれば事実上Introspectionにアクセスできたのと同じことになります。次のIntrospection、APIスキーマを全体に公開する機能なのでこちらを押さえにいきましょう。
GraphQL IDEにアクセスできなくともBurp Suiteの拡張のInQLがその代わりを果たしてくれます。
Introspection
Introspectionは、GraphQLがどのようなスキーマ情報をサポートしているのかを問い合わせるための機能ですが、非認証で呼び出し方や戻り値、引数等を把握できるので攻撃に有用な情報提供になる可能性があります。しかも、デフォルトでIntrospectionが有効なのでまずGraphQLの脆弱性確認では、エンドポイントの確認の次ぐらいに実施するアクションです。
query=IntrospectionQuery { __schema { types { name } } }
上記実施してうまくいきそうであれば以下を投げてみます。
"query":{__schema{queryType{name}mutationType{name}subscriptionType{name}types{…FullType}directives{name description locations args{…InputValue}}}}fragment FullType on __Type{kind name description fields(includeDeprecated :true){name description args{…InputValue}type{…TypeRef}isDeprecated deprecationReason}inputFields{…InputValue}interfaces{…TypeRef}enumValues(includeDeprecated :true){name description isDeprecated deprecationReason}possibleTypes{…TypeRef}}fragment InputValue on __InputValue{name description type{…TypeRef}defaultValue}fragment TypeRef on __Type{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name}}}}}}}}
取得できた情報は整形して戦略を立てましょう。GraphQL Visualizerはオンラインなのでリリース前診断などでは基本的に使用できないのでGraphQL Voyagerにインポートするのが良いかと思います。構造が理解できたら__typenameに取得したいスキーマを構造に合わせて取得しましょう。
form-urlencoded POSTリクエストを試して見ましょう。オリジン間リソース共有 (Cross-Origin Resource Sharing)のことを少し思い出せば判るのですがContent-Type:application/jsonは preflight requestが発生しますが、Content-Type: application/x-www-form-urlencodedの場合は発生しないためチェックが漏れる可能性があります。以下のような形式で送信します。
query=%7B%0A++user+%7B%0A++++id%0A++++__typename%0A++%7D%0A%7D%0A
__schemaだけ回避している可能性もあるので、メタ文字を後述してバイパスするアプローチ、エイリアスを利用した複数回実施によるバイパスも考慮します(エイリアスのブルートフォースで満足する診断士が多いです)一つ目で元々成功するクエリの続きに不正な要求を混ぜるわけです。ここから先は見つけた事象をどのような脆弱性のストーリーとできるかを考えます。
Web Parameter Tampering(パラメータ改竄)
idの指定などがあれば、数字をインクリメントするなどして、アクセス制御不備(broken access control)とIDOR(Insecure Direct Object Reference)の脆弱性が簡単に狙えますが、Introspectionが有効だった場合の侵害はより多岐に渡り対策も慎重に行う必要があります。
Mass Assignment
Laravel,Railsで有名なMass AssignmentですがGraphQLの診断においては必須の項目になります。
Race Condition(競合状態)による脆弱性
GraphQLにおいても、複数のスレッドやプロセスが共有リソースに同時に処理する場合を想定した対策をしていない場合、QueryやMutationのリクエストを同時に送信された場合にRace Condition(競合状態)による脆弱性が見つかる場合があります。システムがリソースをチェックする時間とそのリソースを使用する時間の間に、リソースの状態が変更される可能性、つまり、TOCTOU(Time of check to time of use)の脆弱性が発生している可能性もあります。アクセス権限の侵害が発生する可能性があります。
「何が先に実行されるか」にバグがあればRace Condition(競合状態)による脆弱性、「チェックと使用の間に何が起こるか」にバグがあればTOCTOU(Time of check to time of use)の脆弱性が発生することになります。
GraphQLの脆弱性への対策
GraphQL Best Practicesがあるのでここ読めでも済んでしまいますが、上述挙げた脆弱性に関しては説明したいと思います。
まず基本としてブルート フォース攻撃の対策にもなるため、レート リミッターを設定しましょう。
Race Condition(競合状態)の対策手順
- トランザクションを使用して、データベースの操作をアトミック性を担保します。
- ロックやセマフォを使用して、同時にリソースにアクセスすることを防ぎます。
- キューを使用して、操作を順序付けて実行します。
TOCTOU(Time of check to time of use)の対策手順
- リソースのチェックを処理直前のタイミングで行います。
- トランザクションを使用して、チェックと使用の間に他の操作が介入しないようにします。
Mass Assignmentの対策手順
- 入力データのホワイトリストを作成し、許可されたフィールドのみを更新します。
- GraphQLのスキーマ定義で、公開されるフィールドを厳格に制限します。
アクセス制御不備(broken access control)の対策手順
- 認証と認可をロールとグループアサインに整理して設計します。
- GraphQLのリゾルバ内で、ユーザーの権限をチェックし、不正なアクセスを防ぎます。
- 最小権限の原則に従い、ユーザーに必要な権限のみを付与します。
IDOR(Insecure Direct Object Reference)の対策手順
- リソースへのアクセス時に、ユーザーの権限をチェックします。
- リソースのIDや参照を予測困難なものにします。
- ユーザーがアクセスできるリソースの範囲を制限します。
更なるアドバイスとしては、認証認可の機能拡充にどんなアプローチを開発者が行なっているかもチェックしておくと良いでしょう。
例えばSpring Boot GraphQL APIの実装にLDAP によるセキュリティ保護している場合が想定できると
nmap -n -sV --script "ldap* and not brute" <IPアドレス>
次なる攻撃に必要なヒントが得られる方法が見えてきます。対策の話のはずがまた攻撃の話、いや脆弱性検出方法の話を・・攻撃者サイドが気性にあっているようです。
ここまでGraphQLの脆弱性原理とリスクについて述べて来ましたが、原理が解っていると深堀の方向性も見出せますし、対策も現場のエンジニアと一緒に考えることができます。
他の脆弱性にも応用の効く考え方なのでこの機会に他の脆弱性も復習してみてはいかがでしょうか。
以上です。皆様のお役に立てれば幸いです。