何が起きたか・対象
graphql-rubyは、GraphQLリクエストをRubyアプリの取得・更新処理へ結び付けるライブラリです。その実行方式の一つであるExecution::Nextに、認可で拒否された要求でも処理を続ける欠陥がありました。識別子はGHSL-2026-152です。
アプリの開発者は、権限を確認するauthorized?と、データを取得・変更するresolveを用意します。ライブラリのResolver#callはauthorized?を呼び、許可された場合にcall_resolveを通してresolveを実行する役割です。しかし、拒否を表すUnauthorizedErrorを捕まえた処理がis_authedをtrueにしたため、後の分岐が許可と判断し、resolveへ進んでいました。
利用側が非公開文書を返すresolveを書いていれば、その内容が攻撃者のHTTP応答へ届くおそれがあります。更新を行うresolveなら、応答を隠してもDB更新はすでに起きます。対象の実行エンジンと例外を使う構成が条件で、全てのgraphql-ruby利用環境で同じ処理が起きるわけではありません。
報告・修正・公開の時系列
2026-07-16に報告、07-17に受理と修正、07-21に2.6.6とアドバイザリが公開されました。研究公開は08-08です。研究は2.6.5で検証し、2.6.1でも再現しています。
正常ならどう動くはずだったか
GraphQLリクエストを受けたサーバーは、resolverのauthorized?でその利用者が対象の取得や更新をしてよいか検査します。GraphQL::UnauthorizedErrorを投げて拒否した場合は、データを取得・変更するresolveを呼ばずに終了する必要があります。レスポンスを隠すことだけでなく、実行自体を止めることが必要です。
技術的な原因
Execution::NextのResolver#callは、authorized?でUnauthorizedErrorが起きるとrescueへ進みます。そこでunauthorized_objectを呼び、既定ではnilを受け取りますが、処理を終了せずis_authedへtrueを代入していました。その後のif is_authedは真なのでcall_resolveが実行され、アプリのresolveへ進みます。例外を受け止めたことを、認可に成功したこととして扱う箇所が不備です。
攻撃が成立する条件と流れ
攻撃者が権限のない対象の取得・更新をGraphQLリクエストで指定します。Execution::Nextを使用し、認可拒否を該当例外で表す構成では、例外を処理した後でもresolverが動きます。旧Interpreterでは同じリクエストを拒否でき、graphql-rubyを使う全構成が同じ経路を持つわけではありません。
確認された事実と、想定される影響
取得resolverの説明用の例では、拒否するはずの非公開文書をresolveが読み、HTTPレスポンスで攻撃者へ返します。攻撃者は、その文書を自分の端末で入手できます。何が返るかは、利用側がresolverへ実装したデータ取得の内容によります。
更新resolverの例では、許可されないプロフィール変更をresolveがDBへ保存します。外部APIを呼ぶresolverなら、拒否したはずの利用者の指示で、そのAPIの処理を実行するおそれもあります。応答を隠しても、既に実行したDB更新や外部操作が取り消されるわけではありません。研究の再現と実際のアプリへの悪用・被害件数は区別します。
実際の修正・対応方針
2.6.6で修正と開示されています。一般的にも、認可の拒否や処理失敗から許可へ進まない状態設計が必要です。修正済みライブラリの採用と、利用側での拒否時の確認を合わせて行います。
利用者・運用者が確認すること
ライブラリの版と実行エンジンを確認して更新します。権限不足で例外を投げた場合、結果を隠すだけでなく、resolve自体が呼ばれず、副作用も起きないことをテストします。
開発者への教訓(教材としての考察)
認可では、拒否したという情報が、例外処理を経ても実行停止まで保たれるか確認します。今回のように、例外を捕まえた後で許可フラグへ進む分岐があれば、拒否を返す検査があっても対象の処理は動いてしまいます。
出典・確認範囲
- [1] Privilege escalation via authorization bypass in graphql-rubyGitHub Security Lab / 確認日 2026-10-29
参照資料では実悪用や実際の被害件数を確認できません。未記載の実装差分は推測していません。