opencode-api-security-testing 2.1.0 → 2.1.2

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 (61) hide show
  1. package/SKILL.md +1797 -0
  2. package/core/advanced_recon.py +788 -0
  3. package/core/agentic_analyzer.py +445 -0
  4. package/core/analyzers/api_parser.py +210 -0
  5. package/core/analyzers/response_analyzer.py +212 -0
  6. package/core/analyzers/sensitive_finder.py +184 -0
  7. package/core/api_fuzzer.py +422 -0
  8. package/core/api_interceptor.py +525 -0
  9. package/core/api_parser.py +955 -0
  10. package/core/browser_tester.py +479 -0
  11. package/core/cloud_storage_tester.py +1330 -0
  12. package/core/collectors/__init__.py +23 -0
  13. package/core/collectors/api_path_finder.py +300 -0
  14. package/core/collectors/browser_collect.py +645 -0
  15. package/core/collectors/browser_collector.py +411 -0
  16. package/core/collectors/http_client.py +111 -0
  17. package/core/collectors/js_collector.py +490 -0
  18. package/core/collectors/js_parser.py +780 -0
  19. package/core/collectors/url_collector.py +319 -0
  20. package/core/context_manager.py +682 -0
  21. package/core/deep_api_tester_v35.py +844 -0
  22. package/core/deep_api_tester_v55.py +366 -0
  23. package/core/dynamic_api_analyzer.py +532 -0
  24. package/core/http_client.py +179 -0
  25. package/core/models.py +296 -0
  26. package/core/orchestrator.py +890 -0
  27. package/core/prerequisite.py +227 -0
  28. package/core/reasoning_engine.py +1042 -0
  29. package/core/response_classifier.py +606 -0
  30. package/core/runner.py +938 -0
  31. package/core/scan_engine.py +599 -0
  32. package/core/skill_executor.py +435 -0
  33. package/core/skill_executor_v2.py +670 -0
  34. package/core/skill_executor_v3.py +704 -0
  35. package/core/smart_analyzer.py +687 -0
  36. package/core/strategy_pool.py +707 -0
  37. package/core/testers/auth_tester.py +264 -0
  38. package/core/testers/idor_tester.py +200 -0
  39. package/core/testers/sqli_tester.py +211 -0
  40. package/core/testing_loop.py +655 -0
  41. package/core/utils/base_path_dict.py +255 -0
  42. package/core/utils/payload_lib.py +167 -0
  43. package/core/utils/ssrf_detector.py +220 -0
  44. package/core/verifiers/vuln_verifier.py +536 -0
  45. package/package.json +17 -13
  46. package/references/asset-discovery.md +119 -612
  47. package/references/graphql-guidance.md +65 -641
  48. package/references/intake.md +84 -0
  49. package/references/report-template.md +131 -38
  50. package/references/rest-guidance.md +55 -526
  51. package/references/severity-model.md +52 -264
  52. package/references/test-matrix.md +65 -263
  53. package/references/validation.md +53 -400
  54. package/scripts/postinstall.js +46 -0
  55. package/agents/cyber-supervisor.md +0 -55
  56. package/agents/probing-miner.md +0 -42
  57. package/agents/resource-specialist.md +0 -31
  58. package/commands/api-security-testing-scan.md +0 -59
  59. package/commands/api-security-testing-test.md +0 -49
  60. package/commands/api-security-testing.md +0 -72
  61. package/tsconfig.json +0 -17
@@ -0,0 +1,479 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Browser Automation Tester - 浏览器动态测试引擎
4
+ 支持 Playwright/Puppeteer/Selenium 多引擎
5
+ """
6
+
7
+ import json
8
+ import time
9
+ import re
10
+ import warnings
11
+ from typing import Dict, List, Tuple, Optional, Any
12
+ from dataclasses import dataclass, field
13
+ from enum import Enum
14
+
15
+ warnings.filterwarnings("ignore")
16
+
17
+
18
+ class BrowserEngine(Enum):
19
+ PLAYWRIGHT = "playwright"
20
+ PUPPETEER = "puppeteer"
21
+ SELENIUM = "selenium"
22
+ NONE = "none"
23
+
24
+
25
+ @dataclass
26
+ class XSSResult:
27
+ vuln_type: str
28
+ payload: str
29
+ location: str
30
+ sink: Optional[str] = None
31
+ severity: str = "medium"
32
+ evidence: str = ""
33
+ url: str = ""
34
+
35
+
36
+ @dataclass
37
+ class BrowserTestConfig:
38
+ target_url: str
39
+ engine: BrowserEngine = BrowserEngine.NONE
40
+ headless: bool = True
41
+ timeout: int = 30000
42
+ wait_for_selector: Optional[str] = None
43
+ screenshot_on_error: bool = False
44
+ console_log: bool = False
45
+ user_data_dir: Optional[str] = None
46
+ proxy: Optional[str] = None
47
+
48
+
49
+ class BrowserAutomationTester:
50
+ """
51
+ 浏览器动态测试引擎
52
+
53
+ 支持:
54
+ - DOM XSS 检测
55
+ - SPA 路由测试
56
+ - 表单交互测试
57
+ - JavaScript 执行检测
58
+ - Cookie/Session 测试
59
+ """
60
+
61
+ def __init__(self, config: BrowserTestConfig):
62
+ self.config = config
63
+ self.engine = config.engine
64
+ self.results: List[XSSResult] = []
65
+ self._browser = None
66
+ self._context = None
67
+ self._page = None
68
+ self._init_engine()
69
+
70
+ def _init_engine(self):
71
+ """初始化浏览器引擎"""
72
+ if self.engine == BrowserEngine.PLAYWRIGHT:
73
+ self._init_playwright()
74
+ elif self.engine == BrowserEngine.SELENIUM:
75
+ self._init_selenium()
76
+ elif self.engine == BrowserEngine.PUPPETEER:
77
+ self._init_puppeteer()
78
+ else:
79
+ print("[!] No browser engine available. Install: pip install playwright")
80
+
81
+ def _init_playwright(self):
82
+ """初始化 Playwright"""
83
+ try:
84
+ from playwright.sync_api import sync_playwright
85
+ self._playwright = sync_playwright().start()
86
+ self._browser = self._playwright.chromium.launch(
87
+ headless=self.config.headless,
88
+ args=["--no-sandbox", "--disable-dev-shm-usage"]
89
+ )
90
+ self._context = self._browser.new_context(
91
+ ignore_https_errors=True,
92
+ user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
93
+ )
94
+ self._page = self._context.new_page()
95
+ if self.config.console_log:
96
+ self._page.on("console", lambda msg: print(f"[Browser Console] {msg.text}"))
97
+ print("[+] Playwright initialized successfully")
98
+ except ImportError:
99
+ print("[!] Playwright not installed. Run: pip install playwright && playwright install chromium")
100
+ self.engine = BrowserEngine.NONE
101
+ except Exception as e:
102
+ print(f"[!] Playwright init failed: {e}")
103
+ self.engine = BrowserEngine.NONE
104
+
105
+ def _init_selenium(self):
106
+ """初始化 Selenium"""
107
+ try:
108
+ from selenium import webdriver
109
+ from selenium.webdriver.chrome.options import Options
110
+ from selenium.webdriver.chrome.service import Service
111
+
112
+ options = Options()
113
+ if self.config.headless:
114
+ options.add_argument("--headless")
115
+ options.add_argument("--no-sandbox")
116
+ options.add_argument("--disable-dev-shm-usage")
117
+ if self.config.proxy:
118
+ options.add_argument(f"--proxy-server={self.config.proxy}")
119
+
120
+ self._driver = webdriver.Chrome(options=options)
121
+ self._driver.set_page_load_timeout(self.config.timeout / 1000)
122
+ print("[+] Selenium initialized successfully")
123
+ except ImportError:
124
+ print("[!] Selenium not installed. Run: pip install selenium")
125
+ self.engine = BrowserEngine.NONE
126
+ except Exception as e:
127
+ print(f"[!] Selenium init failed: {e}")
128
+ self.engine = BrowserEngine.NONE
129
+
130
+ def _init_puppeteer(self):
131
+ """初始化 Puppeteer (通过 pyppeteer)"""
132
+ try:
133
+ import asyncio
134
+ from pyppeteer import launch
135
+ asyncio.get_event_loop().run_until_complete(
136
+ self._init_puppeteer_async()
137
+ )
138
+ except ImportError:
139
+ print("[!] pyppeteer not installed. Run: pip install pyppeteer")
140
+ self.engine = BrowserEngine.NONE
141
+ except Exception as e:
142
+ print(f"[!] Puppeteer init failed: {e}")
143
+ self.engine = BrowserEngine.NONE
144
+
145
+ async def _init_puppeteer_async(self):
146
+ self._browser = await launch(
147
+ headless=self.config.headless,
148
+ args=["--no-sandbox", "--disable-dev-shm-usage"]
149
+ )
150
+ self._page = await self._browser.newPage()
151
+ print("[+] Puppeteer initialized successfully")
152
+
153
+ def test_dom_xss(self, payloads: List[Dict]) -> List[XSSResult]:
154
+ """
155
+ 测试 DOM XSS 漏洞
156
+
157
+ 检测 sinks: innerHTML, eval, document.write, location.href, etc.
158
+ """
159
+ if self.engine == BrowserEngine.NONE:
160
+ return []
161
+
162
+ dom_sinks = {
163
+ "innerHTML": r'innerHTML\s*=',
164
+ "outerHTML": r'outerHTML\s*=',
165
+ "insertAdjacentHTML": r'insertAdjacentHTML',
166
+ "document.write": r'document\.write',
167
+ "eval": r'eval\(',
168
+ "setTimeout": r'setTimeout\s*\(',
169
+ "setInterval": r'setInterval\s*\(',
170
+ "Function": r'new\s+Function\(',
171
+ "location.href": r'location\.href',
172
+ "location.hash": r'location\.hash',
173
+ "location.search": r'location\.search',
174
+ "document.cookie": r'document\.cookie',
175
+ "localStorage": r'localStorage\.',
176
+ "sessionStorage": r'sessionStorage\.',
177
+ }
178
+
179
+ xss_payloads = [p for p in payloads if p.get("type") in ["dom", "reflected", "polyglot"]]
180
+
181
+ print(f"[*] Testing {len(xss_payloads)} DOM XSS payloads...")
182
+
183
+ for payload_data in xss_payloads:
184
+ payload = payload_data.get("payload", "")
185
+ sink = payload_data.get("sink", "")
186
+
187
+ for url_pattern, source in [
188
+ (f"{self.config.target_url}?input={payload}", "URL parameter"),
189
+ (f"{self.config.target_url}#{payload}", "URL fragment"),
190
+ (f"{self.config.target_url}?redirect={payload}", "redirect param"),
191
+ ]:
192
+ try:
193
+ self._navigate_and_check(url_pattern, payload, sink, dom_sinks)
194
+ except Exception as e:
195
+ print(f"[!] Error testing payload: {e}")
196
+
197
+ return self.results
198
+
199
+ def _navigate_and_check(self, url: str, payload: str, sink: str, dom_sinks: Dict):
200
+ """导航到 URL 并检查 XSS"""
201
+ if self.engine == BrowserEngine.PLAYWRIGHT:
202
+ self._page.goto(url, timeout=self.config.timeout)
203
+ self._page.wait_for_load_state("networkidle", timeout=self.config.timeout)
204
+ time.sleep(1)
205
+
206
+ content = self._page.content()
207
+
208
+ for sink_pattern, sink_name in dom_sinks.items():
209
+ if re.search(sink_pattern, content, re.IGNORECASE):
210
+ if payload in content or any(x in content for x in ["<script", "<img", "<svg"]):
211
+ self.results.append(XSSResult(
212
+ vuln_type="DOM XSS",
213
+ payload=payload,
214
+ location="client-side",
215
+ sink=sink_name,
216
+ severity="high",
217
+ evidence=f"Sink '{sink_name}' found with payload",
218
+ url=url
219
+ ))
220
+ return
221
+
222
+ def test_spa_routing(self, routes: List[str]) -> Dict[str, Any]:
223
+ """
224
+ 测试 SPA 路由安全性
225
+
226
+ 1. 测试路由参数中的 XSS
227
+ 2. 测试认证绕过
228
+ 3. 测试敏感端点访问
229
+ """
230
+ if self.engine == BrowserEngine.NONE:
231
+ return {"error": "No browser engine"}
232
+
233
+ results = {
234
+ "routes_tested": [],
235
+ "xss_found": [],
236
+ "auth_bypass": [],
237
+ "sensitive_access": []
238
+ }
239
+
240
+ test_params = ["<script>alert(1)</script>", "' Or '1'='1", "../../../etc/passwd"]
241
+
242
+ for route in routes:
243
+ full_url = f"{self.config.target_url}{route}"
244
+
245
+ try:
246
+ if self.engine == BrowserEngine.PLAYWRIGHT:
247
+ self._page.goto(full_url, timeout=self.config.timeout)
248
+ self._page.wait_for_load_state("networkidle")
249
+ time.sleep(1)
250
+
251
+ results["routes_tested"].append(route)
252
+
253
+ content = self._page.content()
254
+ for param in test_params:
255
+ if param in content:
256
+ results["xss_found"].append({
257
+ "route": route,
258
+ "payload": param
259
+ })
260
+
261
+ title = self._page.title()
262
+ if "admin" in title.lower() or "dashboard" in title.lower():
263
+ results["sensitive_access"].append({
264
+ "route": route,
265
+ "title": title
266
+ })
267
+
268
+ except Exception as e:
269
+ print(f"[!] Error testing route {route}: {e}")
270
+
271
+ return results
272
+
273
+ def test_form_interaction(self, form_selectors: List[Dict]) -> List[XSSResult]:
274
+ """
275
+ 测试表单交互 XSS
276
+
277
+ 填写表单并提交,检测存储型 XSS
278
+ """
279
+ if self.engine == BrowserEngine.NONE:
280
+ return []
281
+
282
+ results = []
283
+
284
+ xss_test_payloads = [
285
+ "<script>alert(1)</script>",
286
+ "<img src=x onerror=alert(1)>",
287
+ "javascript:alert(1)",
288
+ "'; alert(1); //"
289
+ ]
290
+
291
+ for form in form_selectors:
292
+ selector = form.get("selector")
293
+ fields = form.get("fields", {})
294
+
295
+ for payload in xss_test_payloads:
296
+ try:
297
+ if self.engine == BrowserEngine.PLAYWRIGHT:
298
+ self._page.goto(self.config.target_url, timeout=self.config.timeout)
299
+ self._page.wait_for_load_state("networkidle")
300
+
301
+ for field_name, field_type in fields.items():
302
+ if field_type == "text":
303
+ self._page.fill(f"input[name='{field_name}']", payload)
304
+ elif field_type == "textarea":
305
+ self._page.fill(f"textarea[name='{field_name}']", payload)
306
+
307
+ if "submit" in fields:
308
+ self._page.click(f"button[type='submit'], input[type='submit']")
309
+ time.sleep(2)
310
+
311
+ content = self._page.content()
312
+ if payload in content:
313
+ results.append(XSSResult(
314
+ vuln_type="Stored XSS",
315
+ payload=payload,
316
+ location=f"Form: {selector}",
317
+ severity="critical",
318
+ evidence="Payload persisted after submission"
319
+ ))
320
+
321
+ except Exception as e:
322
+ print(f"[!] Form test error: {e}")
323
+
324
+ return results
325
+
326
+ def test_javascript_execution(self, payload: str) -> Tuple[bool, str]:
327
+ """
328
+ 测试 JavaScript 是否被执行
329
+
330
+ 使用 console.log 配合 CDP 获取执行结果
331
+ """
332
+ if self.engine == BrowserEngine.NONE:
333
+ return False, "No browser engine"
334
+
335
+ test_payloads = [
336
+ f"<script>console.log('XSS_TEST_{payload[:20]}')</script>",
337
+ f"<img src=x onerror='console.log(\"XSS_TEST_{payload[:20]}\")'>",
338
+ f"<svg onload='console.log(\"XSS_TEST_{payload[:20]}\")'>"
339
+ ]
340
+
341
+ for test_payload in test_payloads:
342
+ try:
343
+ if self.engine == BrowserEngine.PLAYWRIGHT:
344
+ console_messages = []
345
+
346
+ def handle_console(msg):
347
+ if "XSS_TEST" in msg.text:
348
+ console_messages.append(msg.text)
349
+
350
+ self._page.on("console", handle_console)
351
+
352
+ test_url = f"{self.config.target_url}?input={test_payload}"
353
+ self._page.goto(test_url, timeout=self.config.timeout)
354
+ self._page.wait_for_load_state("networkidle")
355
+ time.sleep(2)
356
+
357
+ if console_messages:
358
+ return True, f"JS executed: {console_messages}"
359
+
360
+ except Exception as e:
361
+ continue
362
+
363
+ return False, "No JS execution detected"
364
+
365
+ def capture_network_requests(self) -> List[Dict]:
366
+ """捕获网络请求,用于分析 API 调用"""
367
+ if self.engine == BrowserEngine.PLAYWRIGHT:
368
+ requests = []
369
+
370
+ def handle_request(request):
371
+ requests.append({
372
+ "url": request.url,
373
+ "method": request.method,
374
+ "headers": dict(request.headers),
375
+ "post_data": request.post_data
376
+ })
377
+
378
+ self._page.on("request", handle_request)
379
+ self._page.reload()
380
+ self._page.wait_for_load_state("networkidle")
381
+
382
+ return requests
383
+ return []
384
+
385
+ def get_client_storage(self) -> Dict[str, Dict]:
386
+ """获取客户端存储 (localStorage, sessionStorage, cookies)"""
387
+ if self.engine == BrowserEngine.PLAYWRIGHT:
388
+ return {
389
+ "localStorage": self._page.evaluate("() => JSON.stringify(localStorage)"),
390
+ "sessionStorage": self._page.evaluate("() => JSON.stringify(sessionStorage)"),
391
+ "cookies": [dict(c) for c in self._context.cookies()]
392
+ }
393
+ return {}
394
+
395
+ def close(self):
396
+ """关闭浏览器"""
397
+ try:
398
+ if self._browser:
399
+ self._browser.close()
400
+ if hasattr(self, '_driver') and self._driver:
401
+ self._driver.quit()
402
+ except:
403
+ pass
404
+
405
+
406
+ def auto_detect_engine() -> BrowserEngine:
407
+ """自动检测可用的浏览器引擎"""
408
+ try:
409
+ from playwright.sync_api import sync_playwright
410
+ return BrowserEngine.PLAYWRIGHT
411
+ except ImportError:
412
+ pass
413
+
414
+ try:
415
+ from selenium import webdriver
416
+ return BrowserEngine.SELENIUM
417
+ except ImportError:
418
+ pass
419
+
420
+ return BrowserEngine.NONE
421
+
422
+
423
+ def create_tester(target_url: str, headless: bool = True) -> Optional[BrowserAutomationTester]:
424
+ """创建浏览器测试器,自动选择可用引擎"""
425
+ engine = auto_detect_engine()
426
+
427
+ if engine == BrowserEngine.NONE:
428
+ print("[!] No browser automation library found.")
429
+ print("[*] Install one of:")
430
+ print(" - Playwright: pip install playwright && playwright install chromium")
431
+ print(" - Selenium: pip install selenium")
432
+ return None
433
+
434
+ config = BrowserTestConfig(
435
+ target_url=target_url,
436
+ engine=engine,
437
+ headless=headless,
438
+ timeout=30000,
439
+ console_log=False
440
+ )
441
+
442
+ return BrowserAutomationTester(config)
443
+
444
+
445
+ # CLI interface
446
+ if __name__ == "__main__":
447
+ import argparse
448
+
449
+ parser = argparse.ArgumentParser(description="Browser Automation Security Tester")
450
+ parser.add_argument("--target", required=True, help="Target URL")
451
+ parser.add_argument("--type", choices=["dom", "spa", "form", "all"], default="all")
452
+ parser.add_argument("--payloads", help="Path to payloads JSON file")
453
+ parser.add_argument("--routes", nargs="+", help="SPA routes to test")
454
+ parser.add_argument("--headless", action="store_true", default=True)
455
+
456
+ args = parser.parse_args()
457
+
458
+ tester = create_tester(args.target, headless=args.headless)
459
+
460
+ if not tester:
461
+ exit(1)
462
+
463
+ payloads = [
464
+ {"id": "xss-001", "payload": "<script>alert(1)</script>", "type": "dom"},
465
+ {"id": "xss-002", "payload": "<img src=x onerror=alert(1)>", "type": "reflected"},
466
+ ]
467
+
468
+ if args.type in ["dom", "all"]:
469
+ print("\n[*] Running DOM XSS tests...")
470
+ results = tester.test_dom_xss(payloads)
471
+ print(f"[+] Found {len(results)} DOM XSS vulnerabilities")
472
+
473
+ if args.type in ["spa", "all"]:
474
+ routes = args.routes or ["/", "/admin", "/dashboard", "/login"]
475
+ print("\n[*] Running SPA routing tests...")
476
+ results = tester.test_spa_routing(routes)
477
+ print(f"[+] Tested {len(results.get('routes_tested', []))} routes")
478
+
479
+ tester.close()