発端
GraphQL API の利用者から API クライアントのコードレビューに付き合ってほしいと言われたので出席したんですが、とんでもない実装になっていて腰を抜かしてしまったので、注意喚起しようかなと。
はじめに結論
- クエリの一部にユーザ入力を文字列連結するのはやめましょう!
- 動的パラメータは
variables
で指定しましょう。
variables
の指定例は以下を参照。
https://graphql.org/learn/serving-over-http/#post-request
ポピュラーな GraphQL クライアントであれば、 variables に当たる変数を渡すことができるインタフェースが用意されていると思います。
インジェクションが起きる仕組み
実装言語は何でも良いです。
以下のような API クライアント実装があるとします。
const userInput = 'abc123' // これがユーザの入力値
const result = GraphQLClient.query('{ someQuery(id: "' + userInput + '") { id } }')
このとき、開発者は以下のようなクエリが実行されることを期待しているはずです。
{
someQuery(id: "abc123") {
id
}
}
しかし、もしユーザ入力が以下のような形だったら・・・
// こんな文字列だと...
const userInput = 'abc123") { id } users(id: "user1") { creditCardNumber } q2: someQuery(id: "abc123'
const result = GraphQLClient.query('{ someQuery(id: "' + userInput + '") { id } }')
こんなクエリが発行されてしまいます!
{
someQuery(id: "abc123") {
id
}
users(id: "user1") {
creditCardNumber # ヤバい情報にアクセス
}
q2: someQuery(id: "abc123") {
id
}
}
実際には、得られた実行結果をそのまま出力するようなシステムは無いと思うので、
creditCardNumber
の問い合わせ結果を攻撃者が確認できるケースはかなり限定されると思います。
しかし、以下のような危険は依然として残っています。
- ヘビーなクエリを組み合わせて DoS 攻撃できてしまう可能性
- 実行するのが Mutation であれば、任意の mutation を呼び出せてしまう可能性
では、どうやって対策すれば良いのでしょうか?
対策: 文字列連結ではなく variables を使う
動的パラメータは variables
というもので渡すようにします。
variables
を使う想定で前述のクエリを書き直すと以下のようになります。
query($someId: ID!) {
someQuery(id: $someId) {
id
}
}
ポピュラーな GraphQL API クライアントであれば、以下のように variables も指定できるようなインタフェースが用意されているはずです。
const userInput = ...
const variables = { id: userInput }
const result = GraphQLClient.query('query($id: ID!) { someQuery(id: $id) { id } }', { variables })
GraphQL API クライアントを使わずに HTTP レイヤでリクエストを組み立てる場合、文字列連結で variables の JSON を組み立てることも、同様に好ましくありません。
variables には Object を指定できる
GraphQL の呼び出しを HTTP レイヤで見ると、 query
は (JSON っぽく見えるのに) 文字列で指定することから、 variables
も文字列で指定すると勘違いされるかもしれませんが、 Object を指定できます。
従って、カスタムの input type を丸々 variables から与えることも可能です。
mutation AddUser($input: AddUserInput!) {
addUser(input: $input) {
id
errors { message }
}
}
{
"input": {
"username": "Taro",
"address": "Tokyo",
"languages": [ "ja", "en" ]
}
}
まとめ
GraphQL API とガッツリ通信しようとするクライアントは、最初から GraphQL クライアントライブラリを導入するモチベーションが生まれやすいので、この問題に遭遇しにくいかもしれません。
逆に、公開されている GraphQL API のうち 1, 2 個しか使わないから「fetch API や curl を使って HTTP レイヤで簡単に済ましちゃえ」と思っているクライアントは罠にハマりやすいのではないかと。
発生のメカニズムは他の~~インジェクション系脆弱性と全く同じですね。
言語の境界をまたぐところにインジェクションあり、です。
SQL インジェクション対策にバインド変数を使いましょうなどと言うのと同じく、GraphQL では variables を使いましょう。