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.
- package/agents/api-cyber-supervisor.md +9 -3
- package/agents/api-probing-miner.md +10 -2
- package/agents/api-resource-specialist.md +44 -35
- package/agents/api-vuln-verifier.md +56 -24
- package/package.json +1 -1
- package/postinstall.mjs +1 -0
- package/preuninstall.mjs +43 -32
- package/src/index.ts +3 -100
- package/README.md +0 -74
- package/SKILL.md +0 -1797
- package/core/advanced_recon.py +0 -788
- package/core/agentic_analyzer.py +0 -445
- package/core/analyzers/api_parser.py +0 -210
- package/core/analyzers/response_analyzer.py +0 -212
- package/core/analyzers/sensitive_finder.py +0 -184
- package/core/api_fuzzer.py +0 -422
- package/core/api_interceptor.py +0 -525
- package/core/api_parser.py +0 -955
- package/core/browser_tester.py +0 -479
- package/core/cloud_storage_tester.py +0 -1330
- package/core/collectors/__init__.py +0 -23
- package/core/collectors/api_path_finder.py +0 -300
- package/core/collectors/browser_collect.py +0 -645
- package/core/collectors/browser_collector.py +0 -411
- package/core/collectors/http_client.py +0 -111
- package/core/collectors/js_collector.py +0 -490
- package/core/collectors/js_parser.py +0 -780
- package/core/collectors/url_collector.py +0 -319
- package/core/context_manager.py +0 -682
- package/core/deep_api_tester_v35.py +0 -844
- package/core/deep_api_tester_v55.py +0 -366
- package/core/dynamic_api_analyzer.py +0 -532
- package/core/http_client.py +0 -179
- package/core/models.py +0 -296
- package/core/orchestrator.py +0 -890
- package/core/prerequisite.py +0 -227
- package/core/reasoning_engine.py +0 -1042
- package/core/response_classifier.py +0 -606
- package/core/runner.py +0 -938
- package/core/scan_engine.py +0 -599
- package/core/skill_executor.py +0 -435
- package/core/skill_executor_v2.py +0 -670
- package/core/skill_executor_v3.py +0 -704
- package/core/smart_analyzer.py +0 -687
- package/core/strategy_pool.py +0 -707
- package/core/testers/auth_tester.py +0 -264
- package/core/testers/idor_tester.py +0 -200
- package/core/testers/sqli_tester.py +0 -211
- package/core/testing_loop.py +0 -655
- package/core/utils/base_path_dict.py +0 -255
- package/core/utils/payload_lib.py +0 -167
- package/core/utils/ssrf_detector.py +0 -220
- package/core/verifiers/vuln_verifier.py +0 -536
- package/references/README.md +0 -72
- package/references/asset-discovery.md +0 -119
- package/references/fuzzing-patterns.md +0 -129
- package/references/graphql-guidance.md +0 -108
- package/references/intake.md +0 -84
- package/references/pua-agent.md +0 -192
- package/references/report-template.md +0 -156
- package/references/rest-guidance.md +0 -76
- package/references/severity-model.md +0 -76
- package/references/test-matrix.md +0 -86
- package/references/validation.md +0 -78
- package/references/vulnerabilities/01-sqli-tests.md +0 -1128
- package/references/vulnerabilities/02-user-enum-tests.md +0 -423
- package/references/vulnerabilities/03-jwt-tests.md +0 -499
- package/references/vulnerabilities/04-idor-tests.md +0 -362
- package/references/vulnerabilities/05-sensitive-data-tests.md +0 -466
- package/references/vulnerabilities/06-biz-logic-tests.md +0 -501
- package/references/vulnerabilities/07-security-config-tests.md +0 -511
- package/references/vulnerabilities/08-brute-force-tests.md +0 -457
- package/references/vulnerabilities/09-vulnerability-chains.md +0 -465
- package/references/vulnerabilities/10-auth-tests.md +0 -537
- package/references/vulnerabilities/11-graphql-tests.md +0 -355
- package/references/vulnerabilities/12-ssrf-tests.md +0 -396
- package/references/vulnerabilities/README.md +0 -148
- package/references/workflows.md +0 -192
- package/src/hooks/directory-agents-injector.ts +0 -106
|
@@ -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)
|