# Performing API Fuzzing With Restler

> 使用 Microsoft RESTler 执行有状态 REST API 模糊测试（Fuzzing），通过自动生成并执行测试序列来 覆盖 API 端点，发现请求间的生产者-消费者依赖关系，并找出安全性和可靠性缺陷。 测试人员将 OpenAPI 规范编译为 RESTler 模糊测试语法，配置认证，运行 test/fuzz-lean/fuzz 模式，并分析结果以发现 500 错误、认证绕过、资源泄漏和载荷注入漏洞。 当请求涉及 API 模糊测试、RESTler 测试、有状态 API 测试或自动化 API 安全扫描时触发。

- Skill: `killvxk/performing-api-fuzzing-with-restler` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add killvxk/performing-api-fuzzing-with-restler`
- Raw SKILL.md: https://api.skillmd.com/api/skills/killvxk/performing-api-fuzzing-with-restler/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- License: Apache-2.0
- Author: killvxk (https://skillmd.com/u/killvxk)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/killvxk/performing-api-fuzzing-with-restler

---

# 使用 RESTler 执行 API 模糊测试

## 适用场景

- 使用 OpenAPI/Swagger 规范对 REST API 执行自动化安全测试
- 发现只有在特定 API 调用序列下才会出现的缺陷（有状态测试）
- 查找表明存在未处理异常或崩溃条件的 500 内部服务器错误响应
- 通过使用畸形、边界值和注入载荷对参数进行模糊测试来测试 API 输入验证
- 在 CI/CD 流水线中针对 API 变更运行持续的安全回归测试

**不适用于** 在未经明确授权和监控的情况下针对生产环境使用。RESTler 在模糊测试期间会大量创建和删除资源。

## 前置条件

- 明确授权文件，指定目标 API 和可接受的测试范围
- 已安装 Python 3.12+ 和 .NET 8.0 运行时
- 已从 https://github.com/microsoft/restler-fuzzer 下载 RESTler
- 目标 API 的 OpenAPI/Swagger 规范（v2 或 v3）
- API 认证凭据（令牌、API 密钥或 OAuth 凭据）
- 隔离的测试/预发布环境（RESTler 每小时可创建数千个资源）

## 工作流程

### 步骤 1：RESTler 安装与配置

```bash
# 克隆并构建 RESTler
git clone https://github.com/microsoft/restler-fuzzer.git
cd restler-fuzzer

# 构建 RESTler
python3 ./build-restler.py --dest_dir /opt/restler

# 验证安装
/opt/restler/restler/Restler --help

# 替代方案：使用预构建的发布版本
# 从 https://github.com/microsoft/restler-fuzzer/releases 下载
```

### 步骤 2：编译 API 规范

```bash
# 将 OpenAPI 规范编译为 RESTler 模糊测试语法
/opt/restler/restler/Restler compile \
    --api_spec /path/to/openapi.yaml

# 输出目录结构：
# Compile/
#   grammar.py          - 生成的模糊测试语法
#   grammar.json        - JSON 格式的语法
#   dict.json           - 用于模糊测试值的自定义字典
#   engine_settings.json - 引擎配置
#   config.json         - 编译配置
```

**用于定向模糊测试的自定义字典（dict.json）：**
```json
{
    "restler_fuzzable_string": [
        "fuzzstring",
        "' OR '1'='1",
        "\" OR \"1\"=\"1",
        "<script>alert(1)</script>",
        "../../../etc/passwd",
        "${7*7}",
        "{{7*7}}",
        "a]UNION SELECT 1,2,3--",
        "\"; cat /etc/passwd; echo \"",
        "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
    ],
    "restler_fuzzable_int": [
        "0",
        "-1",
        "999999999",
        "2147483647",
        "-2147483648"
    ],
    "restler_fuzzable_bool": ["true", "false", "null", "1", "0"],
    "restler_fuzzable_datetime": [
        "2024-01-01T00:00:00Z",
        "0000-00-00T00:00:00Z",
        "9999-12-31T23:59:59Z",
        "invalid-date"
    ],
    "restler_fuzzable_uuid4": [
        "00000000-0000-0000-0000-000000000000",
        "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
    ],
    "restler_custom_payload": {
        "/users/{userId}": ["1", "0", "-1", "admin", "' OR 1=1--"],
        "/orders/{orderId}": ["1", "0", "999999999"]
    }
}
```

### 步骤 3：配置认证

```python
# authentication_token.py - RESTler 认证模块
import requests
import json
import time

class AuthenticationProvider:
    def __init__(self):
        self.token = None
        self.token_expiry = 0
        self.auth_url = "https://target-api.example.com/api/v1/auth/login"
        self.credentials = {
            "email": "fuzzer@test.com",
            "password": "FuzzerPass123!"
        }

    def get_token(self):
        """获取或刷新认证令牌。"""
        current_time = time.time()
        if self.token and current_time < self.token_expiry - 60:
            return self.token

        resp = requests.post(self.auth_url, json=self.credentials)
        if resp.status_code == 200:
            data = resp.json()
            self.token = data["access_token"]
            self.token_expiry = current_time + 3600  # 假设 1 小时有效期
            return self.token
        else:
            raise Exception(f"认证失败：{resp.status_code}")

    def get_auth_header(self):
        """返回 RESTler 使用的认证请求头。"""
        token = self.get_token()
        return f"Authorization: Bearer {token}"

# 导出 RESTler 使用的令牌刷新命令
auth = AuthenticationProvider()
print(auth.get_auth_header())
```

**用于认证的引擎设置（engine_settings.json）：**
```json
{
    "authentication": {
        "token": {
            "token_refresh_interval": 300,
            "token_refresh_cmd": "python3 /path/to/authentication_token.py"
        }
    },
    "max_combinations": 20,
    "max_request_execution_time": 30,
    "global_producer_timing_delay": 2,
    "no_ssl": false,
    "host": "target-api.example.com",
    "target_port": 443,
    "garbage_collection_interval": 300,
    "max_sequence_length": 10
}
```

### 步骤 4：以测试模式运行 RESTler（冒烟测试）

```bash
# 测试模式：快速验证所有端点是否可达
/opt/restler/restler/Restler test \
    --grammar_file Compile/grammar.py \
    --dictionary_file Compile/dict.json \
    --settings Compile/engine_settings.json \
    --no_ssl \
    --target_ip target-api.example.com \
    --target_port 443

# 查看测试结果
cat Test/ResponseBuckets/runSummary.json
```

```python
# 解析测试结果
import json

with open("Test/ResponseBuckets/runSummary.json") as f:
    summary = json.load(f)

print("测试模式摘要：")
print(f"  总请求数：{summary.get('total_requests_sent', {}).get('num_requests', 0)}")
print(f"  成功（2xx）：{summary.get('num_fully_valid', 0)}")
print(f"  客户端错误（4xx）：{summary.get('num_invalid', 0)}")
print(f"  服务器错误（5xx）：{summary.get('num_server_error', 0)}")

# 识别未覆盖的端点
covered = summary.get('covered_endpoints', [])
total = summary.get('total_endpoints', [])
uncovered = set(total) - set(covered)
if uncovered:
    print(f"\n未覆盖的端点（{len(uncovered)} 个）：")
    for ep in uncovered:
        print(f"  - {ep}")
```

### 步骤 5：运行 Fuzz-Lean 模式

```bash
# fuzz-lean：启用安全检查器对所有端点进行一轮遍历
/opt/restler/restler/Restler fuzz-lean \
    --grammar_file Compile/grammar.py \
    --dictionary_file Compile/dict.json \
    --settings Compile/engine_settings.json \
    --target_ip target-api.example.com \
    --target_port 443 \
    --time_budget 1  # 最长 1 小时

# fuzz-lean 中自动启用的检查器：
# - UseAfterFree：测试删除后访问资源
# - NamespaceRule：测试跨命名空间/租户访问资源
# - ResourceHierarchy：使用错误父资源 ID 测试子资源
# - LeakageRule：测试错误响应中的信息泄露
# - InvalidDynamicObject：使用畸形动态对象 ID 进行测试
```

### 步骤 6：运行完整模糊测试模式

```bash
# 完整模糊测试模式：扩展模糊测试以获得全面覆盖
/opt/restler/restler/Restler fuzz \
    --grammar_file Compile/grammar.py \
    --dictionary_file Compile/dict.json \
    --settings Compile/engine_settings.json \
    --target_ip target-api.example.com \
    --target_port 443 \
    --time_budget 4 \
    --enable_checkers UseAfterFree NamespaceRule ResourceHierarchy LeakageRule InvalidDynamicObject PayloadBody

# 分析模糊测试结果
python3 <<'EOF'
import json
import os

results_dir = "Fuzz/ResponseBuckets"
bugs_dir = "Fuzz/bug_buckets"

# 解析缺陷桶
if os.path.exists(bugs_dir):
    for bug_file in os.listdir(bugs_dir):
        if bug_file.endswith(".txt"):
            with open(os.path.join(bugs_dir, bug_file)) as f:
                content = f.read()
            print(f"\n=== 缺陷：{bug_file} ===")
            print(content[:500])

# 解析响应摘要
summary_file = os.path.join(results_dir, "runSummary.json")
if os.path.exists(summary_file):
    with open(summary_file) as f:
        summary = json.load(f)
    print(f"\n模糊测试摘要：")
    print(f"  持续时间：{summary.get('time_budget_hours', 0)} 小时")
    print(f"  总请求数：{summary.get('total_requests_sent', {}).get('num_requests', 0)}")
    print(f"  发现缺陷数：{summary.get('num_bugs', 0)}")
    print(f"  500 错误数：{summary.get('num_server_error', 0)}")
EOF
```

## 核心概念

| 术语 | 定义 |
|------|------|
| **有状态模糊测试（Stateful Fuzzing）** | 通过使用早期请求的响应作为后续请求的输入来维持跨请求状态的 API 模糊测试，可测试多步骤工作流 |
| **生产者-消费者依赖（Producer-Consumer Dependencies）** | RESTler 的推断机制：一个 API 调用产生的值（如创建的资源 ID）应被后续调用所消费 |
| **模糊测试语法（Fuzzing Grammar）** | API 规范的编译表示，定义如何为每个端点生成有效和无效的请求 |
| **检查器（Checker）** | 测试特定漏洞模式（如释放后使用、命名空间隔离或信息泄露）的 RESTler 安全规则 |
| **缺陷桶（Bug Bucket）** | RESTler 按类型和端点对发现的缺陷进行分类，将类似故障分组以便高效分诊 |
| **垃圾回收（Garbage Collection）** | RESTler 定期清理在模糊测试期间创建的资源，防止目标系统资源耗尽 |

## 工具与系统

- **RESTler**：Microsoft Research 的有状态 REST API 模糊测试工具，将 OpenAPI 规范编译为模糊测试语法
- **Schemathesis**：基于属性的 API 测试工具，从 OpenAPI/GraphQL 模式生成测试用例
- **Dredd**：根据 OpenAPI/API Blueprint 文档验证 API 实现的测试工具
- **Fuzz-lightyear**：Yelp 的无状态 API 模糊器，专注于发现认证和授权漏洞
- **API Fuzzer**：OWASP 的 API 端点模糊测试工具，支持自定义载荷字典

## 常见场景

### 场景：微服务 API 模糊测试活动

**场景背景**：一家金融科技公司拥有 12 个配有 OpenAPI 规范的微服务 API。在重大发布前，安全团队在预发布环境中对每个服务运行 RESTler 模糊测试以发现缺陷。

**方法**：
1. 收集所有 12 个服务的 OpenAPI 规范，将每个规范编译为 RESTler 语法
2. 使用服务专属凭据为每个服务配置认证
3. 对每个服务运行测试模式，验证端点可达性并修复语法问题
4. 对每个服务运行 fuzz-lean 模式（每服务 1 小时）以快速发现问题
5. 在 fuzz-lean 模式中发现 23 个缺陷：8 个未处理的 500 错误、5 个释放后使用模式、4 个命名空间隔离失败、6 个错误响应中的信息泄露
6. 对缺陷最多的 5 个服务运行完整模糊测试模式（每服务 4 小时）
7. 发现 47 个额外缺陷，包括一个严重的认证绕过：删除用户后重用其令牌仍可访问
8. 生成缺陷报告并通过 JIRA 集成跟踪修复进度

**常见陷阱**：
- 未了解 RESTler 会创建和删除数千个资源，就对生产环境运行
- 未正确配置认证，导致 RESTler 只测试未认证访问
- 使用默认字典，未添加应用特定的注入载荷
- 未设置时间预算，导致 RESTler 无限运行
- 忽略编译警告，这些警告指示 RESTler 因依赖问题无法到达的端点

## 输出格式

```
## RESTler API 模糊测试报告

**目标**：用户服务 API（staging.example.com）
**规范**：OpenAPI 3.0（42 个端点）
**持续时间**：4 小时（完整模糊测试模式）
**总请求数**：145,832

### 缺陷摘要

| 类别 | 数量 | 严重性 |
|----------|-------|----------|
| 500 内部服务器错误 | 12 | 高 |
| 释放后使用 | 3 | 严重 |
| 命名空间规则违规 | 5 | 严重 |
| 信息泄露 | 8 | 中 |
| 资源泄漏 | 4 | 低 |

### 关键发现

**1. 释放后使用：已删除用户的令牌仍然有效**
- 序列：POST /users -> DELETE /users/{id} -> GET /users/{id}
- 删除用户后，使用已删除用户的令牌发送 GET 请求返回 200
- 影响：已删除账户仍可访问 API

**2. 命名空间违规：跨租户数据访问**
- 序列：POST /users（租户 A）-> GET /users/{id}（租户 B 令牌）
- 租户 A 创建的用户可通过租户 B 的凭据访问
- 影响：多租户隔离被突破

**3. 500 错误：未处理的整数溢出**
- 请求：POST /orders {"quantity": 2147483648}
- 响应：500 内部服务器错误，包含堆栈跟踪
- 影响：DoS 潜力，通过堆栈跟踪造成信息泄露

### 覆盖率

- 端点覆盖：38/42（90.5%）
- 未覆盖：POST /admin/migrate、DELETE /admin/cache、
  PUT /config/advanced、POST /webhooks/test
```

