CVE-2020-9483的根本原因是SkyWalking的GraphQL解析器未对用户输入的id参数做参数化处理,直接字符串拼接进SQL语句,导致H2/MySQL/TiDB后端可被SQL注入。

GraphQL解析器里直接拼接SQL字符串就是高危操作
根本原因不是GraphQL本身,而是后端解析器(resolver)代码写法不当。当resolver接收GraphQL查询中的参数(比如id、name等),却用字符串拼接方式构造SQL语句,就等于把用户输入直接喂给数据库引擎执行。
典型错误写法示例如下:
const resolver = {
getMetric: async (_, { id }) => {
// 危险!id未校验、未参数化
const sql = `SELECT * FROM metrics WHERE id = '${id}'`;
return db.query(sql);
}
};- 攻击者传入
id: "1' UNION SELECT password FROM users--",SQL就被篡改 - 即使前端做了限制,GraphQL的灵活性允许任意字段组合和嵌套,参数仍可被绕过校验
- 这种漏洞在
graphql-java、Apollo Server、graphene等库中都出现过实际案例(如CVE-2020-9483)
哪些GraphQL参数最容易触发SQL注入?
不是所有字段都危险,但以下几类在解析器中若被用于构建SQL,风险极高:
-
id、metricId、userId等标识类参数——常被直接拼进WHERE子句 -
orderBy、sortField等排序字段——若拼进ORDER BY后且未白名单校验,可注入name ASC, (SELECT ...) -
filter对象中的动态键值对——比如{ status: "active", type: "admin' OR '1'='1" },极易被展开为拼接条件 - GraphQL变量(variables)里的任意字符串——变量本身不安全,安全取决于resolver怎么用它
注意:__schema内省查询不会导致SQL注入,但它可能泄露字段名、类型等信息,帮攻击者精准构造payload。
为什么参数化查询在GraphQL场景下更容易被忽略?
开发者常误以为“GraphQL自带类型校验=安全”,其实Schema只约束结构,不干预resolver内部逻辑。
- Schema声明
id: ID!,只保证非空、是字符串/数字格式,**不阻止SQL注入字符**(如单引号、分号、注释符) - GraphQL-Java默认不自动转义,
graphql-java的DataFetchingEnvironment返回的id仍是原始字符串 - Apollo Server中
context传给DB层的数据,如果没走pg.query的参数化接口(如pool.query('SELECT $1', [id])),而是用模板字面量,照样中招 - ORM如Prisma、TypeORM虽默认参数化,但一旦写
raw()或unsafe()方法,风险立刻回归
真实漏洞复现中容易踩的坑
复现CVE-2020-9483这类漏洞时,很多人卡在第一步——不是技术不行,而是环境或判断错了。
- 误判存储后端:漏洞仅影响
H2、MySQL、TiDB,用Elasticsearch或InfluxDB做后端时不会触发 - 漏掉GraphQL入口路径:不是所有服务都用
/graphql,常见变体有/graphql/console、/api/graphql、/graphiql,需先确认端点 - 混淆错误回显:SQL报错可能被GraphQL层捕获并包装成
{"errors":[{"message":"Internal server error"}]},需开调试模式或查OAP日志才能看到原始java.sql.SQLException - 忽略权限上下文:有些resolver会对
admin角色放行拼接,对普通用户才参数化——测试时要用低权限账号,否则看不出问题
真正危险的从来不是“能不能注入”,而是“有没有人意识到resolver正在裸奔”。一个没加?占位符的db.query调用,比十个没设CORS头的REST接口更致命。


















