关于 performing-api-fuzzing-with-restler
面向 API 安全测试人员的 RESTler 模糊测试操作指南,使用 OpenAPI 规范生成测试语法,并支持有状态请求序列测试与漏洞分类。
主要功能
- 01从 OpenAPI 生成模糊测试语法
- 02测试有状态 API 请求序列
- 03配置身份验证与测试参数
- 04运行快速或完整模糊测试
- 05按类别整理发现的问题
适用场景
- 01在暂存环境测试 REST API
- 02发布前检查 API 变更
- 03发现特定调用顺序才出现的缺陷
- 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 外,安装时一起下载的参考资料和脚本。
LICENSEreferences/api-reference.mdscripts/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 模糊测试以发现缺陷。
方法:
- 收集全部 12 个服务的 OpenAPI 规范,并将每个规范编译为 RESTler 语法
- 为每个服务配置身份验证,使用服务专属凭据
- 对每个服务运行测试模式,验证端点可达性并修复语法问题
- 运行 fuzz-lean 模式(每个服务 1 小时),以识别快速见效的问题
- 在 fuzz-lean 模式中发现 23 个缺陷:8 个未处理的 500 错误、5 个释放后使用模式、4 个命名空间隔离失败、6 个错误响应中的信息泄露
- 对缺陷最多的 5 个服务运行完整 fuzz 模式(每个服务 4 小时)
- 发现 47 个额外缺陷,其中包括一个严重的身份验证绕过:删除用户后复用其令牌仍可访问
- 生成缺陷报告,并通过 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