opencode-api-security-testing 2.0.0 → 2.1.1
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/README.md +30 -24
- package/SKILL.md +1797 -0
- package/core/advanced_recon.py +788 -0
- package/core/agentic_analyzer.py +445 -0
- package/core/analyzers/api_parser.py +210 -0
- package/core/analyzers/response_analyzer.py +212 -0
- package/core/analyzers/sensitive_finder.py +184 -0
- package/core/api_fuzzer.py +422 -0
- package/core/api_interceptor.py +525 -0
- package/core/api_parser.py +955 -0
- package/core/browser_tester.py +479 -0
- package/core/cloud_storage_tester.py +1330 -0
- package/core/collectors/__init__.py +23 -0
- package/core/collectors/api_path_finder.py +300 -0
- package/core/collectors/browser_collect.py +645 -0
- package/core/collectors/browser_collector.py +411 -0
- package/core/collectors/http_client.py +111 -0
- package/core/collectors/js_collector.py +490 -0
- package/core/collectors/js_parser.py +780 -0
- package/core/collectors/url_collector.py +319 -0
- package/core/context_manager.py +682 -0
- package/core/deep_api_tester_v35.py +844 -0
- package/core/deep_api_tester_v55.py +366 -0
- package/core/dynamic_api_analyzer.py +532 -0
- package/core/http_client.py +179 -0
- package/core/models.py +296 -0
- package/core/orchestrator.py +890 -0
- package/core/prerequisite.py +227 -0
- package/core/reasoning_engine.py +1042 -0
- package/core/response_classifier.py +606 -0
- package/core/runner.py +938 -0
- package/core/scan_engine.py +599 -0
- package/core/skill_executor.py +435 -0
- package/core/skill_executor_v2.py +670 -0
- package/core/skill_executor_v3.py +704 -0
- package/core/smart_analyzer.py +687 -0
- package/core/strategy_pool.py +707 -0
- package/core/testers/auth_tester.py +264 -0
- package/core/testers/idor_tester.py +200 -0
- package/core/testers/sqli_tester.py +211 -0
- package/core/testing_loop.py +655 -0
- package/core/utils/base_path_dict.py +255 -0
- package/core/utils/payload_lib.py +167 -0
- package/core/utils/ssrf_detector.py +220 -0
- package/core/verifiers/vuln_verifier.py +536 -0
- package/package.json +17 -13
- package/references/asset-discovery.md +119 -612
- package/references/graphql-guidance.md +65 -641
- package/references/intake.md +84 -0
- package/references/report-template.md +131 -38
- package/references/rest-guidance.md +55 -526
- package/references/severity-model.md +52 -264
- package/references/test-matrix.md +65 -263
- package/references/validation.md +53 -400
- package/scripts/postinstall.js +46 -0
- package/src/index.ts +259 -275
- package/agents/cyber-supervisor.md +0 -55
- package/agents/probing-miner.md +0 -42
- package/agents/resource-specialist.md +0 -31
- package/commands/api-security-testing-scan.md +0 -59
- package/commands/api-security-testing-test.md +0 -49
- package/commands/api-security-testing.md +0 -72
- package/tsconfig.json +0 -17
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Smart API Analyzer - 智能 API 分析器
|
|
4
|
+
参考 ApiRed + 渗透测试工程师思维
|
|
5
|
+
|
|
6
|
+
核心功能:
|
|
7
|
+
1. 从 JS 中精确提取后端 API (过滤前端路由)
|
|
8
|
+
2. 识别参数定义和类型
|
|
9
|
+
3. 智能分类 API 端点
|
|
10
|
+
4. 敏感信息挖掘
|
|
11
|
+
5. 基于理解生成精准 fuzzing 目标
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
import hashlib
|
|
16
|
+
from typing import Dict, List, Set, Tuple, Optional, Any
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from urllib.parse import urljoin, urlparse, parse_qs
|
|
19
|
+
from enum import Enum
|
|
20
|
+
import requests
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class EndpointType(Enum):
|
|
24
|
+
"""端点类型"""
|
|
25
|
+
BACKEND_API = "backend_api" # 后端 API (有价值)
|
|
26
|
+
FRONTEND_ROUTE = "frontend_route" # 前端路由 (无价值)
|
|
27
|
+
STATIC_RESOURCE = "static" # 静态资源
|
|
28
|
+
UNKNOWN = "unknown"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Sensitivity(Enum):
|
|
32
|
+
"""敏感等级"""
|
|
33
|
+
CRITICAL = "critical"
|
|
34
|
+
HIGH = "high"
|
|
35
|
+
MEDIUM = "medium"
|
|
36
|
+
LOW = "low"
|
|
37
|
+
INFO = "info"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class ParsedParameter:
|
|
42
|
+
"""解析出的参数"""
|
|
43
|
+
name: str
|
|
44
|
+
param_type: str # path, query, body, json
|
|
45
|
+
data_type: str # int, string, bool, object
|
|
46
|
+
required: bool = False
|
|
47
|
+
is_sensitive: bool = False
|
|
48
|
+
source: str = "" # from_response, from_js, from猜测
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class SmartEndpoint:
|
|
53
|
+
"""智能端点"""
|
|
54
|
+
path: str
|
|
55
|
+
method: str
|
|
56
|
+
endpoint_type: EndpointType = EndpointType.UNKNOWN
|
|
57
|
+
sensitivity: Sensitivity = Sensitivity.INFO
|
|
58
|
+
|
|
59
|
+
# 解析出的信息
|
|
60
|
+
base_url: str = ""
|
|
61
|
+
parameters: List[ParsedParameter] = field(default_factory=list)
|
|
62
|
+
request_body: Dict = field(default_factory=dict)
|
|
63
|
+
response_pattern: str = ""
|
|
64
|
+
|
|
65
|
+
# 来源
|
|
66
|
+
source_file: str = ""
|
|
67
|
+
source_line: int = 0
|
|
68
|
+
confidence: float = 0.0
|
|
69
|
+
|
|
70
|
+
# 分析结果
|
|
71
|
+
is_auth_endpoint: bool = False
|
|
72
|
+
is_admin_endpoint: bool = False
|
|
73
|
+
is_user_data_endpoint: bool = False
|
|
74
|
+
has_sensitive_params: bool = False
|
|
75
|
+
|
|
76
|
+
def to_dict(self) -> Dict:
|
|
77
|
+
return {
|
|
78
|
+
'path': self.path,
|
|
79
|
+
'method': self.method,
|
|
80
|
+
'type': self.endpoint_type.value,
|
|
81
|
+
'sensitivity': self.sensitivity.value,
|
|
82
|
+
'base_url': self.base_url,
|
|
83
|
+
'parameters': [{'name': p.name, 'type': p.param_type, 'sensitive': p.is_sensitive} for p in self.parameters],
|
|
84
|
+
'is_auth': self.is_auth_endpoint,
|
|
85
|
+
'is_admin': self.is_admin_endpoint,
|
|
86
|
+
'confidence': self.confidence,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class SensitiveMatch:
|
|
92
|
+
"""敏感信息匹配"""
|
|
93
|
+
data_type: str
|
|
94
|
+
value: str
|
|
95
|
+
severity: Sensitivity
|
|
96
|
+
context: str
|
|
97
|
+
source: str
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class SmartAPIAnalyzer:
|
|
101
|
+
"""
|
|
102
|
+
智能 API 分析器
|
|
103
|
+
|
|
104
|
+
核心思维:像渗透测试工程师一样思考
|
|
105
|
+
1. 先理解目标 - 识别技术栈和 API 结构
|
|
106
|
+
2. 精确提取 - 区分后端 API 和前端路由
|
|
107
|
+
3. 发现参数 - 从响应和代码中提取参数定义
|
|
108
|
+
4. 智能分类 - 按功能和安全影响分类
|
|
109
|
+
5. 精准 Fuzz - 基于理解生成目标
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
# 后端 API 特征
|
|
113
|
+
BACKEND_API_PATTERNS = [
|
|
114
|
+
r'/api/', r'/v\d+/', r'/rest/', r'/graphql',
|
|
115
|
+
r'/auth/', r'/login', r'/logout', r'/token',
|
|
116
|
+
r'/admin/', r'/manage/',
|
|
117
|
+
r'/user/', r'/profile/', r'/account/',
|
|
118
|
+
r'/data/', r'/file/', r'/upload/', r'/download/',
|
|
119
|
+
r'/config/', r'/setting/', r'/sys/',
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
# 前端路由特征 (需过滤)
|
|
123
|
+
FRONTEND_ROUTE_PATTERNS = [
|
|
124
|
+
r'chunk-[a-f0-9]+\.js',
|
|
125
|
+
r'\.css', r'\.svg', r'\.png', r'\.jpg',
|
|
126
|
+
r'/js/', r'/css/', r'/img/', r'/assets/',
|
|
127
|
+
r'/dist/', r'/build/',
|
|
128
|
+
r'/node_modules/',
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
# 敏感关键字
|
|
132
|
+
SENSITIVE_KEYWORDS = {
|
|
133
|
+
'password': Sensitivity.HIGH,
|
|
134
|
+
'passwd': Sensitivity.HIGH,
|
|
135
|
+
'secret': Sensitivity.HIGH,
|
|
136
|
+
'token': Sensitivity.MEDIUM,
|
|
137
|
+
'jwt': Sensitivity.MEDIUM,
|
|
138
|
+
'bearer': Sensitivity.MEDIUM,
|
|
139
|
+
'api_key': Sensitivity.HIGH,
|
|
140
|
+
'apikey': Sensitivity.HIGH,
|
|
141
|
+
'access_key': Sensitivity.HIGH,
|
|
142
|
+
'private_key': Sensitivity.CRITICAL,
|
|
143
|
+
'credential': Sensitivity.HIGH,
|
|
144
|
+
'auth': Sensitivity.MEDIUM,
|
|
145
|
+
'session': Sensitivity.MEDIUM,
|
|
146
|
+
'cookie': Sensitivity.MEDIUM,
|
|
147
|
+
'userid': Sensitivity.LOW,
|
|
148
|
+
'user_id': Sensitivity.LOW,
|
|
149
|
+
'email': Sensitivity.MEDIUM,
|
|
150
|
+
'phone': Sensitivity.MEDIUM,
|
|
151
|
+
'mobile': Sensitivity.MEDIUM,
|
|
152
|
+
'idcard': Sensitivity.CRITICAL,
|
|
153
|
+
'ssn': Sensitivity.CRITICAL,
|
|
154
|
+
'credit': Sensitivity.CRITICAL,
|
|
155
|
+
'bank': Sensitivity.CRITICAL,
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
# 认证相关端点
|
|
159
|
+
AUTH_PATTERNS = [
|
|
160
|
+
r'/auth/', r'/login', r'/logout', r'/register', r'/signup',
|
|
161
|
+
r'/token', r'/refresh', r'/verify', r'/oauth', r'/sso',
|
|
162
|
+
r'/session', r'/cas/', r'/saml/',
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
# 管理后台端点
|
|
166
|
+
ADMIN_PATTERNS = [
|
|
167
|
+
r'/admin/', r'/manage/', r'/control/', r'/dashboard',
|
|
168
|
+
r'/system/', r'/config/', r'/setting/', r'/master',
|
|
169
|
+
r'/root/', r'/super/', r'/privilege',
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
# 用户数据端点
|
|
173
|
+
USER_DATA_PATTERNS = [
|
|
174
|
+
r'/user/', r'/profile/', r'/account/', r'/person/',
|
|
175
|
+
r'/employee/', r'/member/', r'/customer/',
|
|
176
|
+
r'/order/', r'/product/', r'/transaction/',
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
# 参数名模式 (用于从响应中提取)
|
|
180
|
+
PARAM_PATTERNS = [
|
|
181
|
+
r'["\']?(\w+)["\']?\s*:\s*(?:null|undefined|"[^"]*"|\'[^\']*\'|\d+)',
|
|
182
|
+
r'(?:id|userId|user_id|page|token|key|type|category|name|status)\s*[=:]\s*["\']?[^"\'\s,}]+',
|
|
183
|
+
]
|
|
184
|
+
|
|
185
|
+
# 响应参数提取模式
|
|
186
|
+
RESPONSE_PARAM_PATTERNS = [
|
|
187
|
+
r'"(\w+)":\s*(?:\{|\[|"[^"]*"|\d+|true|false|null)',
|
|
188
|
+
r'"(id|code|msg|data|list|count|total|pages?)"\s*:',
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
def __init__(self, session: requests.Session = None):
|
|
192
|
+
self.session = session or requests.Session()
|
|
193
|
+
self.endpoints: List[SmartEndpoint] = []
|
|
194
|
+
self.sensitive_data: List[SensitiveMatch] = []
|
|
195
|
+
self.base_urls: Set[str] = set()
|
|
196
|
+
self.js_content_cache: Dict[str, str] = {}
|
|
197
|
+
|
|
198
|
+
def analyze_js_file(self, js_url: str, content: str) -> List[SmartEndpoint]:
|
|
199
|
+
"""
|
|
200
|
+
分析 JS 文件,提取智能端点
|
|
201
|
+
|
|
202
|
+
核心算法:
|
|
203
|
+
1. 提取所有字符串字面量
|
|
204
|
+
2. 识别 HTTP 调用模式 (fetch, axios, etc.)
|
|
205
|
+
3. 识别 Base URL 配置
|
|
206
|
+
4. 区分后端 API 和前端路由
|
|
207
|
+
5. 提取参数信息
|
|
208
|
+
"""
|
|
209
|
+
endpoints = []
|
|
210
|
+
|
|
211
|
+
base_url = self._extract_base_url(content, js_url)
|
|
212
|
+
if base_url:
|
|
213
|
+
self.base_urls.add(base_url)
|
|
214
|
+
|
|
215
|
+
# 1. 提取 fetch 调用
|
|
216
|
+
fetch_endpoints = self._extract_fetch_patterns(content, base_url)
|
|
217
|
+
endpoints.extend(fetch_endpoints)
|
|
218
|
+
|
|
219
|
+
# 2. 提取 axios 调用
|
|
220
|
+
axios_endpoints = self._extract_axios_patterns(content, base_url)
|
|
221
|
+
endpoints.extend(axios_endpoints)
|
|
222
|
+
|
|
223
|
+
# 3. 提取 $.ajax 调用
|
|
224
|
+
ajax_endpoints = self._extract_jquery_ajax_patterns(content, base_url)
|
|
225
|
+
endpoints.extend(ajax_endpoints)
|
|
226
|
+
|
|
227
|
+
# 4. 提取配置对象中的 URL
|
|
228
|
+
config_endpoints = self._extract_config_urls(content, base_url)
|
|
229
|
+
endpoints.extend(config_endpoints)
|
|
230
|
+
|
|
231
|
+
# 5. 提取常量定义
|
|
232
|
+
constant_endpoints = self._extract_constant_urls(content, base_url)
|
|
233
|
+
endpoints.extend(constant_endpoints)
|
|
234
|
+
|
|
235
|
+
# 去重
|
|
236
|
+
seen = set()
|
|
237
|
+
unique_endpoints = []
|
|
238
|
+
for ep in endpoints:
|
|
239
|
+
key = f"{ep.method}:{ep.path}"
|
|
240
|
+
if key not in seen:
|
|
241
|
+
seen.add(key)
|
|
242
|
+
unique_endpoints.append(ep)
|
|
243
|
+
|
|
244
|
+
return unique_endpoints
|
|
245
|
+
|
|
246
|
+
def _extract_base_url(self, content: str, js_url: str) -> Optional[str]:
|
|
247
|
+
"""提取 Base URL"""
|
|
248
|
+
patterns = [
|
|
249
|
+
r'(?:baseURL|base_url|apiUrl|api_url|API_URL)\s*[:=]\s*["\']([^"\']+)["\']',
|
|
250
|
+
r'(?:BASE_URL|API_URL)\s*[:=]\s*["\']([^"\']+)["\']',
|
|
251
|
+
r'process\.env\.([A-Z_]+)\s*[:=]\s*["\']([^"\']+)["\']',
|
|
252
|
+
]
|
|
253
|
+
|
|
254
|
+
for pattern in patterns:
|
|
255
|
+
matches = re.findall(pattern, content)
|
|
256
|
+
for match in matches:
|
|
257
|
+
if isinstance(match, tuple):
|
|
258
|
+
value = match[-1]
|
|
259
|
+
else:
|
|
260
|
+
value = match
|
|
261
|
+
|
|
262
|
+
if value.startswith('http'):
|
|
263
|
+
return value.rstrip('/')
|
|
264
|
+
|
|
265
|
+
parsed = urlparse(js_url)
|
|
266
|
+
return f"{parsed.scheme}://{parsed.netloc}"
|
|
267
|
+
|
|
268
|
+
def _extract_fetch_patterns(self, content: str, base_url: str) -> List[SmartEndpoint]:
|
|
269
|
+
"""提取 fetch 调用"""
|
|
270
|
+
endpoints = []
|
|
271
|
+
|
|
272
|
+
patterns = [
|
|
273
|
+
(r"fetch\s*\(\s*['\"]([^'\"]+)['\"]", "GET"),
|
|
274
|
+
(r"fetch\s*\(\s*[`'\"]([^`'\"]+)[`'\"]", "GET"),
|
|
275
|
+
(r"fetch\s*\(\s*['\"]([^'\"]+)['\"]\s*,\s*\{", None),
|
|
276
|
+
]
|
|
277
|
+
|
|
278
|
+
for pattern, default_method in patterns:
|
|
279
|
+
matches = re.finditer(pattern, content)
|
|
280
|
+
for match in matches:
|
|
281
|
+
path = match.group(1) if match.groups() else match.group(0)
|
|
282
|
+
|
|
283
|
+
if not self._is_valid_api_path(path):
|
|
284
|
+
continue
|
|
285
|
+
|
|
286
|
+
method = default_method
|
|
287
|
+
if method is None and match.group(0):
|
|
288
|
+
method_match = re.search(r"method:\s*['\"](\w+)['\"]", match.group(0))
|
|
289
|
+
method = method_match.group(1) if method_match else "GET"
|
|
290
|
+
|
|
291
|
+
endpoint = self._create_smart_endpoint(path, method, base_url, "fetch", content)
|
|
292
|
+
endpoints.append(endpoint)
|
|
293
|
+
|
|
294
|
+
return endpoints
|
|
295
|
+
|
|
296
|
+
def _extract_axios_patterns(self, content: str, base_url: str) -> List[SmartEndpoint]:
|
|
297
|
+
"""提取 axios 调用"""
|
|
298
|
+
endpoints = []
|
|
299
|
+
|
|
300
|
+
patterns = [
|
|
301
|
+
r"axios\.(get|post|put|delete|patch|head|options)\s*\(\s*['\"]([^'\"]+)['\"]",
|
|
302
|
+
r"axios\.(get|post|put|delete|patch)\s*\(\s*[`'\"]([^`'\"]+)[`'\"]",
|
|
303
|
+
r"axios\s*.\s*(?:request|post|get)\s*\(\s*\{[^}]*url\s*:\s*['\"]([^'\"]+)['\"]",
|
|
304
|
+
]
|
|
305
|
+
|
|
306
|
+
for pattern in patterns:
|
|
307
|
+
matches = re.finditer(pattern, content)
|
|
308
|
+
for match in matches:
|
|
309
|
+
groups = match.groups()
|
|
310
|
+
if len(groups) >= 2:
|
|
311
|
+
method = groups[0].upper()
|
|
312
|
+
path = groups[1]
|
|
313
|
+
|
|
314
|
+
if not self._is_valid_api_path(path):
|
|
315
|
+
continue
|
|
316
|
+
|
|
317
|
+
endpoint = self._create_smart_endpoint(path, method, base_url, "axios", content)
|
|
318
|
+
endpoints.append(endpoint)
|
|
319
|
+
|
|
320
|
+
return endpoints
|
|
321
|
+
|
|
322
|
+
def _extract_jquery_ajax_patterns(self, content: str, base_url: str) -> List[SmartEndpoint]:
|
|
323
|
+
"""提取 $.ajax 调用"""
|
|
324
|
+
endpoints = []
|
|
325
|
+
|
|
326
|
+
pattern = r"\$\.ajax\s*\(\s*\{([^}]+)\}"
|
|
327
|
+
matches = re.finditer(pattern, content)
|
|
328
|
+
|
|
329
|
+
for match in matches:
|
|
330
|
+
config = match.group(0)
|
|
331
|
+
|
|
332
|
+
url_match = re.search(r"url\s*:\s*['\"]([^'\"]+)['\"]", config)
|
|
333
|
+
method_match = re.search(r"type\s*:\s*['\"](\w+)['\"]", config)
|
|
334
|
+
|
|
335
|
+
if url_match:
|
|
336
|
+
path = url_match.group(1)
|
|
337
|
+
method = method_match.group(1).upper() if method_match else "GET"
|
|
338
|
+
|
|
339
|
+
if not self._is_valid_api_path(path):
|
|
340
|
+
continue
|
|
341
|
+
|
|
342
|
+
endpoint = self._create_smart_endpoint(path, method, base_url, "jquery", content)
|
|
343
|
+
endpoints.append(endpoint)
|
|
344
|
+
|
|
345
|
+
return endpoints
|
|
346
|
+
|
|
347
|
+
def _extract_config_urls(self, content: str, base_url: str) -> List[SmartEndpoint]:
|
|
348
|
+
"""提取配置对象中的 URL"""
|
|
349
|
+
endpoints = []
|
|
350
|
+
|
|
351
|
+
patterns = [
|
|
352
|
+
r'(?:api|API|endpoint|ENDPOINT)\s*:\s*["\']([/a-zA-Z0-9_-]+)["\']',
|
|
353
|
+
r'(?:url|URL|path|PATH)\s*:\s*["\']([/a-zA-Z0-9_-]+)["\']',
|
|
354
|
+
]
|
|
355
|
+
|
|
356
|
+
for pattern in patterns:
|
|
357
|
+
matches = re.findall(pattern, content)
|
|
358
|
+
for path in matches:
|
|
359
|
+
if not self._is_valid_api_path(path):
|
|
360
|
+
continue
|
|
361
|
+
|
|
362
|
+
endpoint = self._create_smart_endpoint(path, "GET", base_url, "config", content)
|
|
363
|
+
endpoints.append(endpoint)
|
|
364
|
+
|
|
365
|
+
return endpoints
|
|
366
|
+
|
|
367
|
+
def _extract_constant_urls(self, content: str, base_url: str) -> List[SmartEndpoint]:
|
|
368
|
+
"""提取常量定义的 URL"""
|
|
369
|
+
endpoints = []
|
|
370
|
+
|
|
371
|
+
patterns = [
|
|
372
|
+
r'const\s+\w*[Uu]rl\w*\s*=\s*["\']([^"\']+)["\']',
|
|
373
|
+
r'(?:API|PATH|ROUTE)_(?:PATH|URL|ENDPOINT)\s*=\s*["\']([^"\']+)["\']',
|
|
374
|
+
]
|
|
375
|
+
|
|
376
|
+
for pattern in patterns:
|
|
377
|
+
matches = re.findall(pattern, content)
|
|
378
|
+
for path in matches:
|
|
379
|
+
if not self._is_valid_api_path(path):
|
|
380
|
+
continue
|
|
381
|
+
|
|
382
|
+
endpoint = self._create_smart_endpoint(path, "GET", base_url, "constant", content)
|
|
383
|
+
endpoints.append(endpoint)
|
|
384
|
+
|
|
385
|
+
return endpoints
|
|
386
|
+
|
|
387
|
+
def _is_valid_api_path(self, path: str) -> bool:
|
|
388
|
+
"""验证是否为有效的 API 路径"""
|
|
389
|
+
if not path or len(path) < 2:
|
|
390
|
+
return False
|
|
391
|
+
|
|
392
|
+
if path.startswith('//') or path.startswith('http'):
|
|
393
|
+
return True
|
|
394
|
+
|
|
395
|
+
if path.startswith('/'):
|
|
396
|
+
path = path[1:]
|
|
397
|
+
|
|
398
|
+
skip_patterns = [
|
|
399
|
+
r'\.js$', r'\.css$', r'\.svg$', r'\.png$', r'\.jpg$',
|
|
400
|
+
r'chunk-', r'module', r'dist', r'build',
|
|
401
|
+
r'node_modules', r'assets', r'images', r'icons',
|
|
402
|
+
]
|
|
403
|
+
|
|
404
|
+
for pattern in skip_patterns:
|
|
405
|
+
if re.search(pattern, path, re.IGNORECASE):
|
|
406
|
+
return False
|
|
407
|
+
|
|
408
|
+
return True
|
|
409
|
+
|
|
410
|
+
def _create_smart_endpoint(self, path: str, method: str, base_url: str, source: str, content: str) -> SmartEndpoint:
|
|
411
|
+
"""创建智能端点"""
|
|
412
|
+
endpoint = SmartEndpoint(
|
|
413
|
+
path=path,
|
|
414
|
+
method=method.upper(),
|
|
415
|
+
base_url=base_url,
|
|
416
|
+
source_file=source,
|
|
417
|
+
confidence=0.8 if source in ['fetch', 'axios', 'jquery'] else 0.5
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
path_lower = path.lower()
|
|
421
|
+
|
|
422
|
+
if any(re.search(p, path_lower) for p in self.AUTH_PATTERNS):
|
|
423
|
+
endpoint.is_auth_endpoint = True
|
|
424
|
+
endpoint.sensitivity = Sensitivity.HIGH
|
|
425
|
+
endpoint.endpoint_type = EndpointType.BACKEND_API
|
|
426
|
+
|
|
427
|
+
if any(re.search(p, path_lower) for p in self.ADMIN_PATTERNS):
|
|
428
|
+
endpoint.is_admin_endpoint = True
|
|
429
|
+
endpoint.sensitivity = Sensitivity.HIGH
|
|
430
|
+
endpoint.endpoint_type = EndpointType.BACKEND_API
|
|
431
|
+
|
|
432
|
+
if any(re.search(p, path_lower) for p in self.USER_DATA_PATTERNS):
|
|
433
|
+
endpoint.is_user_data_endpoint = True
|
|
434
|
+
endpoint.sensitivity = Sensitivity.MEDIUM
|
|
435
|
+
endpoint.endpoint_type = EndpointType.BACKEND_API
|
|
436
|
+
|
|
437
|
+
if endpoint.endpoint_type == EndpointType.UNKNOWN:
|
|
438
|
+
if any(re.search(p, path_lower) for p in self.BACKEND_API_PATTERNS):
|
|
439
|
+
endpoint.endpoint_type = EndpointType.BACKEND_API
|
|
440
|
+
endpoint.sensitivity = Sensitivity.INFO
|
|
441
|
+
|
|
442
|
+
endpoint.parameters = self._extract_parameters_from_content(path, content)
|
|
443
|
+
|
|
444
|
+
return endpoint
|
|
445
|
+
|
|
446
|
+
def _extract_parameters_from_content(self, path: str, content: str) -> List[ParsedParameter]:
|
|
447
|
+
"""从 JS 内容中提取参数"""
|
|
448
|
+
params = []
|
|
449
|
+
|
|
450
|
+
path_parts = path.strip('/').split('/')
|
|
451
|
+
for i, part in enumerate(path_parts):
|
|
452
|
+
if re.match(r'^\{[\w_]+\}$', part):
|
|
453
|
+
param_name = part[1:-1]
|
|
454
|
+
params.append(ParsedParameter(
|
|
455
|
+
name=param_name,
|
|
456
|
+
param_type='path',
|
|
457
|
+
data_type='string',
|
|
458
|
+
source='from_path_template'
|
|
459
|
+
))
|
|
460
|
+
elif re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', part) and part not in ['api', 'v1', 'v2', 'rest']:
|
|
461
|
+
if i > 0:
|
|
462
|
+
params.append(ParsedParameter(
|
|
463
|
+
name=part,
|
|
464
|
+
param_type='path',
|
|
465
|
+
data_type='string',
|
|
466
|
+
source='from_path_guess'
|
|
467
|
+
))
|
|
468
|
+
|
|
469
|
+
param_names = re.findall(r'(?:param|params|parameter|query|data|body|payload)\s*[:=]\s*\{[^}]*\}', content)
|
|
470
|
+
for param in param_names[:5]:
|
|
471
|
+
name_match = re.search(r'["\']?(\w+)["\']?\s*:', param)
|
|
472
|
+
if name_match:
|
|
473
|
+
params.append(ParsedParameter(
|
|
474
|
+
name=name_match.group(1),
|
|
475
|
+
param_type='body',
|
|
476
|
+
data_type='object',
|
|
477
|
+
source='from_code'
|
|
478
|
+
))
|
|
479
|
+
|
|
480
|
+
return params
|
|
481
|
+
|
|
482
|
+
def analyze_response(self, endpoint: SmartEndpoint, response_text: str) -> List[ParsedParameter]:
|
|
483
|
+
"""从响应中提取参数"""
|
|
484
|
+
params = []
|
|
485
|
+
|
|
486
|
+
matches = re.findall(r'"(\w+)":\s*(?:\{|\[|"[^"]*"|\d+|true|false|null)', response_text)
|
|
487
|
+
for match in matches:
|
|
488
|
+
if match not in ['id', 'code', 'msg', 'data', 'list', 'total', 'page', 'pages', 'size']:
|
|
489
|
+
params.append(ParsedParameter(
|
|
490
|
+
name=match,
|
|
491
|
+
param_type='response',
|
|
492
|
+
data_type='mixed',
|
|
493
|
+
source='from_response'
|
|
494
|
+
))
|
|
495
|
+
|
|
496
|
+
return params
|
|
497
|
+
|
|
498
|
+
def extract_sensitive_data(self, content: str, source: str = "") -> List[SensitiveMatch]:
|
|
499
|
+
"""提取敏感信息"""
|
|
500
|
+
matches = []
|
|
501
|
+
|
|
502
|
+
patterns = {
|
|
503
|
+
'AWS Access Key': (r'AKIA[0-9A-Z]{16}', Sensitivity.CRITICAL),
|
|
504
|
+
'AWS Secret Key': (r'[A-Za-z0-9/+=]{40}', Sensitivity.CRITICAL),
|
|
505
|
+
'JWT Token': (r'eyJ[A-Za-z0-9_-]*\.eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*', Sensitivity.HIGH),
|
|
506
|
+
'Bearer Token': (r'Bearer\s+[A-Za-z0-9_-]+', Sensitivity.HIGH),
|
|
507
|
+
'Basic Auth': (r'Basic\s+[A-Za-z0-9_-]+', Sensitivity.MEDIUM),
|
|
508
|
+
'API Key': (r'[a-zA-Z0-9]{32,}', Sensitivity.MEDIUM),
|
|
509
|
+
'Private Key': (r'-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----', Sensitivity.CRITICAL),
|
|
510
|
+
'Password': (r'(?:password|passwd|pwd)\s*[:=]\s*["\'][^"\']{4,}["\']', Sensitivity.HIGH),
|
|
511
|
+
'Email': (r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', Sensitivity.LOW),
|
|
512
|
+
'Phone': (r'1[3-9]\d{9}', Sensitivity.LOW),
|
|
513
|
+
'ID Card': (r'\d{17}[\dXx]', Sensitivity.CRITICAL),
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
for name, (pattern, severity) in patterns.items():
|
|
517
|
+
regex_matches = re.finditer(pattern, content, re.IGNORECASE)
|
|
518
|
+
for match in regex_matches:
|
|
519
|
+
value = match.group(0)
|
|
520
|
+
if len(value) > 5 and len(value) < 500:
|
|
521
|
+
start = max(0, match.start() - 20)
|
|
522
|
+
end = min(len(content), match.end() + 20)
|
|
523
|
+
context = content[start:end]
|
|
524
|
+
|
|
525
|
+
matches.append(SensitiveMatch(
|
|
526
|
+
data_type=name,
|
|
527
|
+
value=value[:50],
|
|
528
|
+
severity=severity,
|
|
529
|
+
context=context,
|
|
530
|
+
source=source
|
|
531
|
+
))
|
|
532
|
+
|
|
533
|
+
return matches
|
|
534
|
+
|
|
535
|
+
def classify_endpoints(self) -> Dict[str, List[SmartEndpoint]]:
|
|
536
|
+
"""分类端点"""
|
|
537
|
+
classified = {
|
|
538
|
+
'auth': [],
|
|
539
|
+
'admin': [],
|
|
540
|
+
'user_data': [],
|
|
541
|
+
'api': [],
|
|
542
|
+
'other': [],
|
|
543
|
+
'frontend': []
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
for ep in self.endpoints:
|
|
547
|
+
if ep.is_auth_endpoint:
|
|
548
|
+
classified['auth'].append(ep)
|
|
549
|
+
elif ep.is_admin_endpoint:
|
|
550
|
+
classified['admin'].append(ep)
|
|
551
|
+
elif ep.is_user_data_endpoint:
|
|
552
|
+
classified['user_data'].append(ep)
|
|
553
|
+
elif ep.endpoint_type == EndpointType.BACKEND_API:
|
|
554
|
+
classified['api'].append(ep)
|
|
555
|
+
elif ep.endpoint_type == EndpointType.FRONTEND_ROUTE:
|
|
556
|
+
classified['frontend'].append(ep)
|
|
557
|
+
else:
|
|
558
|
+
classified['other'].append(ep)
|
|
559
|
+
|
|
560
|
+
return classified
|
|
561
|
+
|
|
562
|
+
def generate_fuzz_targets(self) -> List[Tuple[str, str, Dict]]:
|
|
563
|
+
"""
|
|
564
|
+
生成精准 fuzzing 目标
|
|
565
|
+
|
|
566
|
+
返回: [(url, method, fuzz_params), ...]
|
|
567
|
+
"""
|
|
568
|
+
targets = []
|
|
569
|
+
|
|
570
|
+
classified = self.classify_endpoints()
|
|
571
|
+
|
|
572
|
+
priority_order = ['auth', 'admin', 'user_data', 'api']
|
|
573
|
+
|
|
574
|
+
for category in priority_order:
|
|
575
|
+
for ep in classified[category]:
|
|
576
|
+
full_url = ep.base_url + ep.path if ep.path.startswith('/') else ep.path
|
|
577
|
+
|
|
578
|
+
params = {}
|
|
579
|
+
for param in ep.parameters:
|
|
580
|
+
if param.param_type == 'path':
|
|
581
|
+
params[param.name] = self._get_fuzz_value(param.name)
|
|
582
|
+
elif param.param_type == 'query':
|
|
583
|
+
params[param.name] = self._get_fuzz_value(param.name)
|
|
584
|
+
|
|
585
|
+
targets.append((full_url, ep.method, params))
|
|
586
|
+
|
|
587
|
+
return targets
|
|
588
|
+
|
|
589
|
+
def _get_fuzz_value(self, param_name: str) -> str:
|
|
590
|
+
"""根据参数名生成 fuzz 值"""
|
|
591
|
+
fuzz_db = {
|
|
592
|
+
'id': ["1", "0", "999999", "1 OR 1=1", "${jndi}"],
|
|
593
|
+
'userId': ["1", "0", "admin", "1' OR '1'='1"],
|
|
594
|
+
'page': ["1", "0", "999"],
|
|
595
|
+
'pageSize': ["10", "100", "9999"],
|
|
596
|
+
'search': ["' OR '1'='1", "<script>alert(1)</script>", "${jndi:ldap://}"],
|
|
597
|
+
'q': ["' OR '1'='1", "<script>alert(1)</script>"],
|
|
598
|
+
'query': ["' OR '1'='1", "<script>alert(1)</script>"],
|
|
599
|
+
'type': ["1", "admin", "test"],
|
|
600
|
+
'name': ["admin", "test", "' OR '1'='1"],
|
|
601
|
+
'email': ["admin@test.com", "' OR '1'='1"],
|
|
602
|
+
'password': ["admin", "123456", "' OR '1'='1"],
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return fuzz_db.get(param_name.lower(), ["test", "1", "admin"])
|
|
606
|
+
|
|
607
|
+
def get_high_value_targets(self) -> List[SmartEndpoint]:
|
|
608
|
+
"""获取高价值目标"""
|
|
609
|
+
return [ep for ep in self.endpoints
|
|
610
|
+
if ep.sensitivity in [Sensitivity.CRITICAL, Sensitivity.HIGH]
|
|
611
|
+
or ep.is_auth_endpoint or ep.is_admin_endpoint]
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def smart_analyze(target_url: str, session: requests.Session = None) -> Dict:
|
|
615
|
+
"""
|
|
616
|
+
智能分析主函数
|
|
617
|
+
|
|
618
|
+
像渗透测试工程师一样:
|
|
619
|
+
1. 收集 JS 文件
|
|
620
|
+
2. 智能提取 API 端点
|
|
621
|
+
3. 分类和评估
|
|
622
|
+
4. 提取敏感信息
|
|
623
|
+
5. 生成精准 fuzzing 目标
|
|
624
|
+
"""
|
|
625
|
+
session = session or requests.Session()
|
|
626
|
+
analyzer = SmartAPIAnalyzer(session)
|
|
627
|
+
|
|
628
|
+
print("[*] Fetching target...")
|
|
629
|
+
resp = session.get(target_url, timeout=10)
|
|
630
|
+
html = resp.text
|
|
631
|
+
|
|
632
|
+
js_url_pattern = r'<script[^>]+src=["\']([^"\']+\.js[^"\']*)["\']'
|
|
633
|
+
js_urls = re.findall(js_url_pattern, html)
|
|
634
|
+
|
|
635
|
+
print(f"[*] Found {len(js_urls)} JS files")
|
|
636
|
+
|
|
637
|
+
all_endpoints = []
|
|
638
|
+
|
|
639
|
+
for js_url in js_urls[:5]:
|
|
640
|
+
if not js_url.startswith('http'):
|
|
641
|
+
js_url = urljoin(target_url, js_url)
|
|
642
|
+
|
|
643
|
+
print(f"[*] Analyzing: {js_url.split('/')[-1]}")
|
|
644
|
+
|
|
645
|
+
try:
|
|
646
|
+
js_resp = session.get(js_url, timeout=10)
|
|
647
|
+
content = js_resp.text
|
|
648
|
+
|
|
649
|
+
endpoints = analyzer.analyze_js_file(js_url, content)
|
|
650
|
+
all_endpoints.extend(endpoints)
|
|
651
|
+
|
|
652
|
+
sensitive = analyzer.extract_sensitive_data(content, js_url)
|
|
653
|
+
analyzer.sensitive_data.extend(sensitive)
|
|
654
|
+
|
|
655
|
+
except Exception as e:
|
|
656
|
+
print(f"[!] Error analyzing {js_url}: {e}")
|
|
657
|
+
|
|
658
|
+
analyzer.endpoints = all_endpoints
|
|
659
|
+
|
|
660
|
+
classified = analyzer.classify_endpoints()
|
|
661
|
+
high_value = analyzer.get_high_value_targets()
|
|
662
|
+
fuzz_targets = analyzer.generate_fuzz_targets()
|
|
663
|
+
|
|
664
|
+
print(f"\n[*] Analysis Results:")
|
|
665
|
+
print(f" Total endpoints: {len(all_endpoints)}")
|
|
666
|
+
print(f" Auth endpoints: {len(classified['auth'])}")
|
|
667
|
+
print(f" Admin endpoints: {len(classified['admin'])}")
|
|
668
|
+
print(f" User data endpoints: {len(classified['user_data'])}")
|
|
669
|
+
print(f" API endpoints: {len(classified['api'])}")
|
|
670
|
+
print(f" High value targets: {len(high_value)}")
|
|
671
|
+
print(f" Fuzz targets: {len(fuzz_targets)}")
|
|
672
|
+
print(f" Sensitive data found: {len(analyzer.sensitive_data)}")
|
|
673
|
+
|
|
674
|
+
return {
|
|
675
|
+
'endpoints': all_endpoints,
|
|
676
|
+
'classified': classified,
|
|
677
|
+
'high_value': high_value,
|
|
678
|
+
'fuzz_targets': fuzz_targets,
|
|
679
|
+
'sensitive_data': analyzer.sensitive_data,
|
|
680
|
+
'base_urls': list(analyzer.base_urls),
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
if __name__ == "__main__":
|
|
685
|
+
import sys
|
|
686
|
+
target = sys.argv[1] if len(sys.argv) > 1 else "http://49.65.100.160:6004"
|
|
687
|
+
result = smart_analyze(target)
|