omkoli/GQLS-CLI

GitHub: omkoli/GQLS-CLI

一款专为 GraphQL 端点设计的命令行安全扫描器,用于检测内省暴露、查询深度/复杂度攻击、批量滥用及注入等常见漏洞。

Stars: 4 | Forks: 0

# gqls GraphQL 安全扫描器。探测 GraphQL endpoint 的常见配置错误和漏洞。从 URL flag、`--header` flag,或从浏览器 DevTools 复制的原始 `curl` 命令中读取目标信息。 ## 目录 - [安装](#install) - [快速开始](#quick-start) - [扫描命令](#scan-command) - [Flags 参考](#flags-reference) - [curl 输入](#curl-input) - [配置文件](#configuration-file) - [环境变量](#environment-variables) - [输出格式](#output-formats) - [检查](#checks) - [退出代码](#exit-codes) - [故障排除](#troubleshooting) ## 安装 **从源码安装(需要 Go 1.24+)** ``` git clone https://github.com/omkoli/GQLS-CLI.git cd gqls go build -o gqls ./cmd/gqls ``` Homebrew(macOS / Linux) ``` brew tap omkoli/gqls brew install gqls ``` 或者直接通过单条命令安装: ``` brew install omkoli/gqls/gqls ``` 嵌入版本字符串: ``` go build -ldflags "-X main.Version=1.2.3" -o gqls ./cmd/gqls ``` 将二进制文件移动到 `$PATH` 中的某个位置: ``` mv gqls /usr/local/bin/gqls ``` ## 快速开始 ``` # 使用 URL 进行最小扫描 gqls scan --url https://api.example.com/graphql # 使用 bearer token 进行扫描 gqls scan \ --url https://api.example.com/graphql \ --header 'Authorization: Bearer eyJ...' # 粘贴从浏览器 DevTools 复制的 curl 命令 gqls scan --curl 'curl https://api.example.com/graphql \ -H "Authorization: Bearer eyJ..." \ -H "Content-Type: application/json" \ --data-raw '"'"'{"query":"{ __typename }"}'"'"'' # 将发现结果保存到 SARIF 文件;遇到任何 CRITICAL 发现时以退出码 1 退出 gqls scan \ --url https://api.example.com/graphql \ --output sarif \ --output-file results.sarif \ --fail-on CRITICAL ``` ## 扫描命令 ``` gqls scan [flags] ``` 必须通过 `--url`、`--curl` 或 `--curl-file` 提供目标 URL;其余 flags 为可选。 ## Flags 参考 | Flag | 默认值 | 描述 | |---|---|---| | `--url ` | — | GraphQL endpoint URL。除非由 `--curl` 或 `--curl-file` 提供,否则为必填。 | | `--header ` | — | 添加到每个请求的 HTTP header。可重复。覆盖来自 `--curl` / `--curl-file` 的同名 header。 | | `--curl ` | — | 内联的原始 curl 命令字符串。提取 URL、header 和 body。 | | `--curl-file ` | — | 包含原始 curl 命令的文件路径。支持 Bash (`\`) 和 Windows CMD (`^`) 多行格式。 | | `--checks ` | all | 仅运行列出的检查 ID(以逗号分隔或重复使用 flag)。 | | `--skip-checks ` | — | 跳过列出的检查 ID。 | | `--output ` | `terminal` | 输出格式:`terminal`、`txt`、`json`、`sarif`。 | | `--output-file ` | stdout | 将报告写入此文件而不是 stdout。 | | `--fail-on ` | `HIGH` | 当任何发现的结果达到或超过此严重程度时以状态码 1 退出。可选值为 `INFO`、`LOW`、`MEDIUM`、`HIGH`、`CRITICAL`、`none` 之一。 | | `--no-color` | false | 在终端输出中禁用 ANSI 颜色代码。 | | `--timeout ` | `30s` | 每个请求的 HTTP 超时时间(例如 `10s`、`2m`)。 | | `--rate-limit ` | `10` | 每秒最大 HTTP 请求数。 | | `--config ` | — | `gqls.yaml` 配置文件的路径。 | ## curl 输入 `--curl` 和 `--curl-file` 接受从浏览器 DevTools 复制或手动构造的原始 curl 命令。解析器在不执行任何 shell 进程的情况下提取 URL、HTTP 方法、header 和请求 body。 **支持的语法** - Bash 风格的换行符(`\` + 换行) - Windows CMD 风格的换行符(`^` + 换行) - Windows CMD 内联转义序列(`^"`、`^^`) - 单引号字符串、双引号字符串、ANSI-C 引号字符串(`$'...'`) - `curl.exe` 前缀(标准化为 `curl`) - 排版/智能引号(标准化为 ASCII) - Flags:`-X`/`--request`、`-H`/`--header`、`-d`/`--data`/`--data-raw`/`--data-binary`、`--url` - 方法推断:存在 body 时为 `POST`,否则为 `GET` **合并规则** 当 `--curl` / `--curl-file` 与 `--url` 或 `--header` 结合使用时: - `--url` 优先于 curl 命令中的 URL。 - `--header` 值覆盖从 curl 命令中提取的同名 header。 **内联示例** ``` gqls scan --curl 'curl -X POST https://api.example.com/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer eyJ..." \ --data-raw "{\"query\":\"{ users { id email } }\"}"' ``` **文件示例** ``` # curl.txt curl 'https://api.example.com/graphql' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer eyJ...' \ --data-raw '{"query":"{ users { id email } }"}' ``` ``` gqls scan --curl-file curl.txt ``` ## 配置文件 gqls 会依次在当前目录和 `$HOME/.gqls/gqls.yaml` 中查找 `gqls.yaml`。使用 `--config` 指定显式路径。 **优先级(从低到高):** 配置文件 → 环境变量 → CLI flags。 ``` url: https://api.example.com/graphql headers: Authorization: "Bearer ${API_TOKEN}" X-Tenant-ID: "acme" timeout: 60s rate_limit: 5 output_format: json output_file: report.json fail_on: HIGH no_color: false checks: [] # empty = run all skip_checks: - GQL-004 false_positives: - "a1b2c3d4e5f6..." # SHA-256 fingerprint of a known-safe finding ``` Header 值可以使用 `${VAR_NAME}` 语法引用环境变量;它们会在扫描时展开。 ## 环境变量 所有设置都可以使用 `GQLS_` 前缀作为环境变量提供。环境变量会覆盖配置文件中的值,但会被 CLI flags 覆盖。 | 变量 | 等效 flag | |---|---| | `GQLS_URL` | `--url` | | `GQLS_OUTPUT_FORMAT` | `--output` | | `GQLS_OUTPUT_FILE` | `--output-file` | | `GQLS_FAIL_ON` | `--fail-on` | | `GQLS_NO_COLOR` | `--no-color` | | `GQLS_TIMEOUT` | `--timeout` | | `GQLS_RATE_LIMIT` | `--rate-limit` | ## 输出格式 ### terminal(默认) 带 ANSI 颜色、人类可读的输出。每个发现块包含: ``` [ HIGH ] GQL-001 — Introspection Enabled ──────────────────────────────────────────────────────────────────────── WHAT WAS FOUND GraphQL introspection is enabled at https://api.example.com/graphql. ... REPRODUCE IT curl -X POST \ 'https://api.example.com/graphql' \ -H 'Content-Type: application/json' \ --data-raw '{"query":"{ __schema { types { name } } }"}' ATTACKER IMPACT An attacker can enumerate the entire API surface ... FIX Disable introspection in production environments. ... REFERENCES • https://... ``` 随后是摘要表: ``` SCAN SUMMARY ──────────────────────────────────────────────────────────────────────── Checks run : 12 Duration : 4.231s Requests made : 38 Findings by severity: CRITICAL ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0 HIGH ████████░░░░░░░░░░░░░░░░░░░░░░ 2 MEDIUM ████░░░░░░░░░░░░░░░░░░░░░░░░░░ 1 LOW ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0 INFO ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0 ``` 对于不解释转义序列的 CI 环境,使用 `--no-color` 去除 ANSI 代码。 ### txt 没有 ANSI 代码或 JSON 的纯文本报告。适合附加到工单或电子邮件中。包含部分:header、发现索引、各个发现(带有用于复现的 curl)、通过的检查、跳过的检查、带有稳定报告 ID 的页脚。 ``` gqls scan --url https://api.example.com/graphql --output txt --output-file report.txt ``` ### json 带缩进的 JSON 对象。顶层结构: ``` { "ChecksRun": 12, "Duration": 4231000000, "RequestsMade": 38, "StartTime": "2026-02-28T12:00:00Z", "Findings": [ { "CheckID": "GQL-001", "CheckName": "Introspection Enabled", "Severity": "HIGH", "Category": "InformationDisclosure", "Title": "...", "Description": "...", "Impact": "...", "Remediation": "...", "References": ["..."], "Fingerprint": "a1b2c3..." } ], "Schema": { ... }, "CheckResults": [ { "CheckID": "GQL-001", "Ran": true, "Skipped": false, "SkipReason": "", "PassReason": "", "Findings": [...], "Duration": 210000000, "ProbeCount": 3 } ] } ``` `Duration` 和每次检查的 `Duration` 值以纳秒为单位。`Severity` 序列化为字符串(`"INFO"`、`"LOW"`、`"MEDIUM"`、`"HIGH"`、`"CRITICAL"`)。 ``` gqls scan --url https://api.example.com/graphql --output json | jq '.Findings[].Severity' ``` ### sarif SARIF 2.1.0 JSON。规则在 `runs[0].tool.driver.rules` 下发出;结果在 `runs[0].results` 下。严重程度映射: | gqls 严重程度 | SARIF 级别 | |---|---| | CRITICAL, HIGH | `error` | | MEDIUM | `warning` | | LOW | `note` | | INFO | `none` | ``` gqls scan --url https://api.example.com/graphql --output sarif --output-file results.sarif ``` ## 检查 | ID | 名称 | 严重程度 | 类别 | |---|---|---|---| | GQL-001 | Introspection Enabled | HIGH | InformationDisclosure | | GQL-002 | Introspection Bypass via \_\_type | HIGH | InformationDisclosure | | GQL-003 | Schema Exposed via Field Suggestions | MEDIUM | InformationDisclosure | | GQL-004 | GraphQL Playground Exposed | MEDIUM | InformationDisclosure | | GQL-005 | Stack Trace / Debug Info in Error Responses | MEDIUM | InformationDisclosure | | GQL-006 | Sensitive Fields Exposed in Schema | INFO | InformationDisclosure | | GQL-007 | Query Depth Limit Not Enforced | HIGH | DenialOfService | | GQL-008 | Query Complexity Limit Not Enforced | HIGH | DenialOfService | | GQL-009 | Batch Query Abuse | HIGH | DenialOfService | | GQL-010 | GraphQL GET Queries Enabled | LOW | InformationDisclosure | | GQL-011 | SQL Injection (Error-Based) | CRITICAL | Injection | | GQL-012 | Unauthenticated Access to Mutations | HIGH | Authentication | GQL-006 和 GQL-007/GQL-008/GQL-012 需要可检索的 schema;当 schema 提取失败时,它们会自动被跳过。 **运行部分检查** ``` gqls scan --url https://api.example.com/graphql --checks GQL-001 --checks GQL-002 ``` **跳过特定检查** ``` gqls scan --url https://api.example.com/graphql --skip-checks GQL-004 --skip-checks GQL-010 ``` **通过指纹抑制已知的误报** 每个发现都包含一个稳定的 `Fingerprint`(检查 ID + 目标 + 证据 key 的 SHA-256)。将指纹添加到 `gqls.yaml` 的 `false_positives` 中,以在以后的扫描中抑制它。 ``` false_positives: - "a1b2c3d4e5f67890..." ``` ## 退出代码 | 代码 | 含义 | |---|---| | `0` | 扫描完成。没有发现的结果达到或超过 `--fail-on` 阈值(或指定了 `--fail-on none`)。 | | `1` | 扫描完成且至少有一个发现的结果达到或超过了 `--fail-on` 严重程度。在致命的启动错误(无效的 flags、无法读取的配置、错误的输出格式、无法读取的 `--curl-file`)时也会返回此代码。 | 默认的 `--fail-on` 阈值为 `HIGH`。设置 `--fail-on none` 可无论发现什么结果都始终以 `0` 退出。 **CI 用法** ``` gqls scan \ --url "$GRAPHQL_URL" \ --header "Authorization: Bearer $TOKEN" \ --output sarif \ --output-file results.sarif \ --fail-on HIGH echo "Exit: $?" ``` ## 故障排除 **尽管传递了 `--curl` 但仍提示 `--url is required`** curl 解析器无法提取 URL。验证 curl 字符串是否以 `curl`(或 `curl.exe`)开头,并包含有效的 `http://` 或 `https://` URL。如果命令跨越多个 shell 转义行,请使用 `--curl-file`。 **`parsing curl input: curl: unterminated single-quoted string`** curl 命令包含不对称的引号,通常是在从自动换行的终端复制命令时引入的。请将命令保存到文件并使用 `--curl-file` 传递。 **`warning: schema extraction failed`** 依赖 schema 的检查(GQL-006、GQL-007、GQL-008、GQL-012)将被跳过。原因: - 目标上的 Introspection 被禁用——这是预期的;如果 endpoint 有响应,GQL-001 将会触发。 - Endpoint 需要未提供的身份验证。请通过 `--header` 或 `--curl` 添加凭证。 - Endpoint 无法访问。请检查 `--url` 和网络连接。 **`error: schema extraction [stage]: message`** 在指定阶段的 schema 提取过程中发生致命错误。请检查 URL、身份验证 header 以及服务器是否返回有效的 JSON。 **在已知存在漏洞的 endpoint 上没有发现** - Endpoint 可能阻止了扫描程序探测 payload。使用 `--curl` 复制确切的浏览器请求,包括 cookies 和 CSRF token。 - 某些检查需要 schema 数据。如果提取失败,这些检查将被跳过(记录为 `requires schema (unavailable)`)。 - 服务器上的速率限制可能会导致超时。请降低 `--rate-limit` 或增加 `--timeout`。 **ANSI 代码显示为原始转义序列** 传递 `--no-color` 或设置环境变量 `NO_COLOR=1`。通过 `--output-file` 写入文件时,颜色会自动禁用。 **`invalid output format "…"`** 有效值包括 `terminal`、`txt`、`json`、`sarif`(不区分大小写)。 **复现 curl 中的 Authorization header 显示为 `[REDACTED]`** terminal 和 txt 报告器会在 `REPRODUCE IT` / `REPRODUCE` curl 命令中掩盖 `Authorization` header 值,以防止凭据被存储在报告文件中。在扫描过程中仍会发送真实的 header。
标签:EVTX分析, Go, GraphQL, Ruby工具, Web安全, 图数据库, 密码管理, 文档结构分析, 日志审计, 蓝队分析