opencode-api-security-testing 3.0.9 → 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 (78) hide show
  1. package/agents/api-cyber-supervisor.md +22 -19
  2. package/agents/api-probing-miner.md +34 -10
  3. package/agents/api-resource-specialist.md +49 -20
  4. package/agents/api-vuln-verifier.md +69 -18
  5. package/package.json +1 -1
  6. package/postinstall.mjs +1 -0
  7. package/preuninstall.mjs +43 -32
  8. package/src/index.ts +6 -3
  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
@@ -1,532 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- 动态 API 分析模块 v2
5
-
6
- 利用 Playwright 进行运行时 API 分析:
7
- 1. 使用 CDP (Chrome DevTools Protocol) 拦截所有网络请求
8
- 2. 识别真实 API 调用 (fetch/axios/XHR)
9
- 3. 提取请求参数 (path/query/body)
10
- 4. 追踪交互来源 (点击/输入/导航触发)
11
-
12
- 使用方式:
13
- from core.dynamic_api_analyzer import DynamicAPIAnalyzer
14
-
15
- analyzer = DynamicAPIAnalyzer('http://target.com')
16
- results = analyzer.analyze_full()
17
- """
18
-
19
- import re
20
- import time
21
- import json
22
- from typing import Dict, List, Set, Optional, Any, Callable
23
- from dataclasses import dataclass, field
24
- from datetime import datetime
25
-
26
- import sys
27
- sys.path.insert(0, '/workspace/skill-play/API-Security-Testing-Optimized')
28
-
29
-
30
- @dataclass
31
- class APIRequest:
32
- """API 请求"""
33
- url: str
34
- method: str = "GET"
35
- headers: Dict = field(default_factory=dict)
36
- post_data: Any = None
37
- query_params: Dict = field(default_factory=dict)
38
- path_params: Dict = field(default_factory=dict)
39
- source: str = "" # fetch, axios, xhr, intercepted
40
- trigger: str = "" # click_login, input_search, navigate_dashboard
41
- timestamp: float = 0
42
- response_status: int = 0
43
- response_body: str = ""
44
-
45
- def to_dict(self) -> Dict:
46
- return {
47
- 'url': self.url,
48
- 'method': self.method,
49
- 'query_params': self.query_params,
50
- 'path_params': self.path_params,
51
- 'source': self.source,
52
- 'trigger': self.trigger,
53
- 'response_status': self.response_status,
54
- }
55
-
56
-
57
- class DynamicAPIAnalyzer:
58
- """
59
- 动态 API 分析器 v2
60
-
61
- 改进:
62
- 1. 使用 CDP Request interception 而非简单的事件监听
63
- 2. 追踪触发来源 (stack trace 分析)
64
- 3. 主动注入测试代码追踪交互
65
- """
66
-
67
- def __init__(self, target: str, headless: bool = True):
68
- self.target = target
69
- self.headless = headless
70
- self.requests: List[APIRequest] = []
71
- self._page = None
72
- self._context = None
73
- self._last_interaction = ""
74
-
75
- def analyze_full(self, max_requests: int = 100) -> Dict:
76
- """
77
- 执行完整动态分析
78
-
79
- Args:
80
- max_requests: 最大捕获请求数
81
-
82
- Returns:
83
- {
84
- 'total': 100,
85
- 'api_requests': [...],
86
- 'endpoints': {...},
87
- 'params_summary': {...}
88
- }
89
- """
90
- print(f" [DynamicAPI v2] 启动动态分析")
91
- print(f" [DynamicAPI v2] 目标: {self.target}")
92
-
93
- try:
94
- from playwright.sync_api import sync_playwright
95
-
96
- with sync_playwright() as p:
97
- browser = p.chromium.launch(headless=self.headless)
98
- self._context = browser.new_context(
99
- viewport={'width': 1920, 'height': 1080},
100
- ignore_https_errors=True
101
- )
102
- self._page = self._context.new_page()
103
-
104
- self._setup_cdp_interceptor()
105
-
106
- self._visit_target()
107
- self._trigger_login_interactions()
108
- self._trigger_search_interactions()
109
- self._trigger_navigation()
110
- self._trigger_form_submissions()
111
-
112
- browser.close()
113
-
114
- except ImportError:
115
- print(f" [DynamicAPI] Playwright 不可用")
116
- except Exception as e:
117
- print(f" [DynamicAPI] 分析失败: {e}")
118
-
119
- results = self._process_results()
120
- print(f" [DynamicAPI] 捕获 {results['total_api']} 个 API 请求")
121
-
122
- return results
123
-
124
- def _setup_cdp_interceptor(self):
125
- """设置 CDP 请求拦截器"""
126
-
127
- # 1. 拦截所有请求
128
- def on_request(request):
129
- url = request.url
130
- method = request.method
131
-
132
- # 只处理目标域名的 API 请求
133
- if not self._is_api_request(url):
134
- return
135
-
136
- # 提取参数
137
- query_params = self._extract_query_params(url)
138
- path_params = self._extract_path_params(url)
139
-
140
- # 识别来源
141
- source = self._identify_source(request)
142
-
143
- api_req = APIRequest(
144
- url=url,
145
- method=method,
146
- headers=dict(request.headers),
147
- post_data=request.post_data,
148
- query_params=query_params,
149
- path_params=path_params,
150
- source=source,
151
- trigger=self._last_interaction,
152
- timestamp=time.time()
153
- )
154
-
155
- self.requests.append(api_req)
156
- print(f" [API] {method} {self._short_url(url)}")
157
-
158
- # 2. 拦截响应获取状态码
159
- def on_response(response):
160
- for req in self.requests:
161
- if req.url == response.url:
162
- req.response_status = response.status
163
- try:
164
- if response.status == 200:
165
- body = response.text()
166
- req.response_body = body[:500] if body else ""
167
- except:
168
- pass
169
- break
170
-
171
- self._page.on("request", on_request)
172
- self._page.on("response", on_response)
173
-
174
- def _is_api_request(self, url: str) -> bool:
175
- """判断是否是 API 请求"""
176
- # 必须包含目标主机
177
- target_host = self.target.replace('http://', '').replace('https://', '').split(':')[0]
178
- if target_host not in url:
179
- return False
180
-
181
- # 排除静态资源 (严格匹配)
182
- skip_patterns = [
183
- r'\.js$', r'\.css$', r'\.jpg$', r'\.jpeg$', r'\.png$',
184
- r'\.gif$', r'\.svg$', r'\.ico$', r'\.woff$', r'\.woff2$',
185
- r'\.ttf$', r'\.eot$', r'\.map$', r'\.mp4$', r'\.mp3$',
186
- r'\.webm$', r'\.avi$', r'\.mov$', r'\.png\?', r'\.jpg\?',
187
- r'/static/', r'/assets/', r'/public/', r'/images/',
188
- r'/css/', r'/fonts/', r'/media/', r'/videos/',
189
- ]
190
-
191
- url_lower = url.lower()
192
- for pattern in skip_patterns:
193
- if re.search(pattern, url_lower):
194
- return False
195
-
196
- # API 路径特征 (必须是这些模式之一)
197
- api_patterns = [
198
- '/api/', '/rest/', '/v1/', '/v2/', '/v3/',
199
- '/graphql', '/query', '/login', '/auth',
200
- '/user/', '/admin/', '/config/', '/data/',
201
- '/file/', '/upload/', '/download/',
202
- '/icp-api/', # 发现的内部 API 前缀
203
- ]
204
-
205
- for pattern in api_patterns:
206
- if pattern in url_lower:
207
- return True
208
-
209
- # POST 请求通常是 API
210
- # 检查 URL 中是否有查询参数
211
- if '?' in url:
212
- return True
213
-
214
- return False
215
-
216
- def _short_url(self, url: str, max_len: int = 60) -> str:
217
- """缩短 URL 用于显示"""
218
- if len(url) > max_len:
219
- return url[:max_len] + "..."
220
- return url
221
-
222
- def _extract_query_params(self, url: str) -> Dict:
223
- """提取查询参数"""
224
- params = {}
225
- if '?' not in url:
226
- return params
227
-
228
- query_str = url.split('?')[1]
229
- for pair in query_str.split('&'):
230
- if '=' in pair:
231
- key, value = pair.split('=', 1)
232
- params[key] = value
233
- else:
234
- params[pair] = ""
235
- return params
236
-
237
- def _extract_path_params(self, path: str) -> Dict:
238
- """提取路径参数"""
239
- params = {}
240
-
241
- # 移除查询参数
242
- if '?' in path:
243
- path = path.split('?')[0]
244
-
245
- # 移除目标前缀
246
- target_path = path.replace(self.target.rstrip('/'), '')
247
-
248
- # 数字 ID 模式
249
- id_patterns = [
250
- (r'/(\d+)', 'id'),
251
- (r'/([a-f0-9-]{32,})', 'uuid'),
252
- (r'/([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})', 'uuid'),
253
- ]
254
-
255
- for pattern, name in id_patterns:
256
- matches = re.findall(pattern, path)
257
- for m in matches:
258
- params[name] = m
259
- break
260
-
261
- return params
262
-
263
- def _identify_source(self, request) -> str:
264
- """识别请求来源"""
265
- headers = dict(request.headers)
266
- headers_str = str(headers).lower()
267
-
268
- # 根据头信息判断
269
- if 'x-requested-with' in headers_str and 'xmlhttprequest' in headers_str:
270
- return 'xhr'
271
- if 'content-type' in headers:
272
- ct = headers['content-type'].lower()
273
- if 'application/json' in ct:
274
- return 'fetch' if 'fetch' in headers_str else 'axios'
275
-
276
- # 根据 URL 模式判断
277
- url = request.url.lower()
278
- if 'axios' in url:
279
- return 'axios'
280
- if 'api' in url:
281
- return 'api'
282
-
283
- return 'unknown'
284
-
285
- def _visit_target(self):
286
- """访问目标页面"""
287
- print(f" [DynamicAPI] 访问目标...")
288
- self._last_interaction = "visit_home"
289
- try:
290
- self._page.goto(self.target, wait_until='networkidle', timeout=30000)
291
- self._page.wait_for_timeout(2000)
292
- except Exception as e:
293
- print(f" [WARN] 访问失败: {e}")
294
-
295
- def _trigger_login_interactions(self):
296
- """触发登录相关交互"""
297
- print(f" [DynamicAPI] 触发登录交互...")
298
- self._last_interaction = "trigger_login"
299
-
300
- # 1. 尝试导航到登录页
301
- try:
302
- if '#/login' not in self._page.url:
303
- self._page.goto(self.target + '#/login', timeout=10000)
304
- self._page.wait_for_timeout(2000)
305
- except:
306
- pass
307
-
308
- # 2. 填写登录表单
309
- selectors = [
310
- 'input[type="text"]', 'input[type="email"]',
311
- 'input[name="username"]', 'input[name="user"]',
312
- 'input[placeholder*="用户"]', 'input[placeholder*="账号"]',
313
- ]
314
-
315
- for selector in selectors:
316
- try:
317
- inputs = self._page.query_selector_all(selector)
318
- for i, inp in enumerate(inputs[:2]):
319
- try:
320
- inp.fill(f'testuser{i}')
321
- self._page.wait_for_timeout(300)
322
- except:
323
- pass
324
- except:
325
- pass
326
-
327
- # 3. 填写密码
328
- password_selectors = [
329
- 'input[type="password"]',
330
- 'input[name="password"]',
331
- ]
332
-
333
- for selector in password_selectors:
334
- try:
335
- inputs = self._page.query_selector_all(selector)
336
- for inp in inputs[:1]:
337
- try:
338
- inp.fill('TestPassword123!')
339
- self._page.wait_for_timeout(300)
340
- except:
341
- pass
342
- except:
343
- pass
344
-
345
- # 4. 点击登录按钮
346
- try:
347
- buttons = self._page.query_selector_all('button[type="submit"], button:has-text("登录"), button:has-text("Login")')
348
- for btn in buttons[:2]:
349
- try:
350
- btn.click()
351
- self._page.wait_for_timeout(1000)
352
- except:
353
- pass
354
- except:
355
- pass
356
-
357
- def _trigger_search_interactions(self):
358
- """触发搜索相关交互"""
359
- print(f" [DynamicAPI] 触发搜索交互...")
360
- self._last_interaction = "trigger_search"
361
-
362
- search_selectors = [
363
- 'input[type="search"]',
364
- 'input[placeholder*="搜索"]',
365
- 'input[placeholder*="查询"]',
366
- 'input[placeholder*="search"]',
367
- '.search-input',
368
- '.el-input__inner',
369
- ]
370
-
371
- for selector in search_selectors:
372
- try:
373
- inputs = self._page.query_selector_all(selector)
374
- for inp in inputs[:2]:
375
- try:
376
- inp.fill('test query')
377
- self._page.wait_for_timeout(500)
378
- inp.press('Enter')
379
- self._page.wait_for_timeout(1000)
380
- except:
381
- pass
382
- except:
383
- pass
384
-
385
- def _trigger_navigation(self):
386
- """触发导航"""
387
- print(f" [DynamicAPI] 触发导航...")
388
-
389
- routes = [
390
- '#/home',
391
- '#/dashboard',
392
- '#/admin',
393
- '#/profile',
394
- '#/user',
395
- '#/system',
396
- '#/report',
397
- ]
398
-
399
- for route in routes:
400
- self._last_interaction = f"navigate_{route.replace('#/', '')}"
401
- try:
402
- url = self.target.rstrip('/') + route
403
- self._page.goto(url, timeout=10000)
404
- self._page.wait_for_timeout(2000)
405
- except:
406
- pass
407
-
408
- def _trigger_form_submissions(self):
409
- """触发表单提交"""
410
- print(f" [DynamicAPI] 触发表单提交...")
411
- self._last_interaction = "trigger_form"
412
-
413
- # 点击各种按钮
414
- button_selectors = [
415
- 'button:not([disabled])',
416
- 'a.btn',
417
- '.el-button',
418
- ]
419
-
420
- for selector in button_selectors:
421
- try:
422
- buttons = self._page.query_selector_all(selector)
423
- for btn in buttons[:5]:
424
- try:
425
- btn.click()
426
- self._page.wait_for_timeout(500)
427
- except:
428
- pass
429
- except:
430
- pass
431
-
432
- def _process_results(self) -> Dict:
433
- """处理捕获结果"""
434
-
435
- # 过滤真正的 API 请求
436
- api_requests = [r for r in self.requests if r.response_status > 0 or r.query_params]
437
-
438
- # 统计端点
439
- endpoint_stats = {}
440
- for req in api_requests:
441
- path = req.url.split('?')[0]
442
- method = req.method
443
-
444
- key = f"{method} {path}"
445
- if key not in endpoint_stats:
446
- endpoint_stats[key] = {
447
- 'path': path,
448
- 'method': method,
449
- 'count': 0,
450
- 'params': set(),
451
- 'sources': set(),
452
- 'triggers': set(),
453
- 'statuses': set(),
454
- }
455
-
456
- stats = endpoint_stats[key]
457
- stats['count'] += 1
458
- stats['params'].update(req.query_params.keys())
459
- stats['params'].update(req.path_params.keys())
460
- if req.source:
461
- stats['sources'].add(req.source)
462
- if req.trigger:
463
- stats['triggers'].add(req.trigger)
464
- if req.response_status:
465
- stats['statuses'].add(req.response_status)
466
-
467
- # 转换端点统计
468
- endpoints = []
469
- for key, stats in endpoint_stats.items():
470
- endpoints.append({
471
- 'path': stats['path'],
472
- 'method': stats['method'],
473
- 'count': stats['count'],
474
- 'params': list(stats['params']),
475
- 'sources': list(stats['sources']),
476
- 'triggers': list(stats['triggers']),
477
- 'statuses': list(stats['statuses']),
478
- })
479
-
480
- # 按调用次数排序
481
- endpoints.sort(key=lambda x: -x['count'])
482
-
483
- # 参数统计
484
- all_params = {}
485
- for ep in endpoints:
486
- for param in ep['params']:
487
- all_params[param] = all_params.get(param, 0) + 1
488
-
489
- return {
490
- 'total': len(self.requests),
491
- 'total_api': len(api_requests),
492
- 'endpoints': endpoints,
493
- 'param_summary': all_params,
494
- 'requests': [r.to_dict() for r in api_requests[:50]],
495
- }
496
-
497
-
498
- def run_full_analysis(target: str) -> Dict:
499
- """运行完整动态分析"""
500
- print("=" * 60)
501
- print("Dynamic API Analyzer v2")
502
- print("=" * 60)
503
-
504
- analyzer = DynamicAPIAnalyzer(target)
505
- results = analyzer.analyze_full()
506
-
507
- print("\n" + "=" * 60)
508
- print("分析结果")
509
- print("=" * 60)
510
- print(f"总请求: {results['total']}")
511
- print(f"API 请求: {results['total_api']}")
512
- print(f"唯一端点: {len(results['endpoints'])}")
513
-
514
- if results['param_summary']:
515
- print(f"\nTop 参数:")
516
- top_params = sorted(results['param_summary'].items(), key=lambda x: -x[1])[:10]
517
- for param, count in top_params:
518
- print(f" {param}: {count} 次")
519
-
520
- if results['endpoints']:
521
- print(f"\n端点详情 (前 10):")
522
- for ep in results['endpoints'][:10]:
523
- params_str = ', '.join(ep['params'][:5])
524
- print(f" {ep['method']} {ep['path']}")
525
- print(f" 调用 {ep['count']} 次, 参数: {params_str}")
526
-
527
- return results
528
-
529
-
530
- if __name__ == "__main__":
531
- target = sys.argv[1] if len(sys.argv) > 1 else "http://58.215.18.57:91"
532
- run_full_analysis(target)