opencode-api-security-testing 3.0.8 → 3.0.10

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.
Files changed (79) hide show
  1. package/agents/api-cyber-supervisor.md +9 -3
  2. package/agents/api-probing-miner.md +10 -2
  3. package/agents/api-resource-specialist.md +44 -35
  4. package/agents/api-vuln-verifier.md +56 -24
  5. package/package.json +1 -1
  6. package/postinstall.mjs +1 -0
  7. package/preuninstall.mjs +43 -32
  8. package/src/index.ts +3 -100
  9. package/README.md +0 -74
  10. package/SKILL.md +0 -1797
  11. package/core/advanced_recon.py +0 -788
  12. package/core/agentic_analyzer.py +0 -445
  13. package/core/analyzers/api_parser.py +0 -210
  14. package/core/analyzers/response_analyzer.py +0 -212
  15. package/core/analyzers/sensitive_finder.py +0 -184
  16. package/core/api_fuzzer.py +0 -422
  17. package/core/api_interceptor.py +0 -525
  18. package/core/api_parser.py +0 -955
  19. package/core/browser_tester.py +0 -479
  20. package/core/cloud_storage_tester.py +0 -1330
  21. package/core/collectors/__init__.py +0 -23
  22. package/core/collectors/api_path_finder.py +0 -300
  23. package/core/collectors/browser_collect.py +0 -645
  24. package/core/collectors/browser_collector.py +0 -411
  25. package/core/collectors/http_client.py +0 -111
  26. package/core/collectors/js_collector.py +0 -490
  27. package/core/collectors/js_parser.py +0 -780
  28. package/core/collectors/url_collector.py +0 -319
  29. package/core/context_manager.py +0 -682
  30. package/core/deep_api_tester_v35.py +0 -844
  31. package/core/deep_api_tester_v55.py +0 -366
  32. package/core/dynamic_api_analyzer.py +0 -532
  33. package/core/http_client.py +0 -179
  34. package/core/models.py +0 -296
  35. package/core/orchestrator.py +0 -890
  36. package/core/prerequisite.py +0 -227
  37. package/core/reasoning_engine.py +0 -1042
  38. package/core/response_classifier.py +0 -606
  39. package/core/runner.py +0 -938
  40. package/core/scan_engine.py +0 -599
  41. package/core/skill_executor.py +0 -435
  42. package/core/skill_executor_v2.py +0 -670
  43. package/core/skill_executor_v3.py +0 -704
  44. package/core/smart_analyzer.py +0 -687
  45. package/core/strategy_pool.py +0 -707
  46. package/core/testers/auth_tester.py +0 -264
  47. package/core/testers/idor_tester.py +0 -200
  48. package/core/testers/sqli_tester.py +0 -211
  49. package/core/testing_loop.py +0 -655
  50. package/core/utils/base_path_dict.py +0 -255
  51. package/core/utils/payload_lib.py +0 -167
  52. package/core/utils/ssrf_detector.py +0 -220
  53. package/core/verifiers/vuln_verifier.py +0 -536
  54. package/references/README.md +0 -72
  55. package/references/asset-discovery.md +0 -119
  56. package/references/fuzzing-patterns.md +0 -129
  57. package/references/graphql-guidance.md +0 -108
  58. package/references/intake.md +0 -84
  59. package/references/pua-agent.md +0 -192
  60. package/references/report-template.md +0 -156
  61. package/references/rest-guidance.md +0 -76
  62. package/references/severity-model.md +0 -76
  63. package/references/test-matrix.md +0 -86
  64. package/references/validation.md +0 -78
  65. package/references/vulnerabilities/01-sqli-tests.md +0 -1128
  66. package/references/vulnerabilities/02-user-enum-tests.md +0 -423
  67. package/references/vulnerabilities/03-jwt-tests.md +0 -499
  68. package/references/vulnerabilities/04-idor-tests.md +0 -362
  69. package/references/vulnerabilities/05-sensitive-data-tests.md +0 -466
  70. package/references/vulnerabilities/06-biz-logic-tests.md +0 -501
  71. package/references/vulnerabilities/07-security-config-tests.md +0 -511
  72. package/references/vulnerabilities/08-brute-force-tests.md +0 -457
  73. package/references/vulnerabilities/09-vulnerability-chains.md +0 -465
  74. package/references/vulnerabilities/10-auth-tests.md +0 -537
  75. package/references/vulnerabilities/11-graphql-tests.md +0 -355
  76. package/references/vulnerabilities/12-ssrf-tests.md +0 -396
  77. package/references/vulnerabilities/README.md +0 -148
  78. package/references/workflows.md +0 -192
  79. package/src/hooks/directory-agents-injector.ts +0 -106
@@ -1,422 +0,0 @@
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)