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,844 +0,0 @@
|
|
|
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)
|