opencode-api-security-testing 3.0.10 → 3.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -0
- package/SKILL.md +1797 -0
- package/core/advanced_recon.py +788 -0
- package/core/agentic_analyzer.py +445 -0
- package/core/analyzers/api_parser.py +210 -0
- package/core/analyzers/response_analyzer.py +212 -0
- package/core/analyzers/sensitive_finder.py +184 -0
- package/core/api_fuzzer.py +422 -0
- package/core/api_interceptor.py +525 -0
- package/core/api_parser.py +955 -0
- package/core/browser_tester.py +479 -0
- package/core/cloud_storage_tester.py +1330 -0
- package/core/collectors/__init__.py +23 -0
- package/core/collectors/api_path_finder.py +300 -0
- package/core/collectors/browser_collect.py +645 -0
- package/core/collectors/browser_collector.py +411 -0
- package/core/collectors/http_client.py +111 -0
- package/core/collectors/js_collector.py +490 -0
- package/core/collectors/js_parser.py +780 -0
- package/core/collectors/url_collector.py +319 -0
- package/core/context_manager.py +682 -0
- package/core/deep_api_tester_v35.py +844 -0
- package/core/deep_api_tester_v55.py +366 -0
- package/core/dynamic_api_analyzer.py +532 -0
- package/core/http_client.py +179 -0
- package/core/models.py +296 -0
- package/core/orchestrator.py +890 -0
- package/core/prerequisite.py +227 -0
- package/core/reasoning_engine.py +1042 -0
- package/core/response_classifier.py +606 -0
- package/core/runner.py +938 -0
- package/core/scan_engine.py +599 -0
- package/core/skill_executor.py +435 -0
- package/core/skill_executor_v2.py +670 -0
- package/core/skill_executor_v3.py +704 -0
- package/core/smart_analyzer.py +687 -0
- package/core/strategy_pool.py +707 -0
- package/core/testers/auth_tester.py +264 -0
- package/core/testers/idor_tester.py +200 -0
- package/core/testers/sqli_tester.py +211 -0
- package/core/testing_loop.py +655 -0
- package/core/utils/base_path_dict.py +255 -0
- package/core/utils/payload_lib.py +167 -0
- package/core/utils/ssrf_detector.py +220 -0
- package/core/verifiers/vuln_verifier.py +536 -0
- package/package.json +1 -1
- package/references/README.md +72 -0
- package/references/asset-discovery.md +119 -0
- package/references/fuzzing-patterns.md +129 -0
- package/references/graphql-guidance.md +108 -0
- package/references/intake.md +84 -0
- package/references/pua-agent.md +192 -0
- package/references/report-template.md +156 -0
- package/references/rest-guidance.md +76 -0
- package/references/severity-model.md +76 -0
- package/references/test-matrix.md +86 -0
- package/references/validation.md +78 -0
- package/references/vulnerabilities/01-sqli-tests.md +1128 -0
- package/references/vulnerabilities/02-user-enum-tests.md +423 -0
- package/references/vulnerabilities/03-jwt-tests.md +499 -0
- package/references/vulnerabilities/04-idor-tests.md +362 -0
- package/references/vulnerabilities/05-sensitive-data-tests.md +466 -0
- package/references/vulnerabilities/06-biz-logic-tests.md +501 -0
- package/references/vulnerabilities/07-security-config-tests.md +511 -0
- package/references/vulnerabilities/08-brute-force-tests.md +457 -0
- package/references/vulnerabilities/09-vulnerability-chains.md +465 -0
- package/references/vulnerabilities/10-auth-tests.md +537 -0
- package/references/vulnerabilities/11-graphql-tests.md +355 -0
- package/references/vulnerabilities/12-ssrf-tests.md +396 -0
- package/references/vulnerabilities/README.md +148 -0
- package/references/workflows.md +192 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
API Fuzzer - API 路径模糊测试器
|
|
4
|
+
基于发现的 API 路径,生成变体探测隐藏端点
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
import time
|
|
9
|
+
from typing import List, Set, Dict, Tuple, Optional
|
|
10
|
+
from urllib.parse import urljoin, urlparse
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
import requests
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class FuzzResult:
|
|
17
|
+
"""Fuzzing 结果"""
|
|
18
|
+
path: str
|
|
19
|
+
method: str = "GET"
|
|
20
|
+
status_code: int = 0
|
|
21
|
+
content_length: int = 0
|
|
22
|
+
is_alive: bool = False
|
|
23
|
+
is_new: bool = False
|
|
24
|
+
response_time: float = 0.0
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class APIfuzzer:
|
|
28
|
+
"""
|
|
29
|
+
API 路径模糊测试器
|
|
30
|
+
|
|
31
|
+
功能:
|
|
32
|
+
- 父路径探测 (parent_path + suffix)
|
|
33
|
+
- RESTful 路径生成
|
|
34
|
+
- 路径参数化测试
|
|
35
|
+
- 跨来源路径组合
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
# RESTful 常见后缀
|
|
39
|
+
RESTFUL_SUFFIXES = [
|
|
40
|
+
'list', 'get', 'add', 'create', 'update', 'edit', 'delete', 'remove',
|
|
41
|
+
'detail', 'info', 'view', 'show', 'query', 'search', 'fetch', 'load',
|
|
42
|
+
'save', 'submit', 'export', 'import', 'upload', 'download',
|
|
43
|
+
'config', 'setting', 'settings', 'options', 'permissions', 'all',
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
# 常见资源名
|
|
47
|
+
COMMON_RESOURCES = [
|
|
48
|
+
'user', 'users', 'product', 'products', 'order', 'orders',
|
|
49
|
+
'admin', 'auth', 'login', 'logout', 'register', 'profile',
|
|
50
|
+
'config', 'setting', 'settings', 'menu', 'role', 'permission',
|
|
51
|
+
'department', 'organ', 'organization', 'company', 'employee',
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
# 常见路径前缀
|
|
55
|
+
COMMON_PREFIXES = [
|
|
56
|
+
'/api', '/v1', '/v2', '/v3', '/rest', '/restful',
|
|
57
|
+
'/admin', '/user', '/auth', '/service', '/web', '/mobile',
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
# 危险路径关键字 (跳过测试)
|
|
61
|
+
DANGEROUS_KEYWORDS = [
|
|
62
|
+
'delete', 'remove', 'drop', 'truncate', 'shutdown', 'kill',
|
|
63
|
+
'exec', 'eval', 'shell', 'cmd', 'backup', 'restore',
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
def __init__(self, session: requests.Session = None):
|
|
67
|
+
self.session = session or requests.Session()
|
|
68
|
+
self.session.headers.update({
|
|
69
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
|
70
|
+
'Accept': 'application/json, text/html, */*',
|
|
71
|
+
'Accept-Encoding': 'gzip, deflate',
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
self.found_endpoints: List[FuzzResult] = []
|
|
75
|
+
self.tested_paths: Set[str] = set()
|
|
76
|
+
|
|
77
|
+
def generate_parent_fuzz_targets(self, api_paths: List[str], max_per_parent: int = 20) -> List[str]:
|
|
78
|
+
"""
|
|
79
|
+
基于父路径生成 Fuzz 目标
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
api_paths: 已发现的 API 路径列表
|
|
83
|
+
max_per_parent: 每个父路径最多生成的目标数
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Fuzz 目标路径列表
|
|
87
|
+
"""
|
|
88
|
+
targets = []
|
|
89
|
+
parent_map = {}
|
|
90
|
+
|
|
91
|
+
for path in api_paths:
|
|
92
|
+
if not path or len(path) < 2:
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
path = path.strip()
|
|
96
|
+
parts = path.strip('/').split('/')
|
|
97
|
+
|
|
98
|
+
for i in range(1, len(parts)):
|
|
99
|
+
parent = '/' + '/'.join(parts[:i])
|
|
100
|
+
if parent not in parent_map:
|
|
101
|
+
parent_map[parent] = []
|
|
102
|
+
if i < len(parts):
|
|
103
|
+
child = parts[i]
|
|
104
|
+
if child not in parent_map[parent]:
|
|
105
|
+
parent_map[parent].append(child)
|
|
106
|
+
|
|
107
|
+
for parent, children in parent_map.items():
|
|
108
|
+
targets.append(parent)
|
|
109
|
+
|
|
110
|
+
for suffix in self.RESTFUL_SUFFIXES[:10]:
|
|
111
|
+
if len(targets) >= max_per_parent * len(parent_map):
|
|
112
|
+
break
|
|
113
|
+
targets.append(f"{parent}/{suffix}")
|
|
114
|
+
|
|
115
|
+
for child in children[:5]:
|
|
116
|
+
if child in self.RESTFUL_SUFFIXES:
|
|
117
|
+
continue
|
|
118
|
+
targets.append(f"{parent}/{child}")
|
|
119
|
+
for suffix in self.RESTFUL_SUFFIXES[:5]:
|
|
120
|
+
targets.append(f"{parent}/{child}/{suffix}")
|
|
121
|
+
|
|
122
|
+
return list(set(targets))[:500]
|
|
123
|
+
|
|
124
|
+
def generate_cross_source_targets(self, js_paths: List[str], html_paths: List[str], api_paths: List[str]) -> List[str]:
|
|
125
|
+
"""
|
|
126
|
+
跨来源组合路径
|
|
127
|
+
|
|
128
|
+
将不同来源的路径片段智能组合探测隐藏 API
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
js_paths: JS 中发现的路径
|
|
132
|
+
html_paths: HTML 中发现的路径
|
|
133
|
+
api_paths: API 中发现的路径
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
组合后的测试目标
|
|
137
|
+
"""
|
|
138
|
+
all_segments: Set[str] = set()
|
|
139
|
+
|
|
140
|
+
for path_list in [js_paths, html_paths, api_paths]:
|
|
141
|
+
for path in path_list:
|
|
142
|
+
parts = path.strip('/').split('/')
|
|
143
|
+
for part in parts:
|
|
144
|
+
if part and not part.startswith('{') and not part.isdigit():
|
|
145
|
+
if len(part) > 1:
|
|
146
|
+
all_segments.add(part)
|
|
147
|
+
|
|
148
|
+
targets = []
|
|
149
|
+
|
|
150
|
+
for prefix in self.COMMON_PREFIXES[:5]:
|
|
151
|
+
for segment in list(all_segments)[:30]:
|
|
152
|
+
if segment.lower() not in [p.lower() for p in self.COMMON_PREFIXES]:
|
|
153
|
+
targets.append(f"{prefix}/{segment}")
|
|
154
|
+
for suffix in self.RESTFUL_SUFFIXES[:5]:
|
|
155
|
+
targets.append(f"{prefix}/{segment}/{suffix}")
|
|
156
|
+
|
|
157
|
+
return list(set(targets))[:200]
|
|
158
|
+
|
|
159
|
+
def generate_parameter_fuzz_targets(self, endpoints: List[Dict], params: List[str]) -> List[Tuple[str, Dict]]:
|
|
160
|
+
"""
|
|
161
|
+
生成参数 Fuzz 目标
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
endpoints: 端点列表
|
|
165
|
+
params: 参数名列表
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
(url, params) 元组列表
|
|
169
|
+
"""
|
|
170
|
+
targets = []
|
|
171
|
+
|
|
172
|
+
common_values = {
|
|
173
|
+
'id': ['1', '0', '999999', '-1', "1' OR '1'='1"],
|
|
174
|
+
'page': ['1', '0', '999'],
|
|
175
|
+
'pageSize': ['10', '50', '100', '9999'],
|
|
176
|
+
'userId': ['1', '0', 'admin', "admin'--"],
|
|
177
|
+
'type': ['1', '0', 'admin', 'test'],
|
|
178
|
+
'search': ["' OR '1'='1", '<script>alert(1)</script>', '${jndi}'],
|
|
179
|
+
'q': ["' OR '1'='1", '<script>alert(1)</script>'],
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
for endpoint in endpoints[:50]:
|
|
183
|
+
path = endpoint.get('path', endpoint.get('url', ''))
|
|
184
|
+
method = endpoint.get('method', 'GET')
|
|
185
|
+
|
|
186
|
+
if not path:
|
|
187
|
+
continue
|
|
188
|
+
|
|
189
|
+
for param in params[:10]:
|
|
190
|
+
value = common_values.get(param, ['1', 'test', 'admin'])
|
|
191
|
+
for v in value[:3]:
|
|
192
|
+
targets.append((path, {param: v}))
|
|
193
|
+
|
|
194
|
+
return targets[:500]
|
|
195
|
+
|
|
196
|
+
def fuzz_paths(self, base_url: str, paths: List[str],
|
|
197
|
+
methods: List[str] = None,
|
|
198
|
+
timeout: float = 5.0,
|
|
199
|
+
skip_dangerous: bool = True) -> List[FuzzResult]:
|
|
200
|
+
"""
|
|
201
|
+
执行路径 Fuzzing
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
base_url: 基础 URL
|
|
205
|
+
paths: 路径列表
|
|
206
|
+
methods: HTTP 方法列表
|
|
207
|
+
timeout: 超时时间
|
|
208
|
+
skip_dangerous: 跳过危险路径
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Fuzz 结果列表
|
|
212
|
+
"""
|
|
213
|
+
methods = methods or ['GET', 'POST', 'PUT', 'DELETE', 'HEAD']
|
|
214
|
+
results = []
|
|
215
|
+
|
|
216
|
+
for path in paths:
|
|
217
|
+
if path in self.tested_paths:
|
|
218
|
+
continue
|
|
219
|
+
|
|
220
|
+
if skip_dangerous and any(k in path.lower() for k in self.DANGEROUS_KEYWORDS):
|
|
221
|
+
continue
|
|
222
|
+
|
|
223
|
+
self.tested_paths.add(path)
|
|
224
|
+
full_url = urljoin(base_url, path)
|
|
225
|
+
|
|
226
|
+
for method in methods:
|
|
227
|
+
try:
|
|
228
|
+
start_time = time.time()
|
|
229
|
+
resp = self.session.request(
|
|
230
|
+
method,
|
|
231
|
+
full_url,
|
|
232
|
+
timeout=timeout,
|
|
233
|
+
allow_redirects=False
|
|
234
|
+
)
|
|
235
|
+
response_time = time.time() - start_time
|
|
236
|
+
|
|
237
|
+
result = FuzzResult(
|
|
238
|
+
path=path,
|
|
239
|
+
method=method,
|
|
240
|
+
status_code=resp.status_code,
|
|
241
|
+
content_length=len(resp.content),
|
|
242
|
+
is_alive=resp.status_code < 500,
|
|
243
|
+
is_new=resp.status_code not in [301, 302, 404],
|
|
244
|
+
response_time=response_time
|
|
245
|
+
)
|
|
246
|
+
results.append(result)
|
|
247
|
+
self.found_endpoints.append(result)
|
|
248
|
+
|
|
249
|
+
except requests.exceptions.Timeout:
|
|
250
|
+
results.append(FuzzResult(
|
|
251
|
+
path=path, method=method, status_code=0,
|
|
252
|
+
is_alive=False, response_time=timeout
|
|
253
|
+
))
|
|
254
|
+
except Exception:
|
|
255
|
+
pass
|
|
256
|
+
|
|
257
|
+
return results
|
|
258
|
+
|
|
259
|
+
def fuzz_with_params(self, base_url: str, targets: List[Tuple[str, Dict]],
|
|
260
|
+
timeout: float = 5.0) -> List[FuzzResult]:
|
|
261
|
+
"""
|
|
262
|
+
执行带参数的 Fuzzing
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
base_url: 基础 URL
|
|
266
|
+
targets: (path, params) 元组列表
|
|
267
|
+
timeout: 超时时间
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Fuzz 结果列表
|
|
271
|
+
"""
|
|
272
|
+
results = []
|
|
273
|
+
|
|
274
|
+
for path, params in targets:
|
|
275
|
+
full_url = urljoin(base_url, path)
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
start_time = time.time()
|
|
279
|
+
resp = self.session.post(
|
|
280
|
+
full_url,
|
|
281
|
+
json=params,
|
|
282
|
+
timeout=timeout,
|
|
283
|
+
allow_redirects=False
|
|
284
|
+
)
|
|
285
|
+
response_time = time.time() - start_time
|
|
286
|
+
|
|
287
|
+
result = FuzzResult(
|
|
288
|
+
path=f"{path} (POST JSON {params})",
|
|
289
|
+
method='POST',
|
|
290
|
+
status_code=resp.status_code,
|
|
291
|
+
content_length=len(resp.content),
|
|
292
|
+
is_alive=resp.status_code < 500,
|
|
293
|
+
is_new=resp.status_code not in [301, 302, 404],
|
|
294
|
+
response_time=response_time
|
|
295
|
+
)
|
|
296
|
+
results.append(result)
|
|
297
|
+
self.found_endpoints.append(result)
|
|
298
|
+
|
|
299
|
+
except Exception:
|
|
300
|
+
pass
|
|
301
|
+
|
|
302
|
+
return results
|
|
303
|
+
|
|
304
|
+
def get_alive_endpoints(self) -> List[FuzzResult]:
|
|
305
|
+
"""获取存活的端点"""
|
|
306
|
+
return [r for r in self.found_endpoints if r.is_alive and r.status_code not in [301, 302]]
|
|
307
|
+
|
|
308
|
+
def get_high_value_endpoints(self) -> List[FuzzResult]:
|
|
309
|
+
"""获取高价值端点 (非标准状态码)"""
|
|
310
|
+
return [r for r in self.found_endpoints if r.is_new]
|
|
311
|
+
|
|
312
|
+
def get_summary(self) -> Dict:
|
|
313
|
+
"""获取 Fuzzing 结果摘要"""
|
|
314
|
+
alive = self.get_alive_endpoints()
|
|
315
|
+
high_value = self.get_high_value_endpoints()
|
|
316
|
+
|
|
317
|
+
status_counts = {}
|
|
318
|
+
for r in self.found_endpoints:
|
|
319
|
+
status_counts[r.status_code] = status_counts.get(r.status_code, 0) + 1
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
'total_tested': len(self.tested_paths),
|
|
323
|
+
'total_results': len(self.found_endpoints),
|
|
324
|
+
'alive_endpoints': len(alive),
|
|
325
|
+
'high_value_endpoints': len(high_value),
|
|
326
|
+
'status_distribution': status_counts,
|
|
327
|
+
'avg_response_time': sum(r.response_time for r in self.found_endpoints) / max(len(self.found_endpoints), 1)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def auto_fuzz(target_url: str, api_paths: List[str] = None,
|
|
332
|
+
js_content: str = None, html_content: str = None,
|
|
333
|
+
session: requests.Session = None) -> Dict:
|
|
334
|
+
"""
|
|
335
|
+
自动 Fuzzing 流程
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
target_url: 目标 URL
|
|
339
|
+
api_paths: 已发现的 API 路径
|
|
340
|
+
js_content: JS 文件内容
|
|
341
|
+
html_content: HTML 内容
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
Fuzzing 结果
|
|
345
|
+
"""
|
|
346
|
+
api_paths = api_paths or []
|
|
347
|
+
session = session or requests.Session()
|
|
348
|
+
|
|
349
|
+
fuzzer = APIfuzzer(session=session)
|
|
350
|
+
|
|
351
|
+
all_paths = set(api_paths)
|
|
352
|
+
|
|
353
|
+
if js_content:
|
|
354
|
+
js_api_patterns = [
|
|
355
|
+
r"['\"](/api/[^'\"\\\s]+)['\"]",
|
|
356
|
+
r"['\"](/v\d+/[^'\"\\\s]+)['\"]",
|
|
357
|
+
r"baseURL\s*[:=]\s*['\"]([^'\"]+)['\"]",
|
|
358
|
+
]
|
|
359
|
+
for pattern in js_api_patterns:
|
|
360
|
+
matches = re.findall(pattern, js_content)
|
|
361
|
+
all_paths.update(matches)
|
|
362
|
+
|
|
363
|
+
if html_content:
|
|
364
|
+
html_patterns = [
|
|
365
|
+
r"href=['\"](/[^'\"]+)['\"]",
|
|
366
|
+
r"src=['\"](/[^'\"]+\.js)['\"]",
|
|
367
|
+
]
|
|
368
|
+
for pattern in html_patterns:
|
|
369
|
+
matches = re.findall(pattern, html_content)
|
|
370
|
+
all_paths.update(matches)
|
|
371
|
+
|
|
372
|
+
parent_targets = fuzzer.generate_parent_fuzz_targets(list(all_paths))
|
|
373
|
+
|
|
374
|
+
cross_targets = []
|
|
375
|
+
if js_content and html_content:
|
|
376
|
+
cross_targets = fuzzer.generate_cross_source_targets(
|
|
377
|
+
js_paths=api_paths,
|
|
378
|
+
html_paths=[],
|
|
379
|
+
api_paths=api_paths
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
all_targets = list(set(parent_targets + cross_targets))
|
|
383
|
+
|
|
384
|
+
print(f"[*] Generated {len(all_targets)} fuzz targets")
|
|
385
|
+
|
|
386
|
+
results = fuzzer.fuzz_paths(target_url, all_targets[:200], timeout=3.0)
|
|
387
|
+
|
|
388
|
+
summary = fuzzer.get_summary()
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
'targets_generated': len(all_targets),
|
|
392
|
+
'endpoints_tested': summary['total_tested'],
|
|
393
|
+
'alive_endpoints': summary['alive_endpoints'],
|
|
394
|
+
'high_value_endpoints': summary['high_value_endpoints'],
|
|
395
|
+
'results': results,
|
|
396
|
+
'summary': summary
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
if __name__ == "__main__":
|
|
401
|
+
import argparse
|
|
402
|
+
|
|
403
|
+
parser = argparse.ArgumentParser(description="API Fuzzer")
|
|
404
|
+
parser.add_argument("--target", required=True, help="Target URL")
|
|
405
|
+
parser.add_argument("--paths", help="File with API paths")
|
|
406
|
+
parser.add_argument("--output", help="Output file")
|
|
407
|
+
|
|
408
|
+
args = parser.parse_args()
|
|
409
|
+
|
|
410
|
+
session = requests.Session()
|
|
411
|
+
result = auto_fuzz(args.target, session=session)
|
|
412
|
+
|
|
413
|
+
print(f"\n[*] Fuzzing Summary:")
|
|
414
|
+
print(f" Targets: {result['targets_generated']}")
|
|
415
|
+
print(f" Tested: {result['endpoints_tested']}")
|
|
416
|
+
print(f" Alive: {result['alive_endpoints']}")
|
|
417
|
+
print(f" High Value: {result['high_value_endpoints']}")
|
|
418
|
+
|
|
419
|
+
if args.output:
|
|
420
|
+
import json
|
|
421
|
+
with open(args.output, 'w') as f:
|
|
422
|
+
json.dump(result, f, indent=2)
|