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安全, 图数据库, 密码管理, 文档结构分析, 日志审计, 蓝队分析