在使用GraphQL客户端时,请注意查询注入
起源
有人要求我作为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 を呼び出せてしまう可能性
那么,我们应该采取什么样的对策呢?
解决方案:使用变量而不是字符串连接。
在中国,动态参数是以变量的形式传递的。
假设使用变量,那么将重写上述查询,如下所示。
query($someId: ID!) {
someQuery(id: $someId) {
id
}
}
如果是常用的GraphQL API客户端,应该提供了可以指定变量的接口,如下所示。
const userInput = ...
const variables = { id: userInput }
const result = GraphQLClient.query('query($id: ID!) { someQuery(id: $id) { id } }', { variables })
如果不使用GraphQL API客户端,而是通过HTTP层来组装请求,那么使用字符串连接来组装variables的JSON同样不可取。
变量可以指定为对象。
当在 HTTP 层面查看GraphQL的调用时,查询(看起来像JSON)是以字符串的形式指定的,因此可能会误以为变量也需要以字符串的形式指定,但实际上可以指定一个对象。
因此,甚至可以将自定义的输入类型完全从变量中传递给GraphQL。
mutation AddUser($input: AddUserInput!) {
addUser(input: $input) {
id
errors { message }
}
}
{
"input": {
"username": "Taro",
"address": "Tokyo",
"languages": [ "ja", "en" ]
}
}
概述
由于尝试与GraphQL API进行深度通信的客户端往往从一开始就有引入GraphQL客户端库的动机,所以可能不容易遇到这个问题。
相反,那些只使用1到2个公开的GraphQL API的客户端可能会认为“使用fetch API或curl在HTTP层面上简单处理就行”,而陷入陷阱。
发生机制与其他注入漏洞完全相同呢。
它发生在跨语言边界的地方,就会出现注入问题。
就像在SQL注入防范中使用绑定变量一样,在GraphQL中,我们应该使用variables。