mcpField
S
sast-graphql

sast-graphql

github.com/utkusen安全1.3k70 次安装5 天前更新
代码仓库

关于 sast-graphql

sast-graphql 是用于代码库安全评估的 GraphQL 注入检测技能,面向安全审查人员。它先确认 GraphQL 使用情况并定位动态拼接操作文档,再分批追踪输入并汇总报告。

主要功能

  1. 01确认 GraphQL 技术使用情况
  2. 02定位动态构造操作文档的位置
  3. 03分批并行追踪用户输入
  4. 04汇总核查结果
  5. 05要求预先准备架构摘要

适用场景

  1. 01审查 GraphQL 操作文档构造
  2. 02检查输入是否改变查询文本
  3. 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 操作文档(查询/变更/订阅源字符串)的位置,使变量可能改变文档文本(而不仅仅是 variables JSON)。将结果写入 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,请搜索不安全的文档构造:

  1. 拼接字符串/将内容插入操作文本:
    • `query { ... ${x} ...}`、"mutation { " + userFragment + " }"
    • 使用 sprintf、format、% 格式化、.format() 来构建 query 或 source 参数
  2. 文档参数不是编译时常量的调用:
    • graphql(schema, dynamicString, ...)、execute({ schema, document: parsedDynamic, ...}),其中传给 parse 或 execute 的字符串由非静态部分构建而成
    • graphqlHTTP({ schema, rootValue, context: (req) => ({ query: req.body.query + something }) }) 这类会用用户数据修改或包装查询字符串的模式
  3. 转发构造好的 GraphQL 请求体的 HTTP 客户端:
    • JSON.stringify({ query: ...${userPart}... })、axios.post(url, { query: builtFromInput })
  4. 不安全的持久化/存储查询查找:
    • 根据用户输入的键加载操作文本,但未设置允许列表,导致文件路径或数据库值成为文档源

跳过的内容(不要标记为第 1 阶段候选项):

  • 完全静态的 source/query 字符串;只有 variableValues/variables 来自请求
  • 没有用户插值的 buildSchema Schema 定义/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):

  1. 读取 sast/graphql-recon.md,统计“Injection Candidate Sites”下编号候选小节的数量(### 1.、### 2. 等)。
  2. 将它们划分为每批最多 3 个。例如,8 个站点 → 3 批(1-3、4-6、7-8)。
  3. 从 recon 文件中提取每批候选小节的完整文本。
  4. 并行启动所有批次子 Agent,并且只向每个子 Agent 提供其分配的候选小节(以及架构上下文)。
  5. 每个子 Agent 将结果写入 sast/graphql-batch-N.md,其中 N 为从 1 开始的批次编号。
  6. 从 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]

针对每个指定点,向上追踪动态值的来源:

  1. 直接用户输入 — 查询参数、路径参数、JSON 请求体字段(包括重新包装时的嵌套 query)、请求头、Cookie
  2. 间接用户输入 — 辅助函数、中间件、上下文构建器
  3. 二阶输入 — 后续用于构建文档的已存储偏好设置或数据库字段;追踪写入路径
  4. 仅限服务端 — 配置、环境变量、硬编码片段 — 无法由客户端利用

分类:

  • 易受攻击:用户可控数据到达文档构造过程,且没有有效的缓解措施
  • 可能易受攻击:可能存在污点,或清理措施较弱
  • 未发现漏洞:仅服务端数据,或采用了有效的允许列表/静态文档路径
  • 需要人工审查:数据流不透明

输出格式 — 写入 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。

合并步骤:

  1. 读取所有 sast/graphql-batch-1.md、sast/graphql-batch-2.md 等文件。
  2. 收集各批次文件中的所有发现,并将它们合并为一个列表,同时保留原始分类和所有详细字段。
  3. 汇总所有批次的总数,用于执行摘要。
  4. 使用以下格式将合并后的报告写入 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.]
  1. 写入 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 文件。

评论

支持 Markdown