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.
- package/README.md +74 -0
- 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 +1 -1
- package/references/README.md +72 -0
- package/references/asset-discovery.md +119 -0
- package/references/fuzzing-patterns.md +129 -0
- package/references/graphql-guidance.md +108 -0
- package/references/intake.md +84 -0
- package/references/pua-agent.md +192 -0
- package/references/report-template.md +156 -0
- package/references/rest-guidance.md +76 -0
- package/references/severity-model.md +76 -0
- package/references/test-matrix.md +86 -0
- package/references/validation.md +78 -0
- package/references/vulnerabilities/01-sqli-tests.md +1128 -0
- package/references/vulnerabilities/02-user-enum-tests.md +423 -0
- package/references/vulnerabilities/03-jwt-tests.md +499 -0
- package/references/vulnerabilities/04-idor-tests.md +362 -0
- package/references/vulnerabilities/05-sensitive-data-tests.md +466 -0
- package/references/vulnerabilities/06-biz-logic-tests.md +501 -0
- package/references/vulnerabilities/07-security-config-tests.md +511 -0
- package/references/vulnerabilities/08-brute-force-tests.md +457 -0
- package/references/vulnerabilities/09-vulnerability-chains.md +465 -0
- package/references/vulnerabilities/10-auth-tests.md +537 -0
- package/references/vulnerabilities/11-graphql-tests.md +355 -0
- package/references/vulnerabilities/12-ssrf-tests.md +396 -0
- package/references/vulnerabilities/README.md +148 -0
- package/references/workflows.md +192 -0
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SKILL 执行器 v2.0 - 配置驱动执行
|
|
3
|
+
|
|
4
|
+
根据 SKILL.md v2.0 定义的 execution_config:
|
|
5
|
+
1. 解析配置驱动的执行流程
|
|
6
|
+
2. 综合判断 API 存在性(静态 + 动态)
|
|
7
|
+
3. 端点合并(静态 + 动态 + Hook)
|
|
8
|
+
4. API前缀多源提取
|
|
9
|
+
5. 认证端点优先测试
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
sys.path.insert(0, '/workspace/skill-play/API-Security-Testing-Optimized')
|
|
14
|
+
|
|
15
|
+
from core.prerequisite import prerequisite_check
|
|
16
|
+
from core.api_parser import APIEndpointParser
|
|
17
|
+
from core.cloud_storage_tester import CloudStorageTester
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SKILLExecutorV2:
|
|
21
|
+
"""SKILL 执行器 v2.0 - 配置驱动"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, target: str):
|
|
24
|
+
self.target = target
|
|
25
|
+
self.session = None
|
|
26
|
+
self.playwright_available = False
|
|
27
|
+
self.browser_type = None
|
|
28
|
+
|
|
29
|
+
# 资产发现结果
|
|
30
|
+
self.parser = None
|
|
31
|
+
self.js_files = []
|
|
32
|
+
self.static_endpoints = []
|
|
33
|
+
self.dynamic_endpoints = []
|
|
34
|
+
self.hooked_endpoints = []
|
|
35
|
+
self.parent_paths = {}
|
|
36
|
+
|
|
37
|
+
# v2.0 新增:API前缀多源提取
|
|
38
|
+
self.api_prefix = None
|
|
39
|
+
self.api_prefix_sources = {}
|
|
40
|
+
|
|
41
|
+
# 测试结果
|
|
42
|
+
self.vulnerabilities = []
|
|
43
|
+
self.cloud_findings = []
|
|
44
|
+
|
|
45
|
+
# 决策状态
|
|
46
|
+
self.site_type = None
|
|
47
|
+
self.has_real_api = False
|
|
48
|
+
self.has_dynamic_endpoints = False
|
|
49
|
+
self.html_fallback_all = False
|
|
50
|
+
|
|
51
|
+
def run(self):
|
|
52
|
+
"""执行 SKILL 流程 v2.0"""
|
|
53
|
+
print("=" * 70)
|
|
54
|
+
print(" API Security Testing Skill v2.0 - 配置驱动执行")
|
|
55
|
+
print("=" * 70)
|
|
56
|
+
print(f" 目标: {self.target}")
|
|
57
|
+
print()
|
|
58
|
+
|
|
59
|
+
# ========== 阶段 0: 前置检查 ==========
|
|
60
|
+
print("[阶段 0] 前置检查")
|
|
61
|
+
print("-" * 50)
|
|
62
|
+
self._check_prerequisites()
|
|
63
|
+
|
|
64
|
+
# ========== 阶段 1: 资产发现 ==========
|
|
65
|
+
print("\n[阶段 1] 资产发现")
|
|
66
|
+
print("-" * 50)
|
|
67
|
+
|
|
68
|
+
# 1.1 静态分析
|
|
69
|
+
print("\n[1.1] 静态分析")
|
|
70
|
+
self._static_analysis()
|
|
71
|
+
|
|
72
|
+
# 1.2 站点类型检测
|
|
73
|
+
print("\n[1.2] 站点类型检测")
|
|
74
|
+
self._detect_site_type()
|
|
75
|
+
|
|
76
|
+
# 1.3 父路径探测
|
|
77
|
+
print("\n[1.3] 父路径探测")
|
|
78
|
+
self._probe_parent_paths()
|
|
79
|
+
|
|
80
|
+
# 1.4 动态分析 (条件执行)
|
|
81
|
+
print("\n[1.4] 动态分析")
|
|
82
|
+
if self.site_type in ['modern_spa', 'jquery_spa'] and self.playwright_available:
|
|
83
|
+
self._dynamic_analysis()
|
|
84
|
+
else:
|
|
85
|
+
print(" [跳过] 非SPA或Playwright不可用")
|
|
86
|
+
|
|
87
|
+
# 1.5 API Hook (条件执行)
|
|
88
|
+
print("\n[1.5] API Hook")
|
|
89
|
+
if self.playwright_available and (self.has_real_api or self.has_dynamic_endpoints):
|
|
90
|
+
self._api_hook()
|
|
91
|
+
else:
|
|
92
|
+
print(" [跳过] 无API或Playwright不可用")
|
|
93
|
+
|
|
94
|
+
# ========== 阶段 2: 漏洞分析 ==========
|
|
95
|
+
print("\n[阶段 2] 漏洞分析")
|
|
96
|
+
print("-" * 50)
|
|
97
|
+
|
|
98
|
+
# v2.0 修复:综合判断
|
|
99
|
+
self._update_decision_state()
|
|
100
|
+
|
|
101
|
+
if self.has_real_api or self.has_dynamic_endpoints:
|
|
102
|
+
self._vulnerability_testing()
|
|
103
|
+
else:
|
|
104
|
+
if self.html_fallback_all:
|
|
105
|
+
print(" [SKIP] nginx fallback,报告问题")
|
|
106
|
+
self._report_nginx_fallback()
|
|
107
|
+
else:
|
|
108
|
+
print(" [继续] 无API但非fallback,执行测试")
|
|
109
|
+
self._vulnerability_testing()
|
|
110
|
+
|
|
111
|
+
# ========== 阶段 3: 云存储测试 ==========
|
|
112
|
+
print("\n[阶段 3] 云存储测试")
|
|
113
|
+
print("-" * 50)
|
|
114
|
+
self._cloud_storage_test()
|
|
115
|
+
|
|
116
|
+
# ========== 阶段 4: 报告 ==========
|
|
117
|
+
print("\n[阶段 4] 报告")
|
|
118
|
+
print("-" * 50)
|
|
119
|
+
self._generate_report()
|
|
120
|
+
|
|
121
|
+
return self._get_result()
|
|
122
|
+
|
|
123
|
+
def _check_prerequisites(self):
|
|
124
|
+
"""前置检查"""
|
|
125
|
+
self.playwright_available, self.browser_type, can_proceed = prerequisite_check()
|
|
126
|
+
|
|
127
|
+
if can_proceed:
|
|
128
|
+
import requests
|
|
129
|
+
self.session = requests.Session()
|
|
130
|
+
self.session.headers.update({'User-Agent': 'Mozilla/5.0'})
|
|
131
|
+
print(" [OK] 前置检查通过")
|
|
132
|
+
else:
|
|
133
|
+
print(" [FAIL] 前置检查失败")
|
|
134
|
+
|
|
135
|
+
def _static_analysis(self):
|
|
136
|
+
"""静态分析"""
|
|
137
|
+
self.parser = APIEndpointParser(self.target, self.session)
|
|
138
|
+
|
|
139
|
+
self.js_files = self.parser.discover_js_files()
|
|
140
|
+
print(f" JS 文件: {len(self.js_files)}")
|
|
141
|
+
|
|
142
|
+
self.static_endpoints = self.parser.parse_js_files(self.js_files)
|
|
143
|
+
print(f" 静态端点: {len(self.static_endpoints)}")
|
|
144
|
+
|
|
145
|
+
for ep in self.static_endpoints[:5]:
|
|
146
|
+
print(f" {ep.method} {ep.path}")
|
|
147
|
+
|
|
148
|
+
def _detect_site_type(self):
|
|
149
|
+
"""判断站点类型"""
|
|
150
|
+
html = self.session.get(self.target, timeout=10).text.lower()
|
|
151
|
+
|
|
152
|
+
frontend = 'Unknown'
|
|
153
|
+
ui = 'Unknown'
|
|
154
|
+
|
|
155
|
+
if 'vue' in html:
|
|
156
|
+
frontend = 'Vue.js'
|
|
157
|
+
if 'react' in html:
|
|
158
|
+
frontend = 'React'
|
|
159
|
+
if 'angular' in html:
|
|
160
|
+
frontend = 'Angular'
|
|
161
|
+
if 'jquery' in html:
|
|
162
|
+
frontend = 'jQuery'
|
|
163
|
+
|
|
164
|
+
if 'element-ui' in html or 'element ui' in html:
|
|
165
|
+
ui = 'ElementUI'
|
|
166
|
+
if 'ant-design' in html:
|
|
167
|
+
ui = 'Ant Design'
|
|
168
|
+
|
|
169
|
+
if len(self.js_files) == 0:
|
|
170
|
+
self.site_type = 'pure_html'
|
|
171
|
+
elif frontend == 'Unknown' and len(self.js_files) > 0:
|
|
172
|
+
self.site_type = 'modern_spa'
|
|
173
|
+
frontend = 'Vue.js/React (推断)'
|
|
174
|
+
elif frontend in ['Vue.js', 'React', 'Angular']:
|
|
175
|
+
self.site_type = 'modern_spa'
|
|
176
|
+
elif frontend == 'jQuery':
|
|
177
|
+
self.site_type = 'jquery_spa'
|
|
178
|
+
else:
|
|
179
|
+
self.site_type = 'unknown'
|
|
180
|
+
|
|
181
|
+
print(f" 站点类型: {self.site_type}")
|
|
182
|
+
print(f" 前端框架: {frontend}")
|
|
183
|
+
print(f" UI 框架: {ui}")
|
|
184
|
+
|
|
185
|
+
def _probe_parent_paths(self):
|
|
186
|
+
"""父路径探测"""
|
|
187
|
+
self.parent_paths = self.parser.probe_parent_paths()
|
|
188
|
+
|
|
189
|
+
json_api_count = sum(1 for p in self.parent_paths.values() if p.get('is_api'))
|
|
190
|
+
html_fallback_count = len(self.parent_paths) - json_api_count
|
|
191
|
+
|
|
192
|
+
print(f" 父路径: {len(self.parent_paths)}")
|
|
193
|
+
print(f" JSON API: {json_api_count}")
|
|
194
|
+
print(f" HTML fallback: {html_fallback_count}")
|
|
195
|
+
|
|
196
|
+
# v2.0: 从父路径提取API前缀
|
|
197
|
+
for p in self.parent_paths.values():
|
|
198
|
+
if p.get('prefix'):
|
|
199
|
+
self.api_prefix_sources['parent_paths'] = p.get('prefix')
|
|
200
|
+
self.api_prefix = p.get('prefix')
|
|
201
|
+
print(f" [API Prefix] from parent_paths: {self.api_prefix}")
|
|
202
|
+
break
|
|
203
|
+
|
|
204
|
+
# v2.0: 判断 has_real_api
|
|
205
|
+
if json_api_count > 0:
|
|
206
|
+
self.has_real_api = True
|
|
207
|
+
print(f" [OK] 父路径发现真实 API")
|
|
208
|
+
else:
|
|
209
|
+
self.has_real_api = False
|
|
210
|
+
|
|
211
|
+
def _dynamic_analysis(self):
|
|
212
|
+
"""动态分析 v2.0"""
|
|
213
|
+
print(" [动态分析] 启动 Playwright...")
|
|
214
|
+
try:
|
|
215
|
+
from core.dynamic_api_analyzer import DynamicAPIAnalyzer
|
|
216
|
+
from urllib.parse import urlparse
|
|
217
|
+
|
|
218
|
+
analyzer = DynamicAPIAnalyzer(self.target)
|
|
219
|
+
results = analyzer.analyze_full()
|
|
220
|
+
|
|
221
|
+
count = len(results.get('endpoints', []))
|
|
222
|
+
print(f" [动态分析] 发现 {count} 个端点")
|
|
223
|
+
|
|
224
|
+
self.dynamic_endpoints = results.get('endpoints', [])
|
|
225
|
+
|
|
226
|
+
# v2.0: 从动态端点URL自动提取API前缀
|
|
227
|
+
target_host = urlparse(self.target).netloc
|
|
228
|
+
for ep in self.dynamic_endpoints:
|
|
229
|
+
ep_path = ep.get('path', '')
|
|
230
|
+
if ep_path.startswith('http'):
|
|
231
|
+
ep_host = urlparse(ep_path).netloc
|
|
232
|
+
ep_path_only = urlparse(ep_path).path
|
|
233
|
+
if ep_host == target_host and ep_path_only.startswith('/'):
|
|
234
|
+
# 提取API前缀:通常是 /xxx/xxx 格式
|
|
235
|
+
parts = ep_path_only.strip('/').split('/')
|
|
236
|
+
if len(parts) >= 2:
|
|
237
|
+
potential_prefix = '/' + '/'.join(parts[:2])
|
|
238
|
+
if potential_prefix not in ['/login', '/logout', '/static', '/js', '/css']:
|
|
239
|
+
self.api_prefix_sources['dynamic'] = potential_prefix
|
|
240
|
+
self.api_prefix = potential_prefix
|
|
241
|
+
print(f" [API Prefix] from dynamic: {self.api_prefix}")
|
|
242
|
+
break
|
|
243
|
+
|
|
244
|
+
for ep in self.dynamic_endpoints[:5]:
|
|
245
|
+
print(f" {ep.get('method', 'GET')} {ep.get('path')}")
|
|
246
|
+
|
|
247
|
+
# v2.0: 更新动态端点状态
|
|
248
|
+
if count > 0:
|
|
249
|
+
self.has_dynamic_endpoints = True
|
|
250
|
+
print(f" [OK] 动态分析发现真实 API")
|
|
251
|
+
|
|
252
|
+
except Exception as e:
|
|
253
|
+
print(f" [动态分析] 失败: {e}")
|
|
254
|
+
|
|
255
|
+
def _api_hook(self):
|
|
256
|
+
"""API Hook"""
|
|
257
|
+
print(" [API Hook] 启动 Hook...")
|
|
258
|
+
try:
|
|
259
|
+
from core.api_interceptor import APIInterceptor
|
|
260
|
+
|
|
261
|
+
interceptor = APIInterceptor(self.target)
|
|
262
|
+
results = interceptor.hook_all_apis()
|
|
263
|
+
|
|
264
|
+
count = len(results.get('endpoints', []))
|
|
265
|
+
print(f" [API Hook] 捕获 {count} 个 API 调用")
|
|
266
|
+
|
|
267
|
+
self.hooked_endpoints = results.get('endpoints', [])
|
|
268
|
+
|
|
269
|
+
except Exception as e:
|
|
270
|
+
print(f" [API Hook] 失败: {e}")
|
|
271
|
+
|
|
272
|
+
def _update_decision_state(self):
|
|
273
|
+
"""v2.0: 更新决策状态 - 综合判断"""
|
|
274
|
+
json_api_count = sum(1 for p in self.parent_paths.values() if p.get('is_api'))
|
|
275
|
+
dynamic_count = len(self.dynamic_endpoints)
|
|
276
|
+
|
|
277
|
+
print("\n[决策] 综合判断 API 存在性")
|
|
278
|
+
print(f" - 父路径 JSON API: {json_api_count}")
|
|
279
|
+
print(f" - 动态端点: {dynamic_count}")
|
|
280
|
+
|
|
281
|
+
# v2.0 核心修复:综合判断
|
|
282
|
+
# has_real_api: 任意来源有API即认为有真实API
|
|
283
|
+
self.has_real_api = json_api_count > 0 or dynamic_count > 0
|
|
284
|
+
self.has_dynamic_endpoints = dynamic_count > 0
|
|
285
|
+
|
|
286
|
+
# v2.0: 只有当父路径存在但全返回HTML,且无动态端点时才是真正的fallback
|
|
287
|
+
# 父路径探测失败但有动态端点,说明API是动态加载的,不是nginx fallback
|
|
288
|
+
|
|
289
|
+
# v2.0: html_fallback_all 判断
|
|
290
|
+
# 只有当没有任何API发现时才认为是fallback
|
|
291
|
+
self.html_fallback_all = (
|
|
292
|
+
len(self.parent_paths) > 0 and
|
|
293
|
+
json_api_count == 0 and
|
|
294
|
+
dynamic_count == 0
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
print(f" has_real_api: {self.has_real_api}")
|
|
298
|
+
print(f" has_dynamic_endpoints: {self.has_dynamic_endpoints}")
|
|
299
|
+
print(f" html_fallback_all: {self.html_fallback_all}")
|
|
300
|
+
|
|
301
|
+
def _vulnerability_testing(self):
|
|
302
|
+
"""漏洞测试 v2.0 - 集成API Fuzzer进行深度测试"""
|
|
303
|
+
|
|
304
|
+
print(" [漏洞测试] 启动深度渗透测试...")
|
|
305
|
+
|
|
306
|
+
# 导入fuzzer
|
|
307
|
+
try:
|
|
308
|
+
from core.api_fuzzer import APIfuzzer, auto_fuzz
|
|
309
|
+
fuzzer_available = True
|
|
310
|
+
except ImportError:
|
|
311
|
+
fuzzer_available = False
|
|
312
|
+
print(" [警告] API Fuzzer模块不可用,使用基础测试")
|
|
313
|
+
|
|
314
|
+
# v2.0: 合并所有端点
|
|
315
|
+
all_endpoints = self._merge_all_endpoints()
|
|
316
|
+
|
|
317
|
+
# v2.0: 按优先级排序
|
|
318
|
+
sorted_endpoints = self._sort_by_priority(all_endpoints)
|
|
319
|
+
|
|
320
|
+
print(f" [漏洞测试] 总端点: {len(sorted_endpoints)}")
|
|
321
|
+
|
|
322
|
+
# 打印认证端点详情
|
|
323
|
+
auth_count = 0
|
|
324
|
+
for ep in sorted_endpoints:
|
|
325
|
+
if self._is_auth_endpoint(ep['path']):
|
|
326
|
+
auth_count += 1
|
|
327
|
+
print(f" [漏洞测试] 认证端点: {auth_count}")
|
|
328
|
+
|
|
329
|
+
# ========== 深度Fuzzing测试 ==========
|
|
330
|
+
if fuzzer_available:
|
|
331
|
+
print("\n [深度测试] 执行API Fuzzing...")
|
|
332
|
+
self._run_deep_fuzz_test(sorted_endpoints)
|
|
333
|
+
|
|
334
|
+
# ========== 基础漏洞测试 ==========
|
|
335
|
+
print("\n [基础测试] SQL注入检测...")
|
|
336
|
+
self._test_sql_injection(sorted_endpoints)
|
|
337
|
+
|
|
338
|
+
# ========== 未授权访问测试 ==========
|
|
339
|
+
print("\n [基础测试] 未授权访问检测...")
|
|
340
|
+
self._test_unauthorized_access(sorted_endpoints)
|
|
341
|
+
|
|
342
|
+
print(f"\n 发现漏洞: {len(self.vulnerabilities)}")
|
|
343
|
+
|
|
344
|
+
# 打印漏洞摘要
|
|
345
|
+
if self.vulnerabilities:
|
|
346
|
+
print("\n [漏洞摘要]")
|
|
347
|
+
for v in self.vulnerabilities[:10]:
|
|
348
|
+
print(f" [{v['severity']}] {v['type']} - {v.get('endpoint', 'N/A')}")
|
|
349
|
+
|
|
350
|
+
def _run_deep_fuzz_test(self, endpoints):
|
|
351
|
+
"""深度Fuzz测试"""
|
|
352
|
+
from core.api_fuzzer import APIfuzzer
|
|
353
|
+
|
|
354
|
+
fuzzer = APIfuzzer(session=self.session)
|
|
355
|
+
|
|
356
|
+
# 提取路径列表
|
|
357
|
+
api_paths = []
|
|
358
|
+
for ep in endpoints:
|
|
359
|
+
path = ep['path']
|
|
360
|
+
if path.startswith('http'):
|
|
361
|
+
from urllib.parse import urlparse
|
|
362
|
+
path = urlparse(path).path
|
|
363
|
+
api_paths.append(path)
|
|
364
|
+
|
|
365
|
+
# 生成fuzz目标
|
|
366
|
+
fuzz_targets = fuzzer.generate_parent_fuzz_targets(api_paths, max_per_parent=30)
|
|
367
|
+
print(f" [Fuzz] 生成 {len(fuzz_targets)} 个测试目标")
|
|
368
|
+
|
|
369
|
+
# 执行fuzz
|
|
370
|
+
base_url = self.target.split('?')[0].rstrip('/')
|
|
371
|
+
results = fuzzer.fuzz_paths(base_url, fuzz_targets[:100], timeout=3.0)
|
|
372
|
+
|
|
373
|
+
# 分析结果
|
|
374
|
+
alive = fuzzer.get_alive_endpoints()
|
|
375
|
+
high_value = fuzzer.get_high_value_endpoints()
|
|
376
|
+
|
|
377
|
+
print(f" [Fuzz] 存活端点: {len(alive)}")
|
|
378
|
+
print(f" [Fuzz] 高价值端点: {len(high_value)}")
|
|
379
|
+
|
|
380
|
+
# 发现新端点
|
|
381
|
+
for r in high_value[:5]:
|
|
382
|
+
print(f" [发现] {r.method} {r.path} -> {r.status_code}")
|
|
383
|
+
|
|
384
|
+
def _test_sql_injection(self, endpoints):
|
|
385
|
+
"""SQL注入测试"""
|
|
386
|
+
sqli_payloads = [
|
|
387
|
+
"' OR '1'='1",
|
|
388
|
+
"' OR '1'='1' --",
|
|
389
|
+
"' OR '1'='1' #",
|
|
390
|
+
"1' OR '1'='1",
|
|
391
|
+
"' OR ''='",
|
|
392
|
+
]
|
|
393
|
+
|
|
394
|
+
test_params = ['id', 'page', 'pageNum', 'pageSize', 'userId', 'id']
|
|
395
|
+
|
|
396
|
+
for ep in endpoints[:20]:
|
|
397
|
+
path = ep['path']
|
|
398
|
+
method = ep.get('method', 'GET')
|
|
399
|
+
priority = ep.get('priority', 'low')
|
|
400
|
+
|
|
401
|
+
# 构建URL - 正确添加API前缀
|
|
402
|
+
if path.startswith('http'):
|
|
403
|
+
from urllib.parse import urlparse, parse_qs
|
|
404
|
+
parsed = urlparse(path)
|
|
405
|
+
base = f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
|
|
406
|
+
existing_params = parse_qs(parsed.query)
|
|
407
|
+
else:
|
|
408
|
+
base = self.target.split('?')[0].rstrip('/')
|
|
409
|
+
# v2.0: 如果静态端点没有前缀,但有已知的api_prefix,添加前缀
|
|
410
|
+
path_to_add = path
|
|
411
|
+
if self.api_prefix and not path.startswith('/personnelWeb'):
|
|
412
|
+
# 从 /personnelWeb/auth 提取 /personnelWeb
|
|
413
|
+
prefix_base = '/' + self.api_prefix.split('/')[1]
|
|
414
|
+
if path.startswith('/users') or path.startswith('/system') or path.startswith('/menu'):
|
|
415
|
+
path_to_add = prefix_base + path
|
|
416
|
+
print(f" [修正] {path} -> {path_to_add}")
|
|
417
|
+
base = base + path_to_add
|
|
418
|
+
existing_params = {}
|
|
419
|
+
|
|
420
|
+
# 添加测试参数
|
|
421
|
+
for param in test_params:
|
|
422
|
+
if param not in existing_params:
|
|
423
|
+
test_url = f"{base}?{param}="
|
|
424
|
+
break
|
|
425
|
+
else:
|
|
426
|
+
continue
|
|
427
|
+
|
|
428
|
+
for payload in sqli_payloads[:2]:
|
|
429
|
+
try:
|
|
430
|
+
if method == 'POST':
|
|
431
|
+
r = self.session.post(
|
|
432
|
+
base,
|
|
433
|
+
json={param: payload for param in test_params},
|
|
434
|
+
timeout=5
|
|
435
|
+
)
|
|
436
|
+
else:
|
|
437
|
+
r = self.session.get(test_url + payload, timeout=5)
|
|
438
|
+
|
|
439
|
+
ct = r.headers.get('Content-Type', '').lower()
|
|
440
|
+
|
|
441
|
+
# 检查SQL错误
|
|
442
|
+
text_lower = r.text.lower()
|
|
443
|
+
sql_patterns = [
|
|
444
|
+
'sql syntax', 'sql error', 'mysql', 'oracle',
|
|
445
|
+
'sqlite', 'sqlstate', 'postgresql', 'syntax error',
|
|
446
|
+
'microsoft sql', 'odbc', 'ora-', 'pgsql'
|
|
447
|
+
]
|
|
448
|
+
if any(p in text_lower for p in sql_patterns):
|
|
449
|
+
self.vulnerabilities.append({
|
|
450
|
+
'type': 'SQL Injection',
|
|
451
|
+
'severity': 'CRITICAL',
|
|
452
|
+
'endpoint': path,
|
|
453
|
+
'param': param,
|
|
454
|
+
'payload': payload,
|
|
455
|
+
'priority': priority,
|
|
456
|
+
'evidence': 'SQL error detected'
|
|
457
|
+
})
|
|
458
|
+
print(f" [!] SQL注入: {path} ({param}={payload})")
|
|
459
|
+
break
|
|
460
|
+
|
|
461
|
+
# 检查异常响应
|
|
462
|
+
if r.status_code == 500 and 'error' in text_lower:
|
|
463
|
+
self.vulnerabilities.append({
|
|
464
|
+
'type': 'Potential SQL Injection',
|
|
465
|
+
'severity': 'MEDIUM',
|
|
466
|
+
'endpoint': path,
|
|
467
|
+
'param': param,
|
|
468
|
+
'payload': payload,
|
|
469
|
+
'priority': priority,
|
|
470
|
+
'evidence': f'Server error {r.status_code}'
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
except:
|
|
474
|
+
pass
|
|
475
|
+
|
|
476
|
+
def _test_unauthorized_access(self, endpoints):
|
|
477
|
+
"""未授权访问测试"""
|
|
478
|
+
sensitive_patterns = [
|
|
479
|
+
'/admin', '/user/list', '/user/export', '/config',
|
|
480
|
+
'/system', '/manage', '/dashboard', '/api/users'
|
|
481
|
+
]
|
|
482
|
+
|
|
483
|
+
for ep in endpoints:
|
|
484
|
+
path = ep['path']
|
|
485
|
+
method = ep.get('method', 'GET')
|
|
486
|
+
|
|
487
|
+
# 检查敏感路径
|
|
488
|
+
if not any(p in path.lower() for p in sensitive_patterns):
|
|
489
|
+
continue
|
|
490
|
+
|
|
491
|
+
# 构建URL
|
|
492
|
+
if path.startswith('http'):
|
|
493
|
+
from urllib.parse import urlparse
|
|
494
|
+
url = f"{urlparse(path).scheme}://{urlparse(path).netloc}{urlparse(path).path}"
|
|
495
|
+
else:
|
|
496
|
+
url = self.target.split('?')[0].rstrip('/') + path
|
|
497
|
+
|
|
498
|
+
try:
|
|
499
|
+
# 不带认证信息访问
|
|
500
|
+
if method == 'POST':
|
|
501
|
+
r = self.session.post(url, json={}, timeout=5)
|
|
502
|
+
else:
|
|
503
|
+
r = self.session.get(url, timeout=5)
|
|
504
|
+
|
|
505
|
+
# 检查是否返回敏感数据(未授权访问成功)
|
|
506
|
+
if r.status_code == 200:
|
|
507
|
+
text_lower = r.text.lower()
|
|
508
|
+
if any(k in text_lower for k in ['user', 'admin', 'password', 'email', 'phone']):
|
|
509
|
+
self.vulnerabilities.append({
|
|
510
|
+
'type': 'Unauthorized Access',
|
|
511
|
+
'severity': 'HIGH',
|
|
512
|
+
'endpoint': path,
|
|
513
|
+
'method': method,
|
|
514
|
+
'evidence': f'Sensitive data exposed without auth'
|
|
515
|
+
})
|
|
516
|
+
print(f" [!] 未授权访问: {method} {path}")
|
|
517
|
+
|
|
518
|
+
# 401/403 -> 需要认证(正常)
|
|
519
|
+
# 200 + 无敏感数据 -> 可能不需要认证
|
|
520
|
+
|
|
521
|
+
except:
|
|
522
|
+
pass
|
|
523
|
+
|
|
524
|
+
def _merge_all_endpoints(self):
|
|
525
|
+
"""v2.0: 合并所有端点"""
|
|
526
|
+
all_endpoints = []
|
|
527
|
+
seen = set()
|
|
528
|
+
|
|
529
|
+
# 静态端点
|
|
530
|
+
for ep in self.static_endpoints:
|
|
531
|
+
key = f"{ep.method}:{ep.path}"
|
|
532
|
+
if key not in seen:
|
|
533
|
+
seen.add(key)
|
|
534
|
+
all_endpoints.append({
|
|
535
|
+
'path': ep.path,
|
|
536
|
+
'method': ep.method,
|
|
537
|
+
'source': 'static'
|
|
538
|
+
})
|
|
539
|
+
|
|
540
|
+
# 动态端点 (v2.0 新增)
|
|
541
|
+
for ep in self.dynamic_endpoints:
|
|
542
|
+
key = f"{ep.get('method', 'GET')}:{ep.get('path', '')}"
|
|
543
|
+
if key not in seen and ep.get('path'):
|
|
544
|
+
seen.add(key)
|
|
545
|
+
all_endpoints.append({
|
|
546
|
+
'path': ep.get('path'),
|
|
547
|
+
'method': ep.get('method', 'GET'),
|
|
548
|
+
'source': 'dynamic'
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
# Hook 端点
|
|
552
|
+
for ep in self.hooked_endpoints:
|
|
553
|
+
key = f"{ep.get('method', 'GET')}:{ep.get('path', '')}"
|
|
554
|
+
if key not in seen and ep.get('path'):
|
|
555
|
+
seen.add(key)
|
|
556
|
+
all_endpoints.append({
|
|
557
|
+
'path': ep.get('path'),
|
|
558
|
+
'method': ep.get('method', 'GET'),
|
|
559
|
+
'source': 'hooked'
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
return all_endpoints
|
|
563
|
+
|
|
564
|
+
def _is_auth_endpoint(self, path):
|
|
565
|
+
"""v2.0: 判断是否为认证端点"""
|
|
566
|
+
auth_patterns = [
|
|
567
|
+
'/auth/', '/login', '/oauth/', '/user/login',
|
|
568
|
+
'/api/user/login', '/api/auth/', '/token', '/sso'
|
|
569
|
+
]
|
|
570
|
+
path_lower = path.lower()
|
|
571
|
+
return any(p in path_lower for p in auth_patterns)
|
|
572
|
+
|
|
573
|
+
def _sort_by_priority(self, endpoints):
|
|
574
|
+
"""v2.0: 按优先级排序端点"""
|
|
575
|
+
priority_map = {'high': 0, 'medium': 1, 'low': 2}
|
|
576
|
+
|
|
577
|
+
def get_priority(ep):
|
|
578
|
+
if self._is_auth_endpoint(ep['path']):
|
|
579
|
+
return 'high'
|
|
580
|
+
if '/api/' in ep['path'] or '/prod-api/' in ep['path']:
|
|
581
|
+
return 'medium'
|
|
582
|
+
return 'low'
|
|
583
|
+
|
|
584
|
+
for ep in endpoints:
|
|
585
|
+
ep['priority'] = get_priority(ep)
|
|
586
|
+
|
|
587
|
+
return sorted(endpoints, key=lambda x: priority_map.get(x.get('priority'), 2))
|
|
588
|
+
|
|
589
|
+
def _report_nginx_fallback(self):
|
|
590
|
+
"""报告 nginx fallback 问题"""
|
|
591
|
+
self.vulnerabilities.append({
|
|
592
|
+
'type': 'Backend API Unreachable / nginx fallback',
|
|
593
|
+
'severity': 'HIGH',
|
|
594
|
+
'endpoint': 'Multiple paths',
|
|
595
|
+
'evidence': '父路径全部返回HTML,且无动态端点'
|
|
596
|
+
})
|
|
597
|
+
print(f" 添加问题: nginx fallback")
|
|
598
|
+
|
|
599
|
+
def _cloud_storage_test(self):
|
|
600
|
+
"""云存储测试"""
|
|
601
|
+
tester = CloudStorageTester(self.target)
|
|
602
|
+
tester.session = self.session
|
|
603
|
+
findings, storage_url = tester.full_test(self.target)
|
|
604
|
+
|
|
605
|
+
print(f" 云存储: {storage_url}")
|
|
606
|
+
print(f" 发现: {len(findings)}")
|
|
607
|
+
|
|
608
|
+
self.cloud_findings = findings
|
|
609
|
+
|
|
610
|
+
def _generate_report(self):
|
|
611
|
+
"""生成报告"""
|
|
612
|
+
json_api_count = sum(1 for p in self.parent_paths.values() if p.get('is_api'))
|
|
613
|
+
|
|
614
|
+
print("\n" + "=" * 50)
|
|
615
|
+
print(" 测试完成 v2.0")
|
|
616
|
+
print("=" * 50)
|
|
617
|
+
print(f" 站点类型: {self.site_type}")
|
|
618
|
+
print(f" 静态端点: {len(self.static_endpoints)}")
|
|
619
|
+
print(f" 动态端点: {len(self.dynamic_endpoints)}")
|
|
620
|
+
print(f" Hook 端点: {len(self.hooked_endpoints)}")
|
|
621
|
+
print(f" JSON API: {json_api_count}")
|
|
622
|
+
print(f" API Prefix: {self.api_prefix}")
|
|
623
|
+
print(f" 漏洞: {len(self.vulnerabilities)}")
|
|
624
|
+
print(f" 云存储: {len(self.cloud_findings)}")
|
|
625
|
+
|
|
626
|
+
if self.api_prefix_sources:
|
|
627
|
+
print(f" API Prefix来源: {self.api_prefix_sources}")
|
|
628
|
+
|
|
629
|
+
def _get_result(self):
|
|
630
|
+
"""获取结果"""
|
|
631
|
+
json_api_count = sum(1 for p in self.parent_paths.values() if p.get('is_api'))
|
|
632
|
+
|
|
633
|
+
return {
|
|
634
|
+
'target': self.target,
|
|
635
|
+
'site_type': self.site_type,
|
|
636
|
+
'endpoints': {
|
|
637
|
+
'static': len(self.static_endpoints),
|
|
638
|
+
'dynamic': len(self.dynamic_endpoints),
|
|
639
|
+
'hooked': len(self.hooked_endpoints),
|
|
640
|
+
'parent_paths': len(self.parent_paths),
|
|
641
|
+
'json_api': json_api_count,
|
|
642
|
+
'merged': len(self._merge_all_endpoints()),
|
|
643
|
+
},
|
|
644
|
+
'api_prefix': self.api_prefix,
|
|
645
|
+
'api_prefix_sources': self.api_prefix_sources,
|
|
646
|
+
'decision': {
|
|
647
|
+
'has_real_api': self.has_real_api,
|
|
648
|
+
'has_dynamic_endpoints': self.has_dynamic_endpoints,
|
|
649
|
+
'html_fallback_all': self.html_fallback_all,
|
|
650
|
+
},
|
|
651
|
+
'vulnerabilities': self.vulnerabilities,
|
|
652
|
+
'cloud_findings': self.cloud_findings,
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
if __name__ == "__main__":
|
|
657
|
+
import sys
|
|
658
|
+
|
|
659
|
+
if len(sys.argv) > 1:
|
|
660
|
+
target = sys.argv[1]
|
|
661
|
+
else:
|
|
662
|
+
print("Usage: python skill_executor_v2.py <target_url>")
|
|
663
|
+
print("Example: python skill_executor_v2.py http://example.com")
|
|
664
|
+
sys.exit(1)
|
|
665
|
+
|
|
666
|
+
executor = SKILLExecutorV2(target)
|
|
667
|
+
result = executor.run()
|
|
668
|
+
|
|
669
|
+
print("\n\n结果:")
|
|
670
|
+
print(result)
|