关于 sast-graphql
sast-graphql 是用于代码库安全评估的 GraphQL 注入检测技能,面向安全审查人员。它先确认 GraphQL 使用情况并定位动态拼接操作文档,再分批追踪输入并汇总报告。
主要功能
- 01确认 GraphQL 技术使用情况
- 02定位动态构造操作文档的位置
- 03分批并行追踪用户输入
- 04汇总核查结果
- 05要求预先准备架构摘要
适用场景
- 01审查 GraphQL 操作文档构造
- 02检查输入是否改变查询文本
- 03评估 GraphQL 网关转发代码
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ utkusen/ sast-skills --skill sast-graphql安装后在 Claude Code 中输入/sast-graphql确认已加载。
详细介绍
GraphQL 注入检测
你正在进行一项有针对性的安全评估,以发现 GraphQL 注入漏洞。此技能采用包含子 Agent 的三阶段方法:侦察(确认是否使用 GraphQL,并找出所有以不安全方式组装 GraphQL 操作文档的位置)、分批验证(追踪用户提供的输入是否到达这些组装位置,每批并行检查最多 3 个位置),以及合并(汇总各批结果,生成最终报告)。
前提条件:sast/architecture.md 必须存在。如果不存在,请先运行分析技能。
什么是 GraphQL 注入
当用户可控数据被嵌入 GraphQL 文档(查询、变更或订阅字符串),而不是仅传入 variables 映射时,就会发生 GraphQL 注入。解析器随后会将攻击者控制的语法(新字段、别名、指令或片段)解释为文档的一部分。这可能绕过预期限制、访问未授权的解析器,或在执行或转发文档时改变服务端行为。
核心模式:未经验证的用户输入改变了传递给 execute、graphql、网关客户端的 GraphQL 操作字符串结构或文本,或者改变了通过字符串操作构建的 HTTP 正文 query 字段。
什么情况属于 GraphQL 注入
- 将用户输入拼接或插值到操作字符串中:
`query { user(id: "${id}") { name } }`,"query { user(id: \"" + id + "\") { name } }" - 使用请求正文或参数中的内容通过字符串拼接构建下游 GraphQL HTTP 请求的 JSON
query字段 - 将
req.body.query(或类似内容)转发到另一个插值模板中,由该模板包装或扩展操作 - 动态
gql/graphql-tag模板字符串,其中非静态表达式会改变文档结构(而不仅仅是在静态文档中绑定变量值) - 服务端代码根据用户输入选择或组装操作文本(包括没有允许列表的“持久化查询”ID → 文档映射)
- 包装
graphql.execute()、graphqlHTTP、Yoga/Apollo 请求管线的代码,其中第一个参数(文档/源文本)由可能受用户影响的变量构建
什么情况不属于 GraphQL 注入
以下情况不要标记为 GraphQL 注入:
- 解析器中的 SQL 注入:根据
args构建 SQL 的解析器代码属于 SQL 注入(sast-sqli),不属于此技能的范围 - 解析器中的 NoSQL 注入 / 命令注入:同理,请使用相应的 SAST 技能
- 通过 GraphQL 参数造成的 IDOR:在静态文档中,通过 variables JSON 传入其他用户的 ID——这是授权缺陷,不是文档注入
- 正常的变量绑定:静态文档配合
{"query": "query($id: ID!) { user(id: $id) { name } }", "variables": {"id": userInput}}——值作为变量绑定,文档结构固定(仍需检查解析器中的授权) - 启用了内省 / 字段建议:这是信息泄露和安全加固问题;只有当发现明确涉及向操作字符串注入内容时,才将其标记为 GraphQL 注入
- 查询深度 / 复杂度造成的 DoS:属于速率限制和成本分析问题,是另一类问题
防止 GraphQL 注入的模式
1. 使用包含变量的静态操作文档
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) { name }
}
`;
// execute(schema, GET_USER, null, context, { id: userId });
2. 服务端使用标准 HTTP 处理程序;客户端发送文档;服务端只解析一次
如果服务端只是解析并执行客户端的操作,那么仅仅存在 req.body.query 并不意味着有风险——在这条路径中,注入发生在客户端。应标记的是服务端在执行之前或转发之前,构建并纳入用户字符串的新文档。
3. 持久化查询 / 允许列表中的操作 ID
根据 ID 从服务端注册表中查找文档;客户端无法注入任意文档文本。
4. 使用静态字符串构建 graphql-js Source;动态值只放在 variableValues 中
graphql({ schema, source: staticQueryString, variableValues: { id: userId } });
易受攻击与安全示例
Node.js — 面向下游 API 的动态文档
// VULNERABLE: user input in operation text
app.post('/proxy', async (req, res) => {
const fragment = req.body.fragment;
const query = `query { me { ${fragment} } }`;
const data = await fetch('https://api.internal/graphql', {
method: 'POST',
body: JSON.stringify({ query }),
});
});
// SECURE: static operation, user data only in variables
const PROXY_QUERY = `query ProxyMe { me { id name email } }`;
app.post('/proxy', async (req, res) => {
const data = await fetch('https://api.internal/graphql', {
method: 'POST',
body: JSON.stringify({ query: PROXY_QUERY }),
});
});
Python — 将字符串格式化为可执行内容
# VULNERABLE
def run_custom_query(user_gql: str):
document = f"query {{ user {{ {user_gql} }} }}"
return graphql_sync(schema, document)
# SECURE: validate against allowlist of named operations or use static documents only
ALLOWED = {"id", "name", "email"}
fields = [f for f in requested_fields if f in ALLOWED]
document = "query { user { " + " ".join(ALLOWED.intersection(set(requested_fields))) + " } }"
# Better: fixed FieldNodes, not string building from user input
执行
此技能通过子代理分三个阶段运行。将 sast/architecture.md 的内容作为上下文传递给所有子代理。
阶段 1:GraphQL 技术侦察与注入候选位置
启动一个子代理,并向其提供以下指令:
目标:(1)确定此代码库是否使用 GraphQL。(2)如果使用,找出所有通过字符串拼接、插值、格式化或动态组装来构建 GraphQL 操作文档(查询/变更/订阅源字符串)的位置,使变量可能改变文档文本(而不仅仅是
variablesJSON)。将结果写入sast/graphql-recon.md。背景:你将获得该项目的架构摘要。请据此了解技术栈、API 布局以及 BFF/网关模式。
A 部分——是否使用 GraphQL?
搜索以下内容:
- 依赖项:
graphql、@apollo/server、apollo-server-express、@nestjs/graphql、graphql-yoga、@graphql-yoga/node、mercurius、strawberry-graphql、graphene、sangria、gqlgen、async-graphql、juniper、graphql-ruby、Hot Chocolate/GraphQL.Server等。- Schema 文件:
*.graphql、*.graphqls、代码生成配置(例如 GraphQL Code Generator)- 挂载
/graphql或类似路径的服务端路由或插件将摘要设为以下其中一项:
GraphQL is used in this codebase.(列出库和主要入口)GraphQL is not used in this codebase.B 部分——注入候选位置(仅当使用 GraphQL 时)
如果未使用 GraphQL,请省略“Injection Candidate Sites”部分,或注明没有候选位置。不要臆造候选项。
如果使用 GraphQL,请搜索不安全的文档构造:
- 拼接字符串/将内容插入操作文本:
`query { ... ${x} ...}`、"mutation { " + userFragment + " }"- 使用
sprintf、format、%格式化、.format()来构建query或source参数- 文档参数不是编译时常量的调用:
graphql(schema, dynamicString, ...)、execute({ schema, document: parsedDynamic, ...}),其中传给parse或execute的字符串由非静态部分构建而成graphqlHTTP({ schema, rootValue, context: (req) => ({ query: req.body.query + something }) })这类会用用户数据修改或包装查询字符串的模式- 转发构造好的 GraphQL 请求体的 HTTP 客户端:
JSON.stringify({ query:...${userPart}...})、axios.post(url, { query: builtFromInput })- 不安全的持久化/存储查询查找:
- 根据用户输入的键加载操作文本,但未设置允许列表,导致文件路径或数据库值成为文档源
跳过的内容(不要标记为第 1 阶段候选项):
- 完全静态的
source/query字符串;只有variableValues/variables来自请求- 没有用户插值的
buildSchemaSchema 定义/SDL 文件- 仅通过参数化数据库 API 使用参数的解析器实现(可选:注明“解析器使用 ORM”,但除非文档构造不安全,否则不属于 GraphQL 注入候选项)
输出格式——写入
sast/graphql-recon.md:# GraphQL Recon: [Project Name] ## Summary GraphQL is [used / not used] in this codebase. [If used: libraries, main server files, typical endpoint paths] Found [N] injection candidate site(s) where operation documents may be built unsafely. [If not used, say N/A or 0 and skip candidate list] ## GraphQL Surface (only if used) - **Libraries / frameworks**: ... - **Entry points**: ... - **Notable files**: ... ## Injection Candidate Sites ### 1. [Descriptive name] - **File**: `path/to/file.ext` (lines X-Y) - **Function / endpoint**: ... - **Execution / call pattern**: [graphql.execute / fetch with body / gql template / etc.] - **Construction pattern**: [concat / template literal / format / forwarded body mutation] - **Interpolated variable(s)**: ... - **Code snippet**:...
[Repeat for each site; if none, write "No injection candidate sites found." under the heading]
第 1 阶段之后:第 2 阶段之前的门槛检查
第 1 阶段完成后,读取 sast/graphql-recon.md。
门槛 1 — 不使用 GraphQL 技术
如果摘要指出未使用 GraphQL(或含义相同:没有 GraphQL 库、没有 schema、没有服务端——明确不存在),则跳过第 2 阶段和第 3 阶段。将以下内容写入 sast/graphql-results.md 并停止:
# GraphQL Injection Analysis Results
No GraphQL technology detected in this codebase.
门槛 2 — 使用 GraphQL,但没有注入候选点
如果使用了 GraphQL,但有零个注入候选点(摘要报告候选点为 0,或“Injection Candidate Sites”部分指出未找到候选点 / 为空),则跳过第 2 阶段和第 3 阶段。将以下内容写入 sast/graphql-results.md 并停止:
# GraphQL Injection Analysis Results
No vulnerabilities found.
否则,继续第 2 阶段。
第 2 阶段:追踪用户输入到注入候选点(分批处理)
第 1 阶段完成且两项门槛都通过后(使用了 GraphQL,且至少有一个候选点),读取 sast/graphql-recon.md,并将Injection Candidate Sites 拆分为每批最多 3 个站点(每个 ### N. 小节代表一个站点)。并行启动每批一个子 Agent。每个子 Agent 仅追踪分配给自己的站点的污点,并将结果写入各自的批次文件。
分批流程(由你这个协调者执行,而非子 Agent):
- 读取
sast/graphql-recon.md,统计“Injection Candidate Sites”下编号候选小节的数量(### 1.、### 2.等)。 - 将它们划分为每批最多 3 个。例如,8 个站点 → 3 批(1-3、4-6、7-8)。
- 从 recon 文件中提取每批候选小节的完整文本。
- 并行启动所有批次子 Agent,并且只向每个子 Agent 提供其分配的候选小节(以及架构上下文)。
- 每个子 Agent 将结果写入
sast/graphql-batch-N.md,其中 N 为从 1 开始的批次编号。 - 从
sast/architecture.md确定项目的主要语言 / 框架,并且只选择上方“Vulnerable vs. Secure Examples”部分中匹配的示例。例如,如果项目使用 Node.js,则包含“Node.js — dynamic document for downstream API”示例;如果使用 Python,则包含“Python — string format into execute”示例。在每个子 Agent 的指令中,于下方[TECH-STACK EXAMPLES]所示位置加入这些选定的示例。
向每个批次子 Agent 提供以下指令(替换为对应批次的值):
目标:针对每个指定的注入候选点,判断用户提供的数据是否能到达操作文档中的动态部分。我们的目标是查找 GraphQL 注入漏洞。将结果写入
sast/graphql-batch-[N].md。你负责的候选点(来自侦察阶段):
[在此粘贴指定候选章节的完整文本,并保留原始编号]
上下文:你将获得项目架构摘要。请用它了解 API 布局、请求处理方式以及 BFF/网关模式。
GraphQL 注入参考 — 检查要点:
用户可控数据不得更改 GraphQL 文档文本(查询/变更/订阅源),只能通过静态文档中绑定的 变量 传入。如果污点到达操作语句的字符串拼接过程,请标记。
以下情况不属于 GraphQL 注入 — 请勿在此处标记:
- 解析器中的 SQL/NoSQL 注入 — 属于其他 SAST 技能的范围
- 静态文档 + 变量导致的 IDOR — 属于授权问题,而非文档注入
- 固定文档字符串上的正常变量绑定
- 启用了内省 — 除非问题具体涉及操作字符串注入
- 查询深度/复杂度导致的 DoS — 属于不同类别
可降低风险的缓解措施:
- 在进行任何字符串拼接之前,对字段或操作 ID 进行允许列表校验
- 使用解析器校验,拒绝意外定义(但仍应避免使用用户可控的文档结构)
适用于本项目技术栈的漏洞与安全示例:
[TECH-STACK EXAMPLES]
针对每个指定点,向上追踪动态值的来源:
- 直接用户输入 — 查询参数、路径参数、JSON 请求体字段(包括重新包装时的嵌套
query)、请求头、Cookie- 间接用户输入 — 辅助函数、中间件、上下文构建器
- 二阶输入 — 后续用于构建文档的已存储偏好设置或数据库字段;追踪写入路径
- 仅限服务端 — 配置、环境变量、硬编码片段 — 无法由客户端利用
分类:
- 易受攻击:用户可控数据到达文档构造过程,且没有有效的缓解措施
- 可能易受攻击:可能存在污点,或清理措施较弱
- 未发现漏洞:仅服务端数据,或采用了有效的允许列表/静态文档路径
- 需要人工审查:数据流不透明
输出格式 — 写入
sast/graphql-batch-[N].md:# GraphQL Batch [N] Results ## Findings ### [VULNERABLE] Descriptive name - **File**: `path/to/file.ext` (lines X-Y) - **Endpoint / function**: ... - **Issue**: ... - **Taint trace**: ... - **Impact**: [e.g., unauthorized fields, gateway bypass, SSRF-style behavior to internal GraphQL] - **Remediation**: [static operations; variables only; persisted query allowlist] - **Dynamic Test**:[curl or in-browser GraphQL request showing injected fragment/directive/field]
### [LIKELY VULNERABLE] Descriptive name - **File**: ... - **Endpoint / function**: ... - **Issue**: ... - **Taint trace**: ... - **Concern**: ... - **Remediation**: ... - **Dynamic Test**:...
### [NOT VULNERABLE] Descriptive name - **File**: ... - **Endpoint / function**: ... - **Reason**: ... ### [NEEDS MANUAL REVIEW] Descriptive name - **File**: ... - **Endpoint / function**: ... - **Uncertainty**: ... - **Suggestion**: ...
阶段 3:合并 — 汇总批次结果
在所有阶段 2 批次子 Agent 完成后,读取每个 sast/graphql-batch-*.md 文件,并将它们合并到单个 sast/graphql-results.md 中。由你(协调者)直接完成此操作——无需子 Agent。
合并步骤:
- 读取所有
sast/graphql-batch-1.md、sast/graphql-batch-2.md等文件。 - 收集各批次文件中的所有发现,并将它们合并为一个列表,同时保留原始分类和所有详细字段。
- 汇总所有批次的总数,用于执行摘要。
- 使用以下格式将合并后的报告写入
sast/graphql-results.md:
# GraphQL Injection Analysis Results: [Project Name]
## Executive Summary
- Candidate sites analyzed: [total across all batches]
- Vulnerable: [N]
- Likely Vulnerable: [N]
- Not Vulnerable: [N]
- Needs Manual Review: [N]
## Findings
[All findings from all batches, grouped by classification:
VULNERABLE first, then LIKELY VULNERABLE, then NEEDS MANUAL REVIEW, then NOT VULNERABLE.
Preserve every field from the batch results exactly as written.]
- 写入
sast/graphql-results.md后,删除所有中间批次文件(sast/graphql-batch-*.md)。
重要提醒
- 读取
sast/architecture.md,并将其内容作为上下文传递给所有子 Agent。 - 如果阶段 1 未发现 GraphQL 技术,则跳过阶段 2 和阶段 3——写入“未检测到 GraphQL 技术”结果文件。
- 如果使用了 GraphQL,但阶段 1 未发现任何注入候选项,则跳过阶段 2 和阶段 3——写入“未发现漏洞”。
- 阶段 2 必须在阶段 1 完成后运行——它依赖侦察输出。
- 阶段 3 必须在所有阶段 2 批次完成后运行——它依赖所有批次输出。
- 每个子 Agent 的批次大小为每个 3 个候选站点。如果总共有 1–3 个站点,则使用单个子 Agent。如果有 10 个,则使用 4 个子 Agent(3+3+3+1)。
- 并行启动所有批次子 Agent——不要按顺序运行。
- 每个批次子 Agent 仅接收侦察文件中分配给它的候选项文本,而非整个侦察文件。这样可以让每个子 Agent 的上下文保持精简且聚焦。
- 阶段 1 不追踪污点;阶段 2 会追踪。
- Resolver 层的 SQL/NoSQL 问题属于其他技能;本技能针对的是操作文档构造。
- 如有疑问,应归类为“需要人工审核”,而非“无漏洞”。
- 清理中间文件:写入最终的
sast/graphql-results.md后,删除sast/graphql-recon.md和所有sast/graphql-batch-*.md文件。