Resolver中直接拼接SQL字符串是高危操作,因未参数化处理用户输入,易引发SQL注入;必须使用数据库驱动原生占位符(如pg的$1、mysql2的?),配合字段级语义校验与GraphQLError抛出,禁用任何字符串拼接。

Resolver里直接拼接SQL字符串就是高危操作
GraphQL Resolver 函数如果把 args 或 context 中的原始用户输入,未经处理就插进 SQL 字符串(比如用 + 或模板字符串拼接),等于主动给攻击者开了后门。CVE-2020-9483 就是典型:SkyWalking 的某个 Resolver 把 query 参数直接塞进 WHERE 条件,没走参数化流程。
这种写法在开发时看着省事,但只要字段含单引号、分号、UNION SELECT,数据库就会执行恶意逻辑——和传统 Web 表单 SQL 注入本质相同,只是入口换成了 GraphQL 查询体。
必须用数据库驱动原生的参数占位符
所有主流驱动都支持参数化查询,Resolver 里绝不能出现字符串拼接 SQL。关键不是“有没有校验”,而是“根本没机会让非法字符进 SQL 解析器”。
- PostgreSQL(
pg):用$1,$2占位,传参数组:client.query('SELECT * FROM users WHERE id = $1', [args.id]) - MySQL(
mysql2):用?占位:connection.execute('SELECT * FROM logs WHERE type = ?', [args.type]) - SQL Server(
mssql):用@param命名参数:request.input('id', sql.Int, args.id)
注意:pg_graphql 这类封装层本身已强制参数化,但自定义 Resolver 绕过它时,就得自己扛住这道关。
校验不能只做类型检查,要结合业务语义
仅靠 typeof args.id === 'string' 或 parseInt() 转数字,拦不住形如 "1 OR 1=1" 的绕过。真正的校验得落在具体字段上:
-
id字段:用正则/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i验 UUID,或Number.isInteger(Number(args.id)) && Number(args.id) > 0验正整数 -
email字段:用validator.isEmail()等成熟库,别手写正则 - 搜索类字段(
query,filter):明确限制长度(如 ≤ 100 字符)、禁用通配符(%,_)或转义为字面量
校验失败必须抛出 GraphQLError,而不是静默截断或默认值——否则前端可能误判为数据为空。
复杂查询场景下,预编译语句比运行时拼接更可靠
当 Resolver 需动态生成 SQL(比如根据 args.sortBy 决定 ORDER BY 字段),拼字符串+白名单校验仍是高风险。更稳的做法是:
- 把允许排序的字段硬编码为键值对:
{ name: 'users.name', email: 'users.email' },再从对象取值 - 用 Knex、Prisma 等 Query Builder,它们内部自动参数化且过滤非法标识符
- 对
WHERE条件,用knex('users').where('status', args.status).andWhere('created_at', '>', args.since),而非手动拼AND status = '${args.status}'
真正难防的不是注入本身,而是开发者误以为“我做了校验”就安全了——只要 SQL 字符串里混入了任何未被占位符包裹的用户输入,防线就已失效。











