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,844 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ 深度 API 渗透测试引擎 v3.5
5
+ - 递归分析所有 JS (包括 chunk)
6
+ - 全量流量捕获和提取
7
+ - 提取参数/接口/敏感信息/登录凭证
8
+ - 智能关联分析
9
+ """
10
+
11
+ from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeout
12
+ from urllib.parse import urljoin, urlparse, parse_qs, urlencode
13
+ import requests
14
+ import re
15
+ import json
16
+ import time
17
+ from collections import defaultdict
18
+ from typing import Dict, List, Set, Tuple
19
+ import hashlib
20
+
21
+ class DeepAPITester:
22
+ """深度 API 测试引擎 v3.5"""
23
+
24
+ def __init__(self, target: str, headless: bool = True, max_depth: int = 3):
25
+ self.target = target.rstrip('/')
26
+ self.headless = headless
27
+ self.max_depth = max_depth
28
+
29
+ # 存储数据
30
+ self.all_js_files: Set[str] = set()
31
+ self.all_api_endpoints: List[Dict] = []
32
+ self.all_traffic: List[Dict] = []
33
+ self.secrets: List[Dict] = []
34
+ self.credentials: List[Dict] = []
35
+ self.vulnerabilities: List[Dict] = []
36
+
37
+ # HTTP 会话
38
+ self.session = requests.Session()
39
+ self.session.headers.update({
40
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
41
+ 'Accept': '*/*',
42
+ 'Accept-Language': 'en-US,en;q=0.9'
43
+ })
44
+
45
+ # JS 分析缓存
46
+ self.analyzed_js: Set[str] = set()
47
+
48
+ def crawl_with_browser(self) -> Dict:
49
+ """使用无头浏览器爬取,全量捕获流量"""
50
+ print(f"\n{'='*70}")
51
+ print(f"[+] 使用无头浏览器爬取:{self.target}")
52
+ print(f"{'='*70}\n")
53
+
54
+ traffic_log = []
55
+
56
+ with sync_playwright() as p:
57
+ browser = p.chromium.launch(headless=self.headless)
58
+ context = browser.new_context(
59
+ viewport={'width': 1920, 'height': 1080},
60
+ ignore_https_errors=True
61
+ )
62
+ page = context.new_page()
63
+
64
+ # 拦截所有请求和响应
65
+ def handle_request(request):
66
+ url = request.url
67
+ method = request.method
68
+ resource_type = request.resource_type
69
+
70
+ # 记录所有请求
71
+ request_data = {
72
+ 'timestamp': time.time(),
73
+ 'url': url,
74
+ 'method': method,
75
+ 'resource_type': resource_type,
76
+ 'headers': dict(request.headers),
77
+ 'post_data': request.post_data,
78
+ 'params': self._extract_params(url, method, request.post_data)
79
+ }
80
+
81
+ traffic_log.append(request_data)
82
+ self.all_traffic.append(request_data)
83
+
84
+ # 分类处理
85
+ if resource_type == 'script':
86
+ self.all_js_files.add(url)
87
+ print(f" [JS] {url}")
88
+
89
+ elif self._is_api_request(url):
90
+ self.all_api_endpoints.append(request_data)
91
+ print(f" [API] {method} {url}")
92
+
93
+ def handle_response(response):
94
+ url = response.url
95
+ status = response.status
96
+
97
+ # 检查响应中的敏感信息
98
+ try:
99
+ body = response.text()
100
+ self._analyze_response_body(url, body)
101
+ except:
102
+ pass
103
+
104
+ page.on('request', handle_request)
105
+ page.on('response', handle_response)
106
+
107
+ try:
108
+ # 访问目标页面
109
+ print(f"[*] 访问目标页面...")
110
+ page.goto(self.target, wait_until='networkidle', timeout=30000)
111
+
112
+ # 等待 JS 执行
113
+ print(f"[*] 等待 JS 执行 (5 秒)...")
114
+ page.wait_for_timeout(5000)
115
+
116
+ # 递归探索页面
117
+ print(f"[*] 递归探索页面 (深度:{self.max_depth})...")
118
+ self._recursive_explore(page, depth=0)
119
+
120
+ # 提取 JS 文件
121
+ print(f"[*] 提取所有 JS 文件...")
122
+ js_from_dom = self._extract_all_js_from_dom(page)
123
+ self.all_js_files.update(js_from_dom)
124
+
125
+ # 执行 JS 提取路由
126
+ print(f"[*] 从 JS 中提取路由和 API...")
127
+ self._extract_routes_and_apis_from_all_js()
128
+
129
+ except PlaywrightTimeout:
130
+ print(f"[!] 页面加载超时")
131
+ except Exception as e:
132
+ print(f"[!] 错误:{e}")
133
+ finally:
134
+ browser.close()
135
+
136
+ return {
137
+ 'js_files': len(self.all_js_files),
138
+ 'api_endpoints': len(self.all_api_endpoints),
139
+ 'traffic': len(self.all_traffic)
140
+ }
141
+
142
+ def _recursive_explore(self, page, depth: int = 0):
143
+ """递归探索页面,触发更多 API"""
144
+ if depth >= self.max_depth:
145
+ return
146
+
147
+ print(f" [深度 {depth}] 探索页面...")
148
+
149
+ # 点击所有可点击元素
150
+ clickable_selectors = [
151
+ 'button', 'a[href]', 'input[type="button"]',
152
+ 'input[type="submit"]', '.btn', '[role="button"]',
153
+ '.clickable', '[onclick]'
154
+ ]
155
+
156
+ for selector in clickable_selectors:
157
+ try:
158
+ elements = page.query_selector_all(selector)
159
+ for elem in elements[:20]: # 限制数量
160
+ try:
161
+ elem.click()
162
+ page.wait_for_timeout(1000)
163
+ page.wait_for_load_state('networkidle', timeout=5000)
164
+ except:
165
+ pass
166
+ except:
167
+ pass
168
+
169
+ # 滚动页面
170
+ try:
171
+ page.evaluate('window.scrollTo(0, document.body.scrollHeight)')
172
+ page.wait_for_timeout(2000)
173
+ page.evaluate('window.scrollTo(0, 0)')
174
+ page.wait_for_timeout(1000)
175
+ except:
176
+ pass
177
+
178
+ # 填写表单并提交
179
+ try:
180
+ forms = page.query_selector_all('form')
181
+ for form in forms[:5]:
182
+ try:
183
+ # 尝试填写测试数据
184
+ inputs = form.query_selector_all('input[type="text"], input[type="email"]')
185
+ for inp in inputs[:3]:
186
+ try:
187
+ inp.fill('test@example.com')
188
+ except:
189
+ pass
190
+
191
+ # 点击提交
192
+ submit_btn = form.query_selector('input[type="submit"], button[type="submit"]')
193
+ if submit_btn:
194
+ submit_btn.click()
195
+ page.wait_for_timeout(2000)
196
+ except:
197
+ pass
198
+ except:
199
+ pass
200
+
201
+ # 递归
202
+ if depth < self.max_depth - 1:
203
+ self._recursive_explore(page, depth + 1)
204
+
205
+ def _extract_all_js_from_dom(self, page) -> Set[str]:
206
+ """从 DOM 提取所有 JS 文件"""
207
+ js_files = page.evaluate("""
208
+ () => {
209
+ const scripts = document.querySelectorAll('script');
210
+ const jsFiles = new Set();
211
+
212
+ scripts.forEach(script => {
213
+ // 外部 JS
214
+ if (script.src) {
215
+ jsFiles.add(script.src);
216
+ }
217
+
218
+ // 内联 JS 中的动态加载
219
+ if (script.textContent) {
220
+ const matches = script.textContent.match(/['"`](https?:\\/\\/[^'"`]+\\.js[^'"`]*)['"`]/g);
221
+ if (matches) {
222
+ matches.forEach(m => jsFiles.add(m.replace(/['"`]/g, '')));
223
+ }
224
+ }
225
+ });
226
+
227
+ // 从 window 对象获取
228
+ if (window.webpackChunk) {
229
+ // Webpack chunk 加载逻辑
230
+ }
231
+
232
+ return Array.from(jsFiles);
233
+ }
234
+ """)
235
+
236
+ # 转换为绝对 URL
237
+ absolute_js = set()
238
+ for js in js_files:
239
+ if js.startswith('//'):
240
+ js = 'https:' + js
241
+ elif js.startswith('/'):
242
+ js = self.target + js
243
+ elif not js.startswith('http'):
244
+ js = urljoin(self.target, js)
245
+
246
+ if '.js' in js:
247
+ absolute_js.add(js)
248
+
249
+ print(f" [+] 发现 {len(absolute_js)} 个 JS 文件")
250
+ return absolute_js
251
+
252
+ def _extract_routes_and_apis_from_all_js(self):
253
+ """从所有 JS 文件提取路由和 API"""
254
+ print(f"\n{'='*70}")
255
+ print(f"[+] 全量 JS 分析 ({len(self.all_js_files)} 个文件)")
256
+ print(f"{'='*70}\n")
257
+
258
+ for js_url in self.all_js_files:
259
+ if js_url in self.analyzed_js:
260
+ continue
261
+
262
+ self.analyzed_js.add(js_url)
263
+
264
+ try:
265
+ print(f" [*] 分析:{js_url[:100]}...")
266
+ response = self.session.get(js_url, timeout=10)
267
+ content = response.text
268
+
269
+ # 提取 API 端点
270
+ endpoints = self._extract_apis_from_js(content, js_url)
271
+ for endpoint in endpoints:
272
+ # 检查是否已存在
273
+ exists = any(
274
+ e.get('url', '') == endpoint.get('url', '') and
275
+ e.get('method', '') == endpoint.get('method', '')
276
+ for e in self.all_api_endpoints
277
+ )
278
+ if not exists:
279
+ self.all_api_endpoints.append(endpoint)
280
+ print(f" [API] {endpoint.get('method', 'GET')} {endpoint.get('url', '')}")
281
+
282
+ # 提取敏感信息
283
+ secrets = self._extract_secrets_from_js(content, js_url)
284
+ self.secrets.extend(secrets)
285
+
286
+ # 提取凭证
287
+ creds = self._extract_credentials_from_js(content, js_url)
288
+ self.credentials.extend(creds)
289
+
290
+ except Exception as e:
291
+ print(f" [!] 分析失败:{e}")
292
+
293
+ def _extract_apis_from_js(self, content: str, source: str) -> List[Dict]:
294
+ """从 JS 内容提取 API 端点"""
295
+ apis = []
296
+
297
+ # 全面的 API 提取正则
298
+ patterns = [
299
+ # axios
300
+ (r'axios\.(get|post|put|delete|patch)\s*\(\s*[\'"`]([^\'"`]+)[\'"`]', 'axios'),
301
+ (r'axios\s*\(\s*\{\s*method:\s*[\'"`]?(get|post|put|delete|patch)[\'"`]?', 'axios_config'),
302
+
303
+ # fetch
304
+ (r'fetch\s*\(\s*[\'"`]([^\'"`]+)[\'"`]', 'fetch'),
305
+ (r'fetch\s*\(\s*new\s+Request\s*\(\s*[\'"`]([^\'"`]+)[\'"`]', 'fetch_request'),
306
+
307
+ # XMLHttpRequest
308
+ (r'\.open\s*\(\s*[\'"`](GET|POST|PUT|DELETE|PATCH)[\'"`]\s*,\s*[\'"`]([^\'"`]+)[\'"`]', 'xhr'),
309
+
310
+ # Vue resource
311
+ (r'this\.\$http\.(get|post|put|delete)\s*\(\s*[\'"`]([^\'"`]+)[\'"`]', 'vue_http'),
312
+ (r'this\.\$axios\.(get|post|put|delete|patch)\s*\(\s*[\'"`]([^\'"`]+)[\'"`]', 'vue_axios'),
313
+
314
+ # Angular http
315
+ (r'this\.http\.(get|post|put|delete|patch)\s*\(\s*[\'"`]([^\'"`]+)[\'"`]', 'angular_http'),
316
+
317
+ # 通用路由
318
+ (r'[\'"`](/api/[^\'"`\s?#]+)[\'"`]', 'api_path'),
319
+ (r'[\'"`](/rest/[^\'"`\s?#]+)[\'"`]', 'rest_path'),
320
+ (r'[\'"`](/service/[^\'"`\s?#]+)[\'"`]', 'service_path'),
321
+ (r'[\'"`](/do/[^\'"`\s?#]+)[\'"`]', 'do_path'),
322
+ (r'[\'"`](/action/[^\'"`\s?#]+)[\'"`]', 'action_path'),
323
+
324
+ # 路由配置
325
+ (r'path:\s*[\'"`]([^\'"`]+)[\'"`]', 'vue_router'),
326
+ (r'component:\s*.*?import\s*\(\s*[\'"`]([^\'"`]+)[\'"`]', 'lazy_route'),
327
+
328
+ # 完整 URL
329
+ (r'(https?://[^\'"`\s]+/api/[^\'"`\s]+)', 'full_url'),
330
+ ]
331
+
332
+ for pattern, pattern_type in patterns:
333
+ matches = re.findall(pattern, content, re.IGNORECASE)
334
+ for match in matches:
335
+ if isinstance(match, tuple):
336
+ method = match[0].upper() if match[0].lower() in ['get', 'post', 'put', 'delete', 'patch'] else 'GET'
337
+ url = match[1] if len(match) > 1 else match[0]
338
+ else:
339
+ method = 'GET'
340
+ url = match
341
+
342
+ # 清理 URL
343
+ url = url.replace('${', '{').replace('}', '').replace('${', '')
344
+
345
+ # 转换为绝对 URL
346
+ if url.startswith('/'):
347
+ url = self.target + url
348
+ elif not url.startswith('http'):
349
+ url = urljoin(self.target, url)
350
+
351
+ # 提取参数
352
+ params = self._extract_params_from_url(url)
353
+
354
+ apis.append({
355
+ 'url': url,
356
+ 'method': method,
357
+ 'source': source,
358
+ 'pattern_type': pattern_type,
359
+ 'params': params,
360
+ 'discovered_by': 'js_analysis'
361
+ })
362
+
363
+ return apis
364
+
365
+ def _extract_secrets_from_js(self, content: str, source: str) -> List[Dict]:
366
+ """从 JS 提取敏感信息"""
367
+ secrets = []
368
+
369
+ patterns = {
370
+ 'api_key': [
371
+ r'(?:api[_-]?key|apikey)\s*[=:]\s*[\'"`]([^\'"`]{8,})[\'"`]',
372
+ r'API_KEY\s*[=:]\s*[\'"`]([^\'"`]+)[\'"`]',
373
+ r'appKey\s*[=:]\s*[\'"`]([^\'"`]+)[\'"`]',
374
+ ],
375
+ 'token': [
376
+ r'(?:token|auth[_-]?token|access[_-]?token)\s*[=:]\s*[\'"`]([^\'"`]{8,})[\'"`]',
377
+ r'TOKEN\s*[=:]\s*[\'"`]([^\'"`]+)[\'"`]',
378
+ r'Bearer\s+[\'"`]([^\'"`]+)[\'"`]',
379
+ ],
380
+ 'password': [
381
+ r'(?:password|passwd|pwd)\s*[=:]\s*[\'"`]([^\'"`]+)[\'"`]',
382
+ r'PASSWORD\s*[=:]\s*[\'"`]([^\'"`]+)[\'"`]',
383
+ ],
384
+ 'secret': [
385
+ r'(?:secret|secret[_-]?key)\s*[=:]\s*[\'"`]([^\'"`]{8,})[\'"`]',
386
+ r'SECRET\s*[=:]\s*[\'"`]([^\'"`]+)[\'"`]',
387
+ ],
388
+ 'aws': [
389
+ r'(?:AKIA|ABIA|ACCA)[A-Z0-9]{16}',
390
+ r'aws[_-]?access[_-]?key\s*[=:]\s*[\'"`]([^\'"`]+)[\'"`]',
391
+ ],
392
+ 'database': [
393
+ r'(?:mongodb|mysql|postgresql|redis)://[^\s\'"`]+',
394
+ r'DB_CONNECTION\s*[=:]\s*[\'"`]([^\'"`]+)[\'"`]',
395
+ ],
396
+ 'private_key': [
397
+ r'-----BEGIN (?:RSA |EC )?PRIVATE KEY-----',
398
+ r'private[_-]?key\s*[=:]\s*[\'"`]([^\'"`]+)[\'"`]',
399
+ ],
400
+ }
401
+
402
+ for secret_type, pattern_list in patterns.items():
403
+ for pattern in pattern_list:
404
+ matches = re.findall(pattern, content, re.IGNORECASE)
405
+ for match in matches:
406
+ secrets.append({
407
+ 'type': secret_type,
408
+ 'value': match[:100] + '...' if len(match) > 100 else match,
409
+ 'source': source,
410
+ 'severity': self._get_secret_severity(secret_type)
411
+ })
412
+ print(f" [!] 敏感信息 [{secret_type}]: {match[:30]}...")
413
+
414
+ return secrets
415
+
416
+ def _extract_credentials_from_js(self, content: str, source: str) -> List[Dict]:
417
+ """提取登录凭证"""
418
+ credentials = []
419
+
420
+ # 查找登录相关代码
421
+ login_patterns = [
422
+ r'username\s*[=:]\s*[\'"`]([^\'"`]+)[\'"`]',
423
+ r'password\s*[=:]\s*[\'"`]([^\'"`]+)[\'"`]',
424
+ r'account\s*[=:]\s*[\'"`]([^\'"`]+)[\'"`]',
425
+ r'credentials\s*[=:]\s*\{[^}]*username\s*[=:]\s*[\'"`]([^\'"`]+)[\'"`][^}]*password\s*[=:]\s*[\'"`]([^\'"`]+)[\'"`]',
426
+ ]
427
+
428
+ for pattern in login_patterns:
429
+ matches = re.findall(pattern, content, re.IGNORECASE)
430
+ for match in matches:
431
+ if isinstance(match, tuple):
432
+ credentials.append({
433
+ 'username': match[0],
434
+ 'password': match[1] if len(match) > 1 else match[0],
435
+ 'source': source
436
+ })
437
+ else:
438
+ credentials.append({
439
+ 'username': match,
440
+ 'password': '',
441
+ 'source': source
442
+ })
443
+
444
+ return credentials
445
+
446
+ def _analyze_response_body(self, url: str, body: str):
447
+ """分析响应体,提取敏感信息"""
448
+ # 检查 JSON 响应
449
+ if 'application/json' in str(self.session.headers.get('Content-Type', '')):
450
+ try:
451
+ data = json.loads(body)
452
+ self._check_json_for_secrets(url, data)
453
+ except:
454
+ pass
455
+
456
+ # 检查敏感数据模式
457
+ sensitive_patterns = {
458
+ 'email': r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
459
+ 'phone': r'\b1[3-9]\d{9}\b',
460
+ 'id_card': r'\b[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]\b',
461
+ 'credit_card': r'\b(?:\d{4}[- ]?){3}\d{4}\b',
462
+ }
463
+
464
+ for data_type, pattern in sensitive_patterns.items():
465
+ if re.search(pattern, body):
466
+ self.vulnerabilities.append({
467
+ 'type': 'Sensitive Data Exposure',
468
+ 'severity': 'MEDIUM',
469
+ 'endpoint': url,
470
+ 'data_type': data_type,
471
+ 'evidence': f'Found {data_type} in response'
472
+ })
473
+
474
+ def _check_json_for_secrets(self, url: str, data: Dict):
475
+ """检查 JSON 中的敏感数据"""
476
+ sensitive_keys = ['password', 'token', 'secret', 'api_key', 'private_key', 'credential']
477
+
478
+ def check_dict(d: Dict, path: str = ''):
479
+ for key, value in d.items():
480
+ current_path = f"{path}.{key}" if path else key
481
+
482
+ if any(sensitive in key.lower() for sensitive in sensitive_keys):
483
+ self.secrets.append({
484
+ 'type': 'json_sensitive_data',
485
+ 'key': current_path,
486
+ 'value': str(value)[:50] + '...' if len(str(value)) > 50 else str(value),
487
+ 'source': url
488
+ })
489
+
490
+ if isinstance(value, dict):
491
+ check_dict(value, current_path)
492
+
493
+ check_dict(data)
494
+
495
+ def _extract_params(self, url: str, method: str, post_data: str = None) -> Dict:
496
+ """从请求提取参数"""
497
+ params = {}
498
+
499
+ # URL 参数
500
+ parsed = urlparse(url)
501
+ url_params = parse_qs(parsed.query)
502
+ if url_params:
503
+ params['query'] = url_params
504
+
505
+ # POST 数据
506
+ if post_data:
507
+ try:
508
+ if post_data.startswith('{'):
509
+ params['body'] = json.loads(post_data)
510
+ else:
511
+ params['body'] = parse_qs(post_data)
512
+ except:
513
+ params['body_raw'] = post_data
514
+
515
+ return params
516
+
517
+ def _extract_params_from_url(self, url: str) -> List[str]:
518
+ """从 URL 提取参数占位符"""
519
+ # 提取 {param} 或 :param 形式的参数
520
+ patterns = [
521
+ r'\{([^}]+)\}',
522
+ r':([a-zA-Z_][a-zA-Z0-9_]*)'
523
+ ]
524
+
525
+ params = []
526
+ for pattern in patterns:
527
+ matches = re.findall(pattern, url)
528
+ params.extend(matches)
529
+
530
+ return list(set(params))
531
+
532
+ def _is_api_request(self, url: str) -> bool:
533
+ """判断是否是 API 请求"""
534
+ api_indicators = [
535
+ '/api/', '/api/v', '/rest/', '/graphql', '/graph',
536
+ '/service/', '/do/', '/action/', '/controller/',
537
+ '.json', '.action', '.do', '.api',
538
+ 'controller', 'service', 'api', 'rest'
539
+ ]
540
+ return any(indicator in url.lower() for indicator in api_indicators)
541
+
542
+ def _get_secret_severity(self, secret_type: str) -> str:
543
+ """获取敏感信息严重程度"""
544
+ severity_map = {
545
+ 'private_key': 'CRITICAL',
546
+ 'aws': 'CRITICAL',
547
+ 'database': 'CRITICAL',
548
+ 'password': 'HIGH',
549
+ 'token': 'HIGH',
550
+ 'secret': 'HIGH',
551
+ 'api_key': 'MEDIUM',
552
+ }
553
+ return severity_map.get(secret_type, 'MEDIUM')
554
+
555
+ def scan_vulnerabilities(self):
556
+ """漏洞扫描"""
557
+ print(f"\n{'='*70}")
558
+ print(f"[+] 漏洞扫描")
559
+ print(f"{'='*70}\n")
560
+
561
+ # 去重端点
562
+ unique_endpoints = defaultdict(set)
563
+ for endpoint in self.all_api_endpoints:
564
+ parsed = urlparse(endpoint['url'])
565
+ unique_endpoints[parsed.path].add(endpoint.get('method', 'GET'))
566
+
567
+ # SQL 注入
568
+ print(f"[*] SQL 注入测试 ({len(unique_endpoints)} 个端点)...")
569
+ self._test_sqli(unique_endpoints)
570
+
571
+ # XSS
572
+ print(f"[*] XSS 测试...")
573
+ self._test_xss(unique_endpoints)
574
+
575
+ # 未授权访问
576
+ print(f"[*] 未授权访问测试...")
577
+ self._test_unauthorized_access(unique_endpoints)
578
+
579
+ # 敏感数据
580
+ print(f"[*] 敏感数据泄露测试...")
581
+ self._test_data_exposure()
582
+
583
+ # 硬编码凭证
584
+ if self.credentials:
585
+ print(f"[*] 测试硬编码凭证...")
586
+ self._test_hardcoded_credentials()
587
+
588
+ def _test_sqli(self, endpoints: Dict[str, Set[str]]):
589
+ """SQL 注入测试"""
590
+ sqli_payloads = [
591
+ "' OR '1'='1",
592
+ "' OR 1=1--",
593
+ "admin'--",
594
+ "' UNION SELECT NULL--",
595
+ "1; DROP TABLE users--"
596
+ ]
597
+
598
+ sqli_errors = [
599
+ 'SQL syntax', 'mysql_fetch', 'ORA-', 'PostgreSQL',
600
+ 'SQLite', 'ODBC', 'jdbc', 'hibernate', 'sqlserver'
601
+ ]
602
+
603
+ for path, methods in endpoints.items():
604
+ for method in methods:
605
+ for payload in sqli_payloads:
606
+ try:
607
+ test_url = f"{self.target}{path}"
608
+ params = {'id': payload, 'search': payload, 'user': payload}
609
+
610
+ if method == 'GET':
611
+ response = self.session.get(test_url, params=params, timeout=10)
612
+ else:
613
+ response = self.session.post(test_url, data=params, timeout=10)
614
+
615
+ for error in sqli_errors:
616
+ if error.lower() in response.text.lower():
617
+ self.vulnerabilities.append({
618
+ 'type': 'SQL Injection',
619
+ 'severity': 'CRITICAL',
620
+ 'endpoint': path,
621
+ 'method': method,
622
+ 'payload': payload,
623
+ 'evidence': error
624
+ })
625
+ print(f" [!] SQL 注入:{path}")
626
+ break
627
+
628
+ except:
629
+ pass
630
+
631
+ def _test_xss(self, endpoints: Dict[str, Set[str]]):
632
+ """XSS 测试"""
633
+ xss_payloads = [
634
+ '<script>alert(1)</script>',
635
+ '<img src=x onerror=alert(1)>',
636
+ '<svg onload=alert(1)>'
637
+ ]
638
+
639
+ for path, methods in endpoints.items():
640
+ for payload in xss_payloads:
641
+ try:
642
+ test_url = f"{self.target}{path}"
643
+ params = {'q': payload, 'search': payload, 'name': payload}
644
+
645
+ response = self.session.get(test_url, params=params, timeout=10)
646
+
647
+ if payload in response.text:
648
+ self.vulnerabilities.append({
649
+ 'type': 'XSS (Reflected)',
650
+ 'severity': 'HIGH',
651
+ 'endpoint': path,
652
+ 'payload': payload,
653
+ 'evidence': 'Payload reflected'
654
+ })
655
+ print(f" [!] XSS: {path}")
656
+
657
+ except:
658
+ pass
659
+
660
+ def _test_unauthorized_access(self, endpoints: Dict[str, Set[str]]):
661
+ """未授权访问测试"""
662
+ sensitive_paths = ['/admin', '/api/user', '/api/config', '/api/admin', '/manage']
663
+
664
+ for path in sensitive_paths:
665
+ if path in endpoints:
666
+ try:
667
+ test_url = f"{self.target}{path}"
668
+ response = self.session.get(test_url, timeout=10)
669
+
670
+ if response.status_code == 200:
671
+ self.vulnerabilities.append({
672
+ 'type': 'Unauthorized Access',
673
+ 'severity': 'HIGH',
674
+ 'endpoint': path,
675
+ 'evidence': f'Status: {response.status_code}'
676
+ })
677
+ print(f" [!] 未授权访问:{path}")
678
+
679
+ except:
680
+ pass
681
+
682
+ def _test_data_exposure(self):
683
+ """敏感数据暴露测试"""
684
+ for secret in self.secrets:
685
+ if secret.get('severity') in ['CRITICAL', 'HIGH']:
686
+ self.vulnerabilities.append({
687
+ 'type': 'Sensitive Data Exposure',
688
+ 'severity': secret.get('severity', 'MEDIUM'),
689
+ 'endpoint': secret.get('source', 'Unknown'),
690
+ 'data_type': secret.get('type', 'unknown'),
691
+ 'evidence': secret.get('value', '')[:100]
692
+ })
693
+
694
+ def _test_hardcoded_credentials(self):
695
+ """测试硬编码凭证"""
696
+ for cred in self.credentials:
697
+ username = cred.get('username', '')
698
+ password = cred.get('password', '')
699
+
700
+ if username and password:
701
+ # 尝试登录
702
+ try:
703
+ login_url = f"{self.target}/login"
704
+ response = self.session.post(login_url, data={
705
+ 'username': username,
706
+ 'password': password
707
+ }, timeout=10)
708
+
709
+ if response.status_code == 200 and 'token' in response.text.lower():
710
+ self.vulnerabilities.append({
711
+ 'type': 'Hardcoded Credentials',
712
+ 'severity': 'CRITICAL',
713
+ 'endpoint': '/login',
714
+ 'username': username,
715
+ 'password': password,
716
+ 'evidence': 'Credentials work'
717
+ })
718
+ print(f" [!] 硬编码凭证有效:{username}:{password}")
719
+
720
+ except:
721
+ pass
722
+
723
+ def generate_report(self, output_file: str = 'deep_test_report.md'):
724
+ """生成详细报告"""
725
+ report = f"""# 深度 API 渗透测试报告 v3.5
726
+
727
+ ## 执行摘要
728
+ - **测试目标**: {self.target}
729
+ - **测试时间**: {time.strftime('%Y-%m-%d %H:%M:%S')}
730
+ - **测试工具**: Deep API Tester v3.5
731
+ - **测试深度**: {self.max_depth} 层
732
+
733
+ ## 发现统计
734
+ | 类型 | 数量 |
735
+ |------|------|
736
+ | JS 文件 | {len(self.all_js_files)} |
737
+ | API 端点 | {len(self.all_api_endpoints)} |
738
+ | 捕获流量 | {len(self.all_traffic)} |
739
+ | 敏感信息 | {len(self.secrets)} |
740
+ | 登录凭证 | {len(self.credentials)} |
741
+ | 漏洞数量 | {len(self.vulnerabilities)} |
742
+
743
+ ## JS 文件列表
744
+ """
745
+
746
+ # JS 文件
747
+ for js in sorted(self.all_js_files):
748
+ report += f"- `{js}`\n"
749
+
750
+ # API 端点
751
+ report += f"\n## API 端点列表\n"
752
+ endpoints_grouped = defaultdict(list)
753
+ for endpoint in self.all_api_endpoints:
754
+ parsed = urlparse(endpoint['url'])
755
+ endpoints_grouped[parsed.path].append(endpoint.get('method', 'GET'))
756
+
757
+ for path, methods in sorted(endpoints_grouped.items()):
758
+ report += f"- `{', '.join(set(methods))} {path}`\n"
759
+
760
+ # 敏感信息
761
+ if self.secrets:
762
+ report += f"\n## 敏感信息\n"
763
+ for secret in self.secrets:
764
+ report += f"- **[{secret.get('severity', 'MEDIUM')}]** {secret.get('type', 'unknown')}: `{secret.get('value', '')[:50]}...`\n"
765
+ report += f" - 来源:{secret.get('source', 'Unknown')}\n"
766
+
767
+ # 登录凭证
768
+ if self.credentials:
769
+ report += f"\n## 登录凭证\n"
770
+ for cred in self.credentials:
771
+ report += f"- Username: `{cred.get('username', '')}`\n"
772
+ report += f" - Password: `{cred.get('password', '')}`\n"
773
+ report += f" - 来源:{cred.get('source', 'Unknown')}\n"
774
+
775
+ # 漏洞详情
776
+ report += f"\n## 漏洞详情\n"
777
+ if self.vulnerabilities:
778
+ for vuln in self.vulnerabilities:
779
+ report += f"""
780
+ ### {vuln['type']}
781
+ - **严重程度**: {vuln['severity']}
782
+ - **端点**: {vuln.get('endpoint', 'N/A')}
783
+ - **方法**: {vuln.get('method', 'N/A')}
784
+ - **证据**: {vuln.get('evidence', 'N/A')[:200]}
785
+ """
786
+ else:
787
+ report += "\n未发现明显漏洞。\n"
788
+
789
+ # 保存报告
790
+ with open(output_file, 'w', encoding='utf-8') as f:
791
+ f.write(report)
792
+
793
+ print(f"\n[+] 报告已保存:{output_file}")
794
+ return report
795
+
796
+ def run_full_test(self, output_file: str = 'deep_test_report.md'):
797
+ """执行完整测试"""
798
+ print(f"\n{'='*70}")
799
+ print(f"深度 API 渗透测试 v3.5")
800
+ print(f"目标:{self.target}")
801
+ print(f"{'='*70}\n")
802
+
803
+ # 1. 浏览器爬取
804
+ crawl_result = self.crawl_with_browser()
805
+
806
+ # 2. 漏洞扫描
807
+ self.scan_vulnerabilities()
808
+
809
+ # 3. 生成报告
810
+ self.generate_report(output_file)
811
+
812
+ print(f"\n{'='*70}")
813
+ print(f"测试完成!")
814
+ print(f"JS 文件:{crawl_result['js_files']}")
815
+ print(f"API 端点:{crawl_result['api_endpoints']}")
816
+ print(f"捕获流量:{crawl_result['traffic']}")
817
+ print(f"发现漏洞:{len(self.vulnerabilities)}")
818
+ print(f"{'='*70}\n")
819
+
820
+ return {
821
+ 'js_files': crawl_result['js_files'],
822
+ 'api_endpoints': crawl_result['api_endpoints'],
823
+ 'traffic': crawl_result['traffic'],
824
+ 'secrets': len(self.secrets),
825
+ 'credentials': len(self.credentials),
826
+ 'vulnerabilities': len(self.vulnerabilities)
827
+ }
828
+
829
+
830
+ # CLI 入口
831
+ if __name__ == '__main__':
832
+ import sys
833
+
834
+ if len(sys.argv) < 2:
835
+ print("Usage: python deep_api_tester_v35.py <target_url> [output_file] [max_depth]")
836
+ print("Example: python deep_api_tester_v35.py http://example.com report.md 3")
837
+ sys.exit(1)
838
+
839
+ target = sys.argv[1]
840
+ output = sys.argv[2] if len(sys.argv) > 2 else 'deep_test_report.md'
841
+ max_depth = int(sys.argv[3]) if len(sys.argv) > 3 else 3
842
+
843
+ tester = DeepAPITester(target, max_depth=max_depth)
844
+ tester.run_full_test(output)