Performs stateful REST API fuzzing with Microsoft RESTler using OpenAPI specs to cover endpoints, detect request dependencies, and find defects like 500 errors, auth bypass, leaks, and injections.
npx claudepluginhub killvxk/cybersecurity-skills-zhThis skill uses the workspace's default tool permissions.
- 使用 OpenAPI/Swagger 规范对 REST API 执行自动化安全测试
Performs stateful REST API fuzzing with Microsoft RESTler using OpenAPI specs to generate test sequences, discover dependencies, and detect bugs like 500 errors, auth bypasses, and injections. For automated API security testing.
Fuzzes stateful REST APIs with Microsoft RESTler using OpenAPI specs to generate test sequences, detect producer-consumer deps, and find security/reliability bugs like 500s, auth bypasses, leaks.
Runs API fuzzing with Schemathesis, RESTler, fast-check, and OWASP ZAP to detect crashes, edge cases, and vulnerabilities in REST/GraphQL endpoints from OpenAPI specs.
Share bugs, ideas, or general feedback.
不适用于 在未经明确授权和监控的情况下针对生产环境使用。RESTler 在模糊测试期间会大量创建和删除资源。
# 克隆并构建 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 下载
# 将 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):
{
"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"]
}
}
# 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):
{
"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
}
# 测试模式:快速验证所有端点是否可达
/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
# 解析测试结果
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}")
# 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 进行测试
# 完整模糊测试模式:扩展模糊测试以获得全面覆盖
/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 定期清理在模糊测试期间创建的资源,防止目标系统资源耗尽 |
场景背景:一家金融科技公司拥有 12 个配有 OpenAPI 规范的微服务 API。在重大发布前,安全团队在预发布环境中对每个服务运行 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