mcpField
P
performing-api-fuzzing-with-restler

performing-api-fuzzing-with-restler

github.com/mukul975安全35.2k180 次安装8 周前更新
代码仓库

关于 performing-api-fuzzing-with-restler

面向 API 安全测试人员的 RESTler 模糊测试操作指南,使用 OpenAPI 规范生成测试语法,并支持有状态请求序列测试与漏洞分类。

主要功能

  1. 01从 OpenAPI 生成模糊测试语法
  2. 02测试有状态 API 请求序列
  3. 03配置身份验证与测试参数
  4. 04运行快速或完整模糊测试
  5. 05按类别整理发现的问题

适用场景

  1. 01在暂存环境测试 REST API
  2. 02发布前检查 API 变更
  3. 03发现特定调用顺序才出现的缺陷
  4. 04在 CI/CD 中执行安全回归测试

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mukul975/anthropic-cybersecurity-skills --skill performing-api-fuzzing-with-restler

安装后在 Claude Code 中输入/performing-api-fuzzing-with-restler确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

LICENSE
references/
api-reference.md
scripts/
agent.py

详细介绍

使用 RESTler 执行 API 模糊测试

适用场景

  • 使用 REST API 的 OpenAPI/Swagger 规范对其进行自动化安全测试
  • 发现仅在特定 API 调用序列下才会出现的缺陷(有状态测试)
  • 发现指示未处理异常或崩溃状况的 500 Internal Server Error 响应
  • 通过向参数注入畸形、边界和注入负载来测试 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 安装与设置
# Clone and build RESTler
git clone https://github.com/microsoft/restler-fuzzer.git
cd restler-fuzzer

# Build RESTler
python3 ./build-restler.py --dest_dir /opt/restler

# Verify installation
/opt/restler/restler/Restler --help

# Alternative: Use pre-built release
# Download from https://github.com/microsoft/restler-fuzzer/releases
第 2 步:编译 API 规范
# Compile the OpenAPI spec into a RESTler fuzzing grammar
/opt/restler/restler/Restler compile \
    --api_spec /path/to/openapi.yaml

# Output directory structure:
# Compile/
#   grammar.py          - Generated fuzzing grammar
#   grammar.json        - Grammar in JSON format
#   dict.json           - Custom dictionary for fuzzing values
#   engine_settings.json - Engine configuration
#   config.json         - Compilation config

用于定向模糊测试的自定义字典(dict.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 步:配置身份验证
# authentication_token.py - RESTler authentication module
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": "[email protected]",
            "password": "FuzzerPass123!"
        }

    def get_token(self):
        """Get or refresh authentication token."""
        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  # Assume 1-hour TTL
            return self.token
        else:
            raise Exception(f"Authentication failed: {resp.status_code}")

    def get_auth_header(self):
        """Return the authentication header for RESTler."""
        token = self.get_token()
        return f"Authorization: Bearer {token}"

# Export the token refresh command for RESTler
auth = AuthenticationProvider()
print(auth.get_auth_header())

身份验证的引擎设置(engine_settings.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(冒烟测试)
# Test mode: Quick validation that all endpoints are reachable
/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

# Review test results
cat Test/ResponseBuckets/runSummary.json
# Parse test results
import json

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

print("Test Mode Summary:")
print(f"  Total requests: {summary.get('total_requests_sent', {}).get('num_requests', 0)}")
print(f"  Successful (2xx): {summary.get('num_fully_valid', 0)}")
print(f"  Client errors (4xx): {summary.get('num_invalid', 0)}")
print(f"  Server errors (5xx): {summary.get('num_server_error', 0)}")

# Identify uncovered endpoints
covered = summary.get('covered_endpoints', [])
total = summary.get('total_endpoints', [])
uncovered = set(total) - set(covered)
if uncovered:
    print(f"\nUncovered endpoints ({len(uncovered)}):")
    for ep in uncovered:
        print(f"  - {ep}")
第 5 步:运行 Fuzz-Lean 模式
# Fuzz-lean: One pass through all endpoints with security checkers enabled
/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 hour max

# Checkers automatically enabled in fuzz-lean:
# - UseAfterFree: Tests accessing resources after deletion
# - NamespaceRule: Tests accessing resources across namespaces/tenants
# - ResourceHierarchy: Tests child resources with wrong parent IDs
# - LeakageRule: Tests for information disclosure in error responses
# - InvalidDynamicObject: Tests with malformed dynamic object IDs
第 6 步:运行完整模糊测试模式
# Full fuzz mode: Extended fuzzing for comprehensive coverage
/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

# Analyze fuzzing results
python3 <<'EOF'
import json
import os

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

# Parse 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: {bug_file} ===")
            print(content[:500])

# Parse response summary
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"\nFuzz Summary:")
    print(f"  Duration: {summary.get('time_budget_hours', 0)} hours")
    print(f"  Total requests: {summary.get('total_requests_sent', {}).get('num_requests', 0)}")
    print(f"  Bugs found: {summary.get('num_bugs', 0)}")
    print(f"  500 errors: {summary.get('num_server_error', 0)}")
EOF

关键概念

术语定义
有状态模糊测试通过将较早请求的响应作为后续请求的输入,在请求之间保持状态的 API 模糊测试,从而支持对多步骤工作流进行测试
生产者-消费者依赖关系RESTler 的推断:某个 API 调用产生的值(例如已创建资源的 ID)应由后续调用消费
模糊测试语法API 规范的编译表示,定义了如何为每个端点生成有效和无效请求
检查器RESTler 安全规则,用于测试特定漏洞模式,例如释放后使用、命名空间隔离或信息泄露
缺陷桶RESTler 按类型和端点对发现的缺陷进行分类,将相似的失败分组以便高效分流
垃圾回收RESTler 定期清理模糊测试期间创建的资源,以防止目标系统资源耗尽

工具与系统

  • RESTler:Microsoft Research 的有状态 REST API 模糊测试工具,可将 OpenAPI 规范编译为模糊测试语法
  • Schemathesis:基于属性的 API 测试工具,可根据 OpenAPI/GraphQL 模式生成测试用例
  • Dredd:API 测试工具,可依据 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 个服务运行完整 fuzz 模式(每个服务 4 小时)
  7. 发现 47 个额外缺陷,其中包括一个严重的身份验证绕过:删除用户后复用其令牌仍可访问
  8. 生成缺陷报告,并通过 JIRA 集成跟踪修复

陷阱:

  • 在对 RESTler 会创建和删除数千个资源缺乏了解的情况下,就对生产环境运行 RESTler
  • 未正确配置身份验证,导致 RESTler 仅测试未认证访问
  • 使用默认字典而未添加应用专属的注入载荷
  • 未设置时间预算,导致 RESTler 无限期运行
  • 忽略编译警告,这些警告表明 RESTler 因依赖问题而无法访问某些端点

输出格式

## RESTler API Fuzzing Report

**Target**: User Service API (staging.example.com)
**Specification**: OpenAPI 3.0 (42 endpoints)
**Duration**: 4 hours (full fuzz mode)
**Total Requests**: 145,832

### Bug Summary

| Category | Count | Severity |
|----------|-------|----------|
| 500 Internal Server Error | 12 | High |
| Use After Free | 3 | Critical |
| Namespace Rule Violation | 5 | Critical |
| Information Leakage | 8 | Medium |
| Resource Leak | 4 | Low |

### Critical Findings

**1. Use-After-Free: Deleted user token still valid**
- Sequence: POST /users -> DELETE /users/{id} -> GET /users/{id}
- After deleting user, GET with the deleted user's token returns 200
- Impact: Deleted accounts can still access the API

**2. Namespace Violation: Cross-tenant data access**
- Sequence: POST /users (tenant A) -> GET /users/{id} (tenant B token)
- User created by tenant A is accessible with tenant B's credentials
- Impact: Multi-tenant isolation breach

**3. 500 Error: Unhandled integer overflow**
- Request: POST /orders {"quantity": 2147483648}
- Response: 500 Internal Server Error with stack trace
- Impact: DoS potential, information disclosure via stack trace

### Coverage

- Endpoints covered: 38/42 (90.5%)
- Uncovered: POST /admin/migrate, DELETE /admin/cache,
  PUT /config/advanced, POST /webhooks/test

评论

支持 Markdown