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.
Files changed (71) hide show
  1. package/README.md +74 -0
  2. package/SKILL.md +1797 -0
  3. package/core/advanced_recon.py +788 -0
  4. package/core/agentic_analyzer.py +445 -0
  5. package/core/analyzers/api_parser.py +210 -0
  6. package/core/analyzers/response_analyzer.py +212 -0
  7. package/core/analyzers/sensitive_finder.py +184 -0
  8. package/core/api_fuzzer.py +422 -0
  9. package/core/api_interceptor.py +525 -0
  10. package/core/api_parser.py +955 -0
  11. package/core/browser_tester.py +479 -0
  12. package/core/cloud_storage_tester.py +1330 -0
  13. package/core/collectors/__init__.py +23 -0
  14. package/core/collectors/api_path_finder.py +300 -0
  15. package/core/collectors/browser_collect.py +645 -0
  16. package/core/collectors/browser_collector.py +411 -0
  17. package/core/collectors/http_client.py +111 -0
  18. package/core/collectors/js_collector.py +490 -0
  19. package/core/collectors/js_parser.py +780 -0
  20. package/core/collectors/url_collector.py +319 -0
  21. package/core/context_manager.py +682 -0
  22. package/core/deep_api_tester_v35.py +844 -0
  23. package/core/deep_api_tester_v55.py +366 -0
  24. package/core/dynamic_api_analyzer.py +532 -0
  25. package/core/http_client.py +179 -0
  26. package/core/models.py +296 -0
  27. package/core/orchestrator.py +890 -0
  28. package/core/prerequisite.py +227 -0
  29. package/core/reasoning_engine.py +1042 -0
  30. package/core/response_classifier.py +606 -0
  31. package/core/runner.py +938 -0
  32. package/core/scan_engine.py +599 -0
  33. package/core/skill_executor.py +435 -0
  34. package/core/skill_executor_v2.py +670 -0
  35. package/core/skill_executor_v3.py +704 -0
  36. package/core/smart_analyzer.py +687 -0
  37. package/core/strategy_pool.py +707 -0
  38. package/core/testers/auth_tester.py +264 -0
  39. package/core/testers/idor_tester.py +200 -0
  40. package/core/testers/sqli_tester.py +211 -0
  41. package/core/testing_loop.py +655 -0
  42. package/core/utils/base_path_dict.py +255 -0
  43. package/core/utils/payload_lib.py +167 -0
  44. package/core/utils/ssrf_detector.py +220 -0
  45. package/core/verifiers/vuln_verifier.py +536 -0
  46. package/package.json +1 -1
  47. package/references/README.md +72 -0
  48. package/references/asset-discovery.md +119 -0
  49. package/references/fuzzing-patterns.md +129 -0
  50. package/references/graphql-guidance.md +108 -0
  51. package/references/intake.md +84 -0
  52. package/references/pua-agent.md +192 -0
  53. package/references/report-template.md +156 -0
  54. package/references/rest-guidance.md +76 -0
  55. package/references/severity-model.md +76 -0
  56. package/references/test-matrix.md +86 -0
  57. package/references/validation.md +78 -0
  58. package/references/vulnerabilities/01-sqli-tests.md +1128 -0
  59. package/references/vulnerabilities/02-user-enum-tests.md +423 -0
  60. package/references/vulnerabilities/03-jwt-tests.md +499 -0
  61. package/references/vulnerabilities/04-idor-tests.md +362 -0
  62. package/references/vulnerabilities/05-sensitive-data-tests.md +466 -0
  63. package/references/vulnerabilities/06-biz-logic-tests.md +501 -0
  64. package/references/vulnerabilities/07-security-config-tests.md +511 -0
  65. package/references/vulnerabilities/08-brute-force-tests.md +457 -0
  66. package/references/vulnerabilities/09-vulnerability-chains.md +465 -0
  67. package/references/vulnerabilities/10-auth-tests.md +537 -0
  68. package/references/vulnerabilities/11-graphql-tests.md +355 -0
  69. package/references/vulnerabilities/12-ssrf-tests.md +396 -0
  70. package/references/vulnerabilities/README.md +148 -0
  71. package/references/workflows.md +192 -0
@@ -0,0 +1,599 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ ScanEngine - 统一扫描引擎
4
+ 提供 Collector → Analyzer → Tester 的三阶段 Pipeline 架构
5
+ """
6
+
7
+ import asyncio
8
+ import time
9
+ import logging
10
+ from typing import Dict, List, Optional, Set, Any, Callable
11
+ from dataclasses import dataclass, field
12
+ from datetime import datetime
13
+ from enum import Enum
14
+ from urllib.parse import urlparse
15
+
16
+ try:
17
+ import requests
18
+ HAS_REQUESTS = True
19
+ except ImportError:
20
+ HAS_REQUESTS = False
21
+
22
+ try:
23
+ from .collectors import JSCollector, ApiPathFinder, URLCollector, BrowserCollectorFacade, JSFingerprintCache
24
+ except ImportError:
25
+ from collectors import JSCollector, ApiPathFinder, URLCollector, BrowserCollectorFacade, JSFingerprintCache
26
+ from .models import APIEndpoint, Vulnerability, ScanResult, Severity
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ class ScanStage(Enum):
32
+ """扫描阶段"""
33
+ COLLECT = "collect"
34
+ ANALYZE = "analyze"
35
+ TEST = "test"
36
+ REPORT = "report"
37
+
38
+
39
+ @dataclass
40
+ class ScanEngineConfig:
41
+ """扫描引擎配置"""
42
+ target: str
43
+ concurrency: int = 50
44
+ timeout: int = 30
45
+ js_depth: int = 3
46
+ cookies: str = ""
47
+ proxy: Optional[str] = None
48
+ verify_ssl: bool = True
49
+ output_dir: str = "./results"
50
+
51
+ # 各阶段开关
52
+ enable_js_collect: bool = True
53
+ enable_api_collect: bool = True
54
+ enable_browser_collect: bool = True
55
+ enable_sqli_test: bool = True
56
+ enable_xss_test: bool = True
57
+ enable_idor_test: bool = True
58
+ enable_info_test: bool = True
59
+
60
+ # 评分阈值
61
+ high_value_threshold: int = 5
62
+
63
+
64
+ @dataclass
65
+ class ScanProgress:
66
+ """扫描进度"""
67
+ stage: ScanStage
68
+ phase: str
69
+ total_apis: int = 0
70
+ alive_apis: int = 0
71
+ high_value_apis: int = 0
72
+ vulnerabilities: int = 0
73
+ start_time: float = field(default_factory=time.time)
74
+
75
+ def elapsed(self) -> float:
76
+ return time.time() - self.start_time
77
+
78
+ def to_dict(self) -> Dict:
79
+ return {
80
+ 'stage': self.stage.value,
81
+ 'phase': self.phase,
82
+ 'total_apis': self.total_apis,
83
+ 'alive_apis': self.alive_apis,
84
+ 'high_value_apis': self.high_value_apis,
85
+ 'vulnerabilities': self.vulnerabilities,
86
+ 'elapsed_seconds': round(self.elapsed(), 2)
87
+ }
88
+
89
+
90
+ class ScanEngine:
91
+ """
92
+ 统一扫描引擎
93
+
94
+ 三阶段 Pipeline:
95
+ ┌─────────────────────────────────────────────────────────────┐
96
+ │ Stage 1: COLLECT (采集) │
97
+ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐│
98
+ │ │ JS采集 │→ │ API发现 │→ │ URL采集 │→ │ 浏览器采集││
99
+ │ │ (递归深度3)│ │ (25+正则) │ │ (域名/Base│ │ (动态渲染││
100
+ │ └───────────┘ └───────────┘ └───────────┘ └───────────┘│
101
+ └─────────────────────────────────────────────────────────────┘
102
+
103
+ ┌─────────────────────────────────────────────────────────────┐
104
+ │ Stage 2: ANALYZE (分析) │
105
+ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
106
+ │ │ HTTP探测 │→ │ API评分 │→ │ 敏感信息 │ │
107
+ │ │ (验证存活)│ │ (高价值) │ │ 检测 │ │
108
+ │ └───────────┘ └───────────┘ └───────────┘ │
109
+ └─────────────────────────────────────────────────────────────┘
110
+
111
+ ┌─────────────────────────────────────────────────────────────┐
112
+ │ Stage 3: TEST (测试) │
113
+ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐│
114
+ │ │ SQL注入 │→ │ XSS测试 │→ │ IDOR测试 │→ │ 信息泄露 ││
115
+ │ └───────────┘ └───────────┘ └───────────┘ └───────────┘│
116
+ └─────────────────────────────────────────────────────────────┘
117
+ """
118
+
119
+ def __init__(self, config: ScanEngineConfig):
120
+ self.config = config
121
+ self.session = requests.Session() if HAS_REQUESTS else None
122
+
123
+ self._js_cache: Optional[JSFingerprintCache] = None
124
+ self._js_collector: Optional[JSCollector] = None
125
+ self._api_finder: Optional[ApiPathFinder] = None
126
+ self._url_collector: Optional[URLCollector] = None
127
+ self._browser_collector: Optional[BrowserCollectorFacade] = None
128
+
129
+ self.progress = ScanProgress(stage=ScanStage.COLLECT, phase="init")
130
+ self.result: Optional[ScanResult] = None
131
+
132
+ self._callbacks: Dict[str, List[Callable]] = {
133
+ 'stage_start': [],
134
+ 'stage_progress': [],
135
+ 'stage_complete': [],
136
+ 'finding': [],
137
+ }
138
+
139
+ self._running = False
140
+
141
+ def on(self, event: str, callback: Callable):
142
+ """注册事件回调"""
143
+ if event in self._callbacks:
144
+ self._callbacks[event].append(callback)
145
+
146
+ def _emit(self, event: str, data: Any):
147
+ """触发事件"""
148
+ for callback in self._callbacks.get(event, []):
149
+ try:
150
+ callback(data)
151
+ except Exception as e:
152
+ logger.warning(f"Callback error for {event}: {e}")
153
+
154
+ def _update_progress(self, stage: ScanStage, phase: str, **kwargs):
155
+ """更新进度"""
156
+ self.progress.stage = stage
157
+ self.progress.phase = phase
158
+ for key, value in kwargs.items():
159
+ if hasattr(self.progress, key):
160
+ setattr(self.progress, key, value)
161
+ self._emit('stage_progress', self.progress.to_dict())
162
+
163
+ async def initialize(self):
164
+ """初始化扫描引擎"""
165
+ self._running = True
166
+
167
+ if self.session:
168
+ self.session.headers.update({
169
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
170
+ })
171
+ if self.config.cookies:
172
+ self.session.headers['Cookie'] = self.config.cookies
173
+
174
+ self._js_collector = JSCollector(session=self.session, max_depth=self.config.js_depth)
175
+ self._api_finder = ApiPathFinder()
176
+ self._url_collector = URLCollector(session=self.session)
177
+ self._browser_collector = BrowserCollectorFacade(headless=True)
178
+
179
+ self.result = ScanResult(
180
+ target_url=self.config.target,
181
+ start_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
182
+ )
183
+
184
+ async def run(self) -> ScanResult:
185
+ """运行完整扫描流程"""
186
+ await self.initialize()
187
+
188
+ try:
189
+ # Stage 1: 采集
190
+ self._emit('stage_start', {'stage': 'collect'})
191
+ self._update_progress(ScanStage.COLLECT, 'js_collection')
192
+ await self._run_collectors()
193
+ self._emit('stage_complete', {'stage': 'collect'})
194
+
195
+ # Stage 2: 分析
196
+ self._emit('stage_start', {'stage': 'analyze'})
197
+ self._update_progress(ScanStage.ANALYZE, 'api_scoring')
198
+ await self._run_analyzers()
199
+ self._emit('stage_complete', {'stage': 'analyze'})
200
+
201
+ # Stage 3: 测试
202
+ if self.result and self.result.api_endpoints:
203
+ self._emit('stage_start', {'stage': 'test'})
204
+ self._update_progress(ScanStage.TEST, 'vulnerability_testing')
205
+ await self._run_testers()
206
+ self._emit('stage_complete', {'stage': 'test'})
207
+
208
+ # Stage 4: 报告
209
+ self._emit('stage_start', {'stage': 'report'})
210
+ self._update_progress(ScanStage.REPORT, 'report_generation')
211
+ self._emit('stage_complete', {'stage': 'report'})
212
+
213
+ if self.result:
214
+ self.result.status = "completed"
215
+
216
+ except Exception as e:
217
+ logger.error(f"Scan error: {e}")
218
+ if self.result:
219
+ self.result.errors.append(str(e))
220
+ self.result.status = "failed"
221
+
222
+ return self.result or ScanResult(target_url=self.config.target)
223
+
224
+ async def _run_collectors(self):
225
+ """运行采集阶段"""
226
+ collected_js = []
227
+ collected_apis = []
228
+
229
+ try:
230
+ resp = self.session.get(self.config.target, timeout=self.config.timeout)
231
+ html = resp.text
232
+ except Exception as e:
233
+ logger.error(f"Failed to fetch target: {e}")
234
+ html = ""
235
+
236
+ # JS 采集
237
+ if self.config.enable_js_collect and html:
238
+ self._update_progress(ScanStage.COLLECT, phase='js_collect')
239
+
240
+ js_urls = self._js_collector.extract_js_from_html(html, self.config.target)
241
+ logger.info(f"Found {len(js_urls)} JS files in HTML")
242
+
243
+ for js_url in js_urls[:20]:
244
+ try:
245
+ js_resp = self.session.get(js_url, timeout=self.config.timeout)
246
+ if js_resp.status_code == 200:
247
+ content = js_resp.text
248
+ collected_js.append({'url': js_url, 'content': content})
249
+
250
+ apis = self._api_finder.find_api_paths_in_text(content, js_url)
251
+ collected_apis.extend(apis)
252
+
253
+ self._js_collector.parse_js_content(js_url, content)
254
+ except Exception as e:
255
+ logger.debug(f"JS fetch error for {js_url}: {e}")
256
+
257
+ logger.info(f"JS collection: {len(collected_js)} files, {len(collected_apis)} APIs")
258
+
259
+ # URL 采集
260
+ if self.config.enable_url_collect and html:
261
+ self._update_progress(ScanStage.COLLECT, phase='url_collect')
262
+
263
+ url_result = self._url_collector.collect_from_html(html, self.config.target)
264
+ logger.info(f"URL collection: domains={len(url_result.domains)}, static={len(url_result.static_urls)}")
265
+
266
+ # 浏览器动态采集
267
+ if self.config.enable_browser_collect:
268
+ self._update_progress(ScanStage.COLLECT, phase='browser_collect')
269
+
270
+ try:
271
+ browser_result = self._browser_collector.collect_all(self.config.target, {
272
+ 'capture_console': True,
273
+ 'capture_storage': True
274
+ })
275
+
276
+ for js_url in browser_result.get('js_urls', []):
277
+ if js_url not in [j['url'] for j in collected_js]:
278
+ try:
279
+ js_resp = self.session.get(js_url, timeout=self.config.timeout)
280
+ if js_resp.status_code == 200:
281
+ collected_js.append({'url': js_url, 'content': js_resp.text})
282
+ except:
283
+ pass
284
+
285
+ for req in browser_result.get('api_requests', []):
286
+ if req.get('method') in ['POST', 'GET', 'PUT', 'DELETE']:
287
+ collected_apis.append({
288
+ 'path': req['url'],
289
+ 'method': req['method'],
290
+ 'source': 'browser'
291
+ })
292
+
293
+ logger.info(f"Browser collection: {len(browser_result.get('js_urls', []))} JS, {len(browser_result.get('api_requests', []))} API requests")
294
+ except Exception as e:
295
+ logger.warning(f"Browser collection failed: {e}")
296
+
297
+ # 存储采集结果
298
+ if not hasattr(self.result, 'collector_data'):
299
+ self.result.collector_data = {}
300
+
301
+ self.result.collector_data['js_files'] = collected_js
302
+ self.result.collector_data['api_paths'] = collected_apis
303
+ self.result.collector_data['js_cache'] = self._js_collector.cache
304
+
305
+ self._update_progress(
306
+ ScanStage.COLLECT,
307
+ phase='complete',
308
+ total_apis=len(collected_apis)
309
+ )
310
+
311
+ async def _run_analyzers(self):
312
+ """运行分析阶段"""
313
+ if not self.result or 'api_paths' not in self.result.collector_data:
314
+ return
315
+
316
+ apis = self.result.collector_data['api_paths']
317
+ api_endpoints: List[APIEndpoint] = []
318
+ alive_apis = []
319
+ high_value_apis = []
320
+
321
+ self._update_progress(ScanStage.ANALYZE, phase='http_probe')
322
+
323
+ seen_paths = set()
324
+ for api in apis:
325
+ path = api.get('path', '') or api.get('url', '')
326
+ method = api.get('method', 'GET')
327
+
328
+ if not path or path in seen_paths:
329
+ continue
330
+ seen_paths.add(path)
331
+
332
+ full_url = path if path.startswith('http') else f"{self.config.target.rstrip('/')}{path}"
333
+
334
+ endpoint = APIEndpoint(
335
+ path=path,
336
+ method=method,
337
+ source=api.get('source', 'unknown'),
338
+ full_url=full_url
339
+ )
340
+
341
+ score = self._score_endpoint(endpoint)
342
+ endpoint.score = score
343
+ endpoint.is_high_value = score >= self.config.high_value_threshold
344
+
345
+ api_endpoints.append(endpoint)
346
+
347
+ if self._probe_endpoint(endpoint):
348
+ alive_apis.append(endpoint)
349
+ if endpoint.is_high_value:
350
+ high_value_apis.append(endpoint)
351
+
352
+ self.result.api_endpoints = api_endpoints
353
+ self.result.alive_apis = len(alive_apis)
354
+ self.result.high_value_apis = len(high_value_apis)
355
+ self.result.total_apis = len(api_endpoints)
356
+
357
+ logger.info(f"Analysis: {len(api_endpoints)} total, {len(alive_apis)} alive, {len(high_value_apis)} high-value")
358
+
359
+ self._update_progress(
360
+ ScanStage.ANALYZE,
361
+ phase='complete',
362
+ total_apis=len(api_endpoints),
363
+ alive_apis=len(alive_apis),
364
+ high_value_apis=len(high_value_apis)
365
+ )
366
+
367
+ def _score_endpoint(self, endpoint: APIEndpoint) -> int:
368
+ """
369
+ API 评分算法
370
+
371
+ 评分维度:
372
+ - 路径特征: 包含 admin/user/auth 等关键字 (+3分)
373
+ - HTTP方法: POST/PUT/DELETE (+2分), GET (+1分)
374
+ - 参数数量: 有参数 (+1分)
375
+ - 敏感关键字: 包含 token/key/secret/password (+5分)
376
+ """
377
+ score = 0
378
+ path_lower = endpoint.path.lower()
379
+
380
+ if any(k in path_lower for k in ['admin', 'user', 'auth', 'login', 'pass', 'token']):
381
+ score += 3
382
+
383
+ if endpoint.method in ['POST', 'PUT', 'DELETE']:
384
+ score += 2
385
+ elif endpoint.method == 'GET':
386
+ score += 1
387
+
388
+ if endpoint.parameters:
389
+ score += 1
390
+
391
+ sensitive_keywords = ['token', 'key', 'secret', 'password', 'jwt', 'bearer', 'auth']
392
+ if any(k in path_lower for k in sensitive_keywords):
393
+ score += 5
394
+
395
+ return score
396
+
397
+ def _probe_endpoint(self, endpoint: APIEndpoint) -> bool:
398
+ """探测端点是否存活"""
399
+ try:
400
+ resp = self.session.request(
401
+ endpoint.method,
402
+ endpoint.full_url,
403
+ timeout=self.config.timeout,
404
+ allow_redirects=False
405
+ )
406
+
407
+ endpoint.status_code = resp.status_code
408
+ endpoint.content_length = len(resp.content)
409
+
410
+ return resp.status_code < 500
411
+ except Exception as e:
412
+ logger.debug(f"Probe failed for {endpoint.path}: {e}")
413
+ return False
414
+
415
+ async def _run_testers(self):
416
+ """运行测试阶段"""
417
+ if not self.result or not self.result.api_endpoints:
418
+ return
419
+
420
+ vulnerabilities: List[Vulnerability] = []
421
+
422
+ high_value_endpoints = [ep for ep in self.result.api_endpoints if ep.is_high_value]
423
+ test_targets = high_value_endpoints[:50]
424
+
425
+ self._update_progress(ScanStage.TEST, phase='sqli_test', vulnerabilities=len(vulnerabilities))
426
+
427
+ if self.config.enable_sqli_test:
428
+ for endpoint in test_targets:
429
+ vulns = await self._test_sqli(endpoint)
430
+ vulnerabilities.extend(vulns)
431
+
432
+ self._update_progress(ScanStage.TEST, phase='xss_test', vulnerabilities=len(vulnerabilities))
433
+
434
+ if self.config.enable_xss_test:
435
+ for endpoint in test_targets:
436
+ vulns = await self._test_xss(endpoint)
437
+ vulnerabilities.extend(vulns)
438
+
439
+ if self.config.enable_idor_test:
440
+ for endpoint in test_targets:
441
+ vulns = await self._test_idor(endpoint)
442
+ vulnerabilities.extend(vulns)
443
+
444
+ if self.config.enable_info_test:
445
+ for endpoint in test_targets:
446
+ vulns = await self._test_info_disclosure(endpoint)
447
+ vulnerabilities.extend(vulns)
448
+
449
+ self.result.vulnerabilities = vulnerabilities
450
+
451
+ self._update_progress(
452
+ ScanStage.TEST,
453
+ phase='complete',
454
+ vulnerabilities=len(vulnerabilities)
455
+ )
456
+
457
+ logger.info(f"Testing: {len(vulnerabilities)} vulnerabilities found")
458
+
459
+ async def _test_sqli(self, endpoint: APIEndpoint) -> List[Vulnerability]:
460
+ """SQL 注入测试"""
461
+ vulns = []
462
+
463
+ sqli_payloads = [
464
+ "' OR '1'='1",
465
+ "' OR 1=1--",
466
+ "admin'--",
467
+ "' UNION SELECT NULL--",
468
+ "' AND SLEEP(3)--",
469
+ ]
470
+
471
+ for payload in sqli_payloads:
472
+ try:
473
+ test_url = f"{endpoint.full_url}?id={payload}" if endpoint.method == 'GET' else endpoint.full_url
474
+
475
+ resp = self.session.post(
476
+ test_url,
477
+ data={'id': payload} if endpoint.method == 'POST' else None,
478
+ timeout=self.config.timeout
479
+ )
480
+
481
+ text_lower = resp.text.lower()
482
+ if any(s in text_lower for s in ['sql', 'syntax', 'error', 'mysql', 'oracle', 'warning']):
483
+ vulns.append(Vulnerability(
484
+ vuln_type='SQL Injection',
485
+ severity=Severity.HIGH,
486
+ endpoint=endpoint.path,
487
+ method=endpoint.method,
488
+ payload=payload,
489
+ evidence=f"SQL error detected in response"
490
+ ))
491
+ break
492
+ except:
493
+ pass
494
+
495
+ return vulns
496
+
497
+ async def _test_xss(self, endpoint: APIEndpoint) -> List[Vulnerability]:
498
+ """XSS 测试"""
499
+ vulns = []
500
+
501
+ xss_payloads = [
502
+ "<script>alert(1)</script>",
503
+ "<img src=x onerror=alert(1)>",
504
+ "<svg onload=alert(1)>",
505
+ "javascript:alert(1)",
506
+ ]
507
+
508
+ for payload in xss_payloads:
509
+ try:
510
+ test_url = f"{endpoint.full_url}?q={payload}"
511
+
512
+ resp = self.session.get(test_url, timeout=self.config.timeout)
513
+
514
+ if payload in resp.text:
515
+ vulns.append(Vulnerability(
516
+ vuln_type='XSS',
517
+ severity=Severity.MEDIUM,
518
+ endpoint=endpoint.path,
519
+ method=endpoint.method,
520
+ payload=payload,
521
+ evidence=f"Payload reflected in response"
522
+ ))
523
+ break
524
+ except:
525
+ pass
526
+
527
+ return vulns
528
+
529
+ async def _test_idor(self, endpoint: APIEndpoint) -> List[Vulnerability]:
530
+ """IDOR 测试"""
531
+ vulns = []
532
+
533
+ if endpoint.method != 'GET' or 'id' not in endpoint.path.lower():
534
+ return vulns
535
+
536
+ try:
537
+ resp1 = self.session.get(endpoint.full_url, timeout=self.config.timeout)
538
+ resp2 = self.session.get(f"{endpoint.full_url}?id=99999", timeout=self.config.timeout)
539
+
540
+ if resp1.status_code == resp2.status_code and len(resp1.content) != len(resp2.content):
541
+ vulns.append(Vulnerability(
542
+ vuln_type='IDOR',
543
+ severity=Severity.HIGH,
544
+ endpoint=endpoint.path,
545
+ method=endpoint.method,
546
+ payload='id=99999',
547
+ evidence=f"Different response for different ID values"
548
+ ))
549
+ except:
550
+ pass
551
+
552
+ return vulns
553
+
554
+ async def _test_info_disclosure(self, endpoint: APIEndpoint) -> List[Vulnerability]:
555
+ """信息泄露测试"""
556
+ vulns = []
557
+
558
+ sensitive_patterns = [
559
+ (r'AKIA[0-9A-Z]{16}', 'AWS Access Key'),
560
+ (r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', 'Email'),
561
+ (r'"password"\s*:\s*"[^"]+"', 'Password in response'),
562
+ (r'"token"\s*:\s*"[^"]+"', 'Token in response'),
563
+ (r'"secret"\s*:\s*"[^"]+"', 'Secret in response'),
564
+ ]
565
+
566
+ try:
567
+ resp = self.session.get(endpoint.full_url, timeout=self.config.timeout)
568
+
569
+ for pattern, info_type in sensitive_patterns:
570
+ import re
571
+ matches = re.findall(pattern, resp.text, re.IGNORECASE)
572
+ if matches:
573
+ vulns.append(Vulnerability(
574
+ vuln_type='Information Disclosure',
575
+ severity=Severity.LOW,
576
+ endpoint=endpoint.path,
577
+ method=endpoint.method,
578
+ payload='',
579
+ evidence=f"{info_type} found in response"
580
+ ))
581
+ except:
582
+ pass
583
+
584
+ return vulns
585
+
586
+ async def cleanup(self):
587
+ """清理资源"""
588
+ self._running = False
589
+ if self._browser_collector:
590
+ try:
591
+ self._browser_collector.collector.stop()
592
+ except:
593
+ pass
594
+
595
+
596
+ def create_scan_engine(target: str, **kwargs) -> ScanEngine:
597
+ """创建扫描引擎的工厂函数"""
598
+ config = ScanEngineConfig(target=target, **kwargs)
599
+ return ScanEngine(config)