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
|
@@ -1,612 +1,119 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
8000, 8001, # Python
|
|
121
|
-
8888, # Jupyter
|
|
122
|
-
9000, # PHP-FPM
|
|
123
|
-
9200, # Elasticsearch
|
|
124
|
-
27017, # MongoDB
|
|
125
|
-
6379, # Redis
|
|
126
|
-
3306, # MySQL
|
|
127
|
-
5432, # PostgreSQL
|
|
128
|
-
11211, # Memcached
|
|
129
|
-
]
|
|
130
|
-
|
|
131
|
-
# 快速端口扫描
|
|
132
|
-
def quick_port_scan(host, ports=WEB_PORTS):
|
|
133
|
-
open_ports = []
|
|
134
|
-
for port in ports:
|
|
135
|
-
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
136
|
-
sock.settimeout(1)
|
|
137
|
-
result = sock.connect_ex((host, port))
|
|
138
|
-
if result == 0:
|
|
139
|
-
open_ports.append(port)
|
|
140
|
-
print(f"[+] Port {port} open")
|
|
141
|
-
sock.close()
|
|
142
|
-
return open_ports
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
### 2.3 路径爆破
|
|
146
|
-
|
|
147
|
-
```python
|
|
148
|
-
# 常见路径字典
|
|
149
|
-
PATH_WORDLIST = [
|
|
150
|
-
# 管理后台
|
|
151
|
-
"/admin", "/manage", "/management", "/administrator",
|
|
152
|
-
"/login", "/signin", "/auth", "/oauth",
|
|
153
|
-
"/dashboard", "/console", "/backend",
|
|
154
|
-
# API
|
|
155
|
-
"/api", "/api/v1", "/api/v2", "/rest",
|
|
156
|
-
"/swagger", "/swagger-ui", "/api-docs",
|
|
157
|
-
"/graphql", "/graphiql",
|
|
158
|
-
# 文件
|
|
159
|
-
"/robots.txt", "/sitemap.xml", "/crossdomain.xml",
|
|
160
|
-
"/.git/config", "/.env", "/config.php",
|
|
161
|
-
"/backup", "/bak", "/old", "/debug",
|
|
162
|
-
# 探测
|
|
163
|
-
"/server-status", "/server-info",
|
|
164
|
-
"/actuator", "/actuator/health",
|
|
165
|
-
]
|
|
166
|
-
|
|
167
|
-
def bruteforce_paths(base_url, wordlist=PATH_WORDLIST):
|
|
168
|
-
found = []
|
|
169
|
-
for path in wordlist:
|
|
170
|
-
url = base_url.rstrip('/') + path
|
|
171
|
-
try:
|
|
172
|
-
resp = requests.get(url, timeout=5, allow_redirects=False)
|
|
173
|
-
if resp.status_code in [200, 301, 302, 403]:
|
|
174
|
-
found.append((path, resp.status_code))
|
|
175
|
-
print(f"[+] {path} ({resp.status_code})")
|
|
176
|
-
except:
|
|
177
|
-
pass
|
|
178
|
-
return found
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
---
|
|
182
|
-
|
|
183
|
-
## 3. JavaScript 分析
|
|
184
|
-
|
|
185
|
-
### 3.1 JS 文件收集
|
|
186
|
-
|
|
187
|
-
```python
|
|
188
|
-
# 从 HTML 中提取所有 JS 文件
|
|
189
|
-
def collect_js_files(html_content):
|
|
190
|
-
js_files = []
|
|
191
|
-
|
|
192
|
-
# script src
|
|
193
|
-
patterns = [
|
|
194
|
-
r'<script[^>]+src=["\']([^"\']+\.js[^"\']*)["\']',
|
|
195
|
-
r'"(/_next/static/[^"]+\.js)"',
|
|
196
|
-
r'"(/static/js/[^"]+\.js)"',
|
|
197
|
-
r'"(https://[^"]+\.js)"',
|
|
198
|
-
]
|
|
199
|
-
|
|
200
|
-
for pattern in patterns:
|
|
201
|
-
matches = re.findall(pattern, html_content)
|
|
202
|
-
js_files.extend(matches)
|
|
203
|
-
|
|
204
|
-
# 去重
|
|
205
|
-
return list(set(js_files))
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
### 3.2 API 端点提取
|
|
209
|
-
|
|
210
|
-
```python
|
|
211
|
-
# 从 JS 中提取 API 端点
|
|
212
|
-
API_PATTERNS = [
|
|
213
|
-
# axios/fetch
|
|
214
|
-
r'["\'](/(?:api|auth|admin|user|login|logout)[^"\']*)["\']',
|
|
215
|
-
r'\.(?:get|post|put|delete|patch)\\(["\']([^"\']+)["\']\\)',
|
|
216
|
-
# URL patterns
|
|
217
|
-
r'["\']https?://[^"\']+(?:api|auth|admin)[^"\']*["\']',
|
|
218
|
-
# baseURL
|
|
219
|
-
r'baseURL\s*[:=]\s*["\']([^"\']+)["\']',
|
|
220
|
-
# webpack chunk
|
|
221
|
-
r'"(?:chunk-|js/)([a-f0-9]+)"',
|
|
222
|
-
]
|
|
223
|
-
|
|
224
|
-
def extract_api_from_js(js_content):
|
|
225
|
-
endpoints = set()
|
|
226
|
-
|
|
227
|
-
for pattern in API_PATTERNS:
|
|
228
|
-
matches = re.findall(pattern, js_content, re.IGNORECASE)
|
|
229
|
-
for match in matches:
|
|
230
|
-
if isinstance(match, tuple):
|
|
231
|
-
endpoints.update(match)
|
|
232
|
-
else:
|
|
233
|
-
endpoints.add(match)
|
|
234
|
-
|
|
235
|
-
return list(endpoints)
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
### 3.3 敏感信息提取
|
|
239
|
-
|
|
240
|
-
```python
|
|
241
|
-
# 敏感信息正则
|
|
242
|
-
SENSITIVE_PATTERNS = {
|
|
243
|
-
'api_key': [
|
|
244
|
-
r'(?:api[_-]?key|apikey)\s*[:=]\s*["\']([a-zA-Z0-9_-]{20,})["\']',
|
|
245
|
-
r'["\'](?:sk|pk|api)[_-][a-zA-Z0-9]{20,}["\']',
|
|
246
|
-
],
|
|
247
|
-
'aws_key': [
|
|
248
|
-
r'AKIA[0-9A-Z]{16}',
|
|
249
|
-
r'(?:aws[_-]?)?(?:access[_-]?key[_-]?id|secret[_-]?key)',
|
|
250
|
-
],
|
|
251
|
-
'jwt': [
|
|
252
|
-
r'eyJ[A-Za-z0-9-_]+\.eyJ[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+',
|
|
253
|
-
],
|
|
254
|
-
'private_key': [
|
|
255
|
-
r'-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----',
|
|
256
|
-
],
|
|
257
|
-
'ip_address': [
|
|
258
|
-
r'\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b',
|
|
259
|
-
],
|
|
260
|
-
'internal_host': [
|
|
261
|
-
r'(?:https?://)?(?:[a-z0-9-]+\.)+(?:local|internal|lan|intranet)(?::\d+)?',
|
|
262
|
-
r'http://192\.168\.\d{1,3}\.\d{1,3}(?::\d+)?',
|
|
263
|
-
r'http://10\.\d{1,3}\.\d{1,3}\.\d{1,3}(?::\d+)?',
|
|
264
|
-
],
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
def extract_sensitive_info(js_content):
|
|
268
|
-
findings = {}
|
|
269
|
-
for info_type, patterns in SENSITIVE_PATTERNS.items():
|
|
270
|
-
for pattern in patterns:
|
|
271
|
-
matches = re.findall(pattern, js_content, re.IGNORECASE)
|
|
272
|
-
if matches:
|
|
273
|
-
findings[info_type] = list(set(matches))
|
|
274
|
-
return findings
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
---
|
|
278
|
-
|
|
279
|
-
## 4. Swagger/OpenAPI 发现
|
|
280
|
-
|
|
281
|
-
### 4.1 常见 Swagger 路径
|
|
282
|
-
|
|
283
|
-
```python
|
|
284
|
-
# Swagger 路径字典
|
|
285
|
-
SWAGGER_PATHS = [
|
|
286
|
-
# Swagger UI
|
|
287
|
-
"/swagger-ui.html",
|
|
288
|
-
"/swagger-ui/index.html",
|
|
289
|
-
"/swagger-ui/swagger-ui-bundle.js",
|
|
290
|
-
"/swagger-ui-standalone-preset.js",
|
|
291
|
-
# OpenAPI
|
|
292
|
-
"/v2/api-docs",
|
|
293
|
-
"/v3/api-docs",
|
|
294
|
-
"/api-docs",
|
|
295
|
-
"/api-docs.json",
|
|
296
|
-
"/openapi.json",
|
|
297
|
-
"/openapi.yaml",
|
|
298
|
-
# Swagger 2.0
|
|
299
|
-
"/swagger.json",
|
|
300
|
-
"/swagger.yaml",
|
|
301
|
-
# 其他
|
|
302
|
-
"/doc.html",
|
|
303
|
-
"/swagger/index.html",
|
|
304
|
-
"/api/swagger.json",
|
|
305
|
-
"/api/swagger.yaml",
|
|
306
|
-
"/api-docs/swagger.json",
|
|
307
|
-
]
|
|
308
|
-
|
|
309
|
-
def probe_swagger(base_url):
|
|
310
|
-
for path in SWAGGER_PATHS:
|
|
311
|
-
url = base_url + path
|
|
312
|
-
try:
|
|
313
|
-
resp = requests.get(url, timeout=10)
|
|
314
|
-
if resp.status_code == 200:
|
|
315
|
-
if 'swagger' in resp.text.lower() or 'openapi' in resp.text.lower():
|
|
316
|
-
print(f"[+] Found: {url}")
|
|
317
|
-
return url
|
|
318
|
-
except:
|
|
319
|
-
pass
|
|
320
|
-
return None
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
### 4.2 解析 OpenAPI Schema
|
|
324
|
-
|
|
325
|
-
```python
|
|
326
|
-
# 解析 OpenAPI JSON
|
|
327
|
-
def parse_openapi_schema(schema_url):
|
|
328
|
-
resp = requests.get(schema_url)
|
|
329
|
-
schema = resp.json()
|
|
330
|
-
|
|
331
|
-
api_info = {
|
|
332
|
-
'title': schema.get('info', {}).get('title'),
|
|
333
|
-
'version': schema.get('info', {}).get('version'),
|
|
334
|
-
'basePath': schema.get('basePath'),
|
|
335
|
-
'servers': [s.get('url') for s in schema.get('servers', [])],
|
|
336
|
-
'endpoints': []
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
# 提取所有端点
|
|
340
|
-
for path, methods in schema.get('paths', {}).items():
|
|
341
|
-
for method, details in methods.items():
|
|
342
|
-
if method.upper() in ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']:
|
|
343
|
-
api_info['endpoints'].append({
|
|
344
|
-
'path': path,
|
|
345
|
-
'method': method.upper(),
|
|
346
|
-
'summary': details.get('summary'),
|
|
347
|
-
'parameters': details.get('parameters', []),
|
|
348
|
-
'requestBody': details.get('requestBody'),
|
|
349
|
-
'responses': details.get('responses'),
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
return api_info
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
---
|
|
356
|
-
|
|
357
|
-
## 5. 云存储发现
|
|
358
|
-
|
|
359
|
-
### 5.1 云存储 URL 模式
|
|
360
|
-
|
|
361
|
-
```python
|
|
362
|
-
# 云存储 URL 正则
|
|
363
|
-
CLOUD_STORAGE_PATTERNS = {
|
|
364
|
-
'aliyun_oss': [
|
|
365
|
-
r'[a-z0-9-]+\.oss-(?:cn-[a-z0-9-]+)\.aliyuncs\.com',
|
|
366
|
-
r'https?://(?:[a-z0-9-]+\.)?oss-[a-z0-9-]+\.aliyuncs\.com',
|
|
367
|
-
],
|
|
368
|
-
'aws_s3': [
|
|
369
|
-
r'[a-z0-9-]+\.s3(?:(?:\.|\-)[a-z0-9-]+)?\.amazonaws\.com',
|
|
370
|
-
r's3(?:(?:\.|\-)[a-z0-9-]+)?\.amazonaws\.com/[a-z0-9-]+',
|
|
371
|
-
],
|
|
372
|
-
'tencent_cos': [
|
|
373
|
-
r'[a-z0-9-]+\.cos\.[a-z0-9-]+\.myqcloud\.com',
|
|
374
|
-
r'https?://(?:[a-z0-9-]+\.)?cos\.myqcloud\.com',
|
|
375
|
-
],
|
|
376
|
-
'huawei_obs': [
|
|
377
|
-
r'[a-z0-9-]+\.obs\.[a-z0-9-]+\.hwclouds\.com',
|
|
378
|
-
r'https?://(?:[a-z0-9-]+\.)?obs\.hwclouds\.com',
|
|
379
|
-
],
|
|
380
|
-
'minio': [
|
|
381
|
-
r'[a-z0-9-]+\.minio\.[a-z0-9.-]+',
|
|
382
|
-
r'http://minio(?:[a-z0-9.-]+)?:\d+',
|
|
383
|
-
],
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
def detect_cloud_storage(content):
|
|
387
|
-
findings = []
|
|
388
|
-
for provider, patterns in CLOUD_STORAGE_PATTERNS.items():
|
|
389
|
-
for pattern in patterns:
|
|
390
|
-
matches = re.findall(pattern, content, re.IGNORECASE)
|
|
391
|
-
for match in matches:
|
|
392
|
-
findings.append({
|
|
393
|
-
'provider': provider,
|
|
394
|
-
'url': match if '://' in match else f'http://{match}'
|
|
395
|
-
})
|
|
396
|
-
return findings
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
### 5.2 云存储探测
|
|
400
|
-
|
|
401
|
-
```python
|
|
402
|
-
# 云存储探测函数
|
|
403
|
-
def probe_cloud_storage(bucket_url):
|
|
404
|
-
tests = [
|
|
405
|
-
# 列出目录
|
|
406
|
-
("GET", "", None, [200], "list_bucket"),
|
|
407
|
-
# 上传测试
|
|
408
|
-
("PUT", "test.txt", b"test", [200, 201], "put_object"),
|
|
409
|
-
# 获取上传的文件
|
|
410
|
-
("GET", "test.txt", None, [200], "get_object"),
|
|
411
|
-
]
|
|
412
|
-
|
|
413
|
-
results = []
|
|
414
|
-
for method, path, data, expected_codes, test_name in tests:
|
|
415
|
-
url = bucket_url.rstrip('/') + '/' + path
|
|
416
|
-
try:
|
|
417
|
-
if method == "GET":
|
|
418
|
-
resp = requests.get(url, timeout=10)
|
|
419
|
-
elif method == "PUT":
|
|
420
|
-
resp = requests.put(url, data=data, timeout=10)
|
|
421
|
-
|
|
422
|
-
if resp.status_code in expected_codes:
|
|
423
|
-
results.append({
|
|
424
|
-
'test': test_name,
|
|
425
|
-
'status': 'success',
|
|
426
|
-
'details': resp.status_code
|
|
427
|
-
})
|
|
428
|
-
except Exception as e:
|
|
429
|
-
results.append({
|
|
430
|
-
'test': test_name,
|
|
431
|
-
'status': 'failed',
|
|
432
|
-
'error': str(e)
|
|
433
|
-
})
|
|
434
|
-
|
|
435
|
-
return results
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
---
|
|
439
|
-
|
|
440
|
-
## 6. 关联系统发现
|
|
441
|
-
|
|
442
|
-
### 6.1 内网地址发现
|
|
443
|
-
|
|
444
|
-
```python
|
|
445
|
-
# 从响应中提取内网地址
|
|
446
|
-
INTERNAL_IP_PATTERNS = [
|
|
447
|
-
# 私有IP
|
|
448
|
-
r'http://192\.168\.\d{1,3}\.\d{1,3}(?::\d+)?(?:/[^\s]*)?',
|
|
449
|
-
r'http://10\.\d{1,3}\.\d{1,3}\.\d{1,3}(?::\d+)?(?:/[^\s]*)?',
|
|
450
|
-
r'http://172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}(?::\d+)?(?:/[^\s]*)?',
|
|
451
|
-
# localhost
|
|
452
|
-
r'http://localhost(?::\d+)?(?:/[^\s]*)?',
|
|
453
|
-
r'http://127\.0\.0\.1(?::\d+)?(?:/[^\s]*)?',
|
|
454
|
-
# Docker/K8s
|
|
455
|
-
r'http://172\.(?:1[7-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}(?::\d+)?',
|
|
456
|
-
]
|
|
457
|
-
|
|
458
|
-
def extract_internal_ips(content):
|
|
459
|
-
findings = []
|
|
460
|
-
for pattern in INTERNAL_IP_PATTERNS:
|
|
461
|
-
matches = re.findall(pattern, content, re.IGNORECASE)
|
|
462
|
-
findings.extend(matches)
|
|
463
|
-
return list(set(findings))
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
### 6.2 系统关联发现
|
|
467
|
-
|
|
468
|
-
```python
|
|
469
|
-
# 从响应头提取 Server 信息
|
|
470
|
-
def extract_server_info(response_headers):
|
|
471
|
-
info = {
|
|
472
|
-
'server': response_headers.get('Server', ''),
|
|
473
|
-
'xPoweredBy': response_headers.get('X-Powered-By', ''),
|
|
474
|
-
'via': response_headers.get('Via', ''),
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
# 提取版本信息
|
|
478
|
-
for header_value in info.values():
|
|
479
|
-
version_match = re.findall(r'(\d+\.\d+\.\d+)', header_value)
|
|
480
|
-
if version_match:
|
|
481
|
-
info['versions'] = version_match
|
|
482
|
-
|
|
483
|
-
return info
|
|
484
|
-
|
|
485
|
-
# 常见系统 URL 模式
|
|
486
|
-
SYSTEM_PATTERNS = {
|
|
487
|
-
'admin': '/admin', '/administrator', '/manage',
|
|
488
|
-
'monitoring': '/monitor', '/health', '/status',
|
|
489
|
-
'database': '/phpmyadmin', '/adminer', '/pgadmin',
|
|
490
|
-
'devops': '/jenkins', '/gitlab', '/jira', '/confluence',
|
|
491
|
-
'storage': '/oss', '/files', '/upload',
|
|
492
|
-
}
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
---
|
|
496
|
-
|
|
497
|
-
## 7. 指纹识别
|
|
498
|
-
|
|
499
|
-
### 7.1 技术栈识别
|
|
500
|
-
|
|
501
|
-
```python
|
|
502
|
-
# 技术栈指纹
|
|
503
|
-
TECH_FINGERPRINTS = {
|
|
504
|
-
'frontend': {
|
|
505
|
-
'vue': ['vue.js', 'vue.min.js', '__vue__', 'Vue'],
|
|
506
|
-
'react': ['react.js', 'react.min.js', '_react', 'React'],
|
|
507
|
-
'angular': ['angular.js', 'angular.min.js', 'ng-app', 'Angular'],
|
|
508
|
-
'jquery': ['jquery.js', 'jquery.min.js', 'jQuery'],
|
|
509
|
-
},
|
|
510
|
-
'backend': {
|
|
511
|
-
'express': ['Express', 'x-powered-by', 'express-session'],
|
|
512
|
-
'django': ['csrftoken', 'csrfmiddlewaretoken', 'django'],
|
|
513
|
-
'flask': ['flask', 'session', ' werkzeug'],
|
|
514
|
-
'spring': ['JSESSIONID', 'spring', 'GRAILS_'],
|
|
515
|
-
'rails': ['_session_id', 'Ruby on Rails'],
|
|
516
|
-
'laravel': ['laravel_session', 'XSRF-TOKEN'],
|
|
517
|
-
},
|
|
518
|
-
'server': {
|
|
519
|
-
'nginx': ['nginx', 'Server: nginx'],
|
|
520
|
-
'apache': ['apache', 'Server: apache'],
|
|
521
|
-
'iis': ['IIS', 'X-AspNet-Version'],
|
|
522
|
-
'tomcat': ['Apache-Coyote', 'tomcat'],
|
|
523
|
-
},
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
def identify_tech_stack(html_content, headers):
|
|
527
|
-
identified = {'frontend': [], 'backend': [], 'server': []}
|
|
528
|
-
|
|
529
|
-
content = html_content + str(headers)
|
|
530
|
-
|
|
531
|
-
for category, fingerprints in TECH_FINGERPRINTS.items():
|
|
532
|
-
for tech, keywords in fingerprints.items():
|
|
533
|
-
for keyword in keywords:
|
|
534
|
-
if keyword.lower() in content.lower():
|
|
535
|
-
identified[category].append(tech)
|
|
536
|
-
break
|
|
537
|
-
|
|
538
|
-
return identified
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
### 7.2 CMS 识别
|
|
542
|
-
|
|
543
|
-
```python
|
|
544
|
-
# CMS 指纹
|
|
545
|
-
CMS_PATTERNS = {
|
|
546
|
-
'wordpress': [
|
|
547
|
-
'/wp-content/',
|
|
548
|
-
'/wp-includes/',
|
|
549
|
-
'wp-json',
|
|
550
|
-
'WordPress',
|
|
551
|
-
],
|
|
552
|
-
'drupal': [
|
|
553
|
-
'/sites/default/',
|
|
554
|
-
'drupal.js',
|
|
555
|
-
'Drupal.settings',
|
|
556
|
-
],
|
|
557
|
-
'joomla': [
|
|
558
|
-
'/media/jui/',
|
|
559
|
-
'Joomla!',
|
|
560
|
-
'option=com_',
|
|
561
|
-
],
|
|
562
|
-
'dedecms': [
|
|
563
|
-
'/templets/default/',
|
|
564
|
-
'DedeCms',
|
|
565
|
-
'dede_',
|
|
566
|
-
],
|
|
567
|
-
'discuz': [
|
|
568
|
-
'/static/image/common/',
|
|
569
|
-
'Discuz!',
|
|
570
|
-
'forum.php',
|
|
571
|
-
],
|
|
572
|
-
}
|
|
573
|
-
```
|
|
574
|
-
|
|
575
|
-
---
|
|
576
|
-
|
|
577
|
-
## 附录:资产发现检查清单
|
|
578
|
-
|
|
579
|
-
```
|
|
580
|
-
□ 被动收集
|
|
581
|
-
□ SSL 证书分析
|
|
582
|
-
□ DNS 记录收集
|
|
583
|
-
□ 网页内容提取
|
|
584
|
-
|
|
585
|
-
□ 主动探测
|
|
586
|
-
□ 子域名爆破
|
|
587
|
-
□ 端口扫描
|
|
588
|
-
□ 路径爆破
|
|
589
|
-
|
|
590
|
-
□ JS 分析
|
|
591
|
-
□ JS 文件收集
|
|
592
|
-
□ API 端点提取
|
|
593
|
-
□ 敏感信息发现
|
|
594
|
-
|
|
595
|
-
□ API 文档
|
|
596
|
-
□ Swagger 发现
|
|
597
|
-
□ OpenAPI 解析
|
|
598
|
-
□ API 端点整理
|
|
599
|
-
|
|
600
|
-
□ 云存储
|
|
601
|
-
□ 云存储 URL 发现
|
|
602
|
-
□ Bucket 权限测试
|
|
603
|
-
|
|
604
|
-
□ 关联系统
|
|
605
|
-
□ 内网地址发现
|
|
606
|
-
□ 系统关联分析
|
|
607
|
-
|
|
608
|
-
□ 指纹识别
|
|
609
|
-
□ 技术栈识别
|
|
610
|
-
□ CMS 识别
|
|
611
|
-
□ 版本信息收集
|
|
612
|
-
```
|
|
1
|
+
# Asset Discovery Guidance
|
|
2
|
+
|
|
3
|
+
将原始 API 材料转换为紧凑的安全相关清单。
|
|
4
|
+
|
|
5
|
+
## 目标
|
|
6
|
+
|
|
7
|
+
识别对安全测试最重要的 API 部分。
|
|
8
|
+
|
|
9
|
+
## 核心表面
|
|
10
|
+
|
|
11
|
+
- base URL(s)
|
|
12
|
+
- versioning scheme
|
|
13
|
+
- routes 或 operations
|
|
14
|
+
- methods
|
|
15
|
+
- content types
|
|
16
|
+
- auth schemes
|
|
17
|
+
|
|
18
|
+
## 信任边界
|
|
19
|
+
|
|
20
|
+
- public vs authenticated endpoints
|
|
21
|
+
- user vs admin operations
|
|
22
|
+
- internal vs external APIs
|
|
23
|
+
- service-to-service 或 callback flows
|
|
24
|
+
- tenant 或 organization 边界
|
|
25
|
+
|
|
26
|
+
## 敏感对象
|
|
27
|
+
|
|
28
|
+
关注以下对象:
|
|
29
|
+
- users
|
|
30
|
+
- roles
|
|
31
|
+
- teams
|
|
32
|
+
- organizations
|
|
33
|
+
- invoices
|
|
34
|
+
- payments
|
|
35
|
+
- orders
|
|
36
|
+
- files
|
|
37
|
+
- secrets
|
|
38
|
+
- API keys
|
|
39
|
+
- tokens
|
|
40
|
+
- audit logs
|
|
41
|
+
- exports
|
|
42
|
+
- configuration objects
|
|
43
|
+
|
|
44
|
+
## 高风险操作模式
|
|
45
|
+
|
|
46
|
+
标记与以下相关的 endpoints 或 mutations:
|
|
47
|
+
- create/update/delete user
|
|
48
|
+
- role assignment
|
|
49
|
+
- permission change
|
|
50
|
+
- password reset
|
|
51
|
+
- token issue 或 refresh
|
|
52
|
+
- export 或 bulk download
|
|
53
|
+
- import 或 bulk update
|
|
54
|
+
- file upload
|
|
55
|
+
- webhook registration
|
|
56
|
+
- callback URL configuration
|
|
57
|
+
- search 或 filter on sensitive entities
|
|
58
|
+
- internal admin dashboards 或 debug endpoints
|
|
59
|
+
|
|
60
|
+
## REST 提示
|
|
61
|
+
|
|
62
|
+
优先包含模式的 endpoints:
|
|
63
|
+
- `/admin`
|
|
64
|
+
- `/internal`
|
|
65
|
+
- `/users`
|
|
66
|
+
- `/roles`
|
|
67
|
+
- `/permissions`
|
|
68
|
+
- `/export`
|
|
69
|
+
- `/import`
|
|
70
|
+
- `/search`
|
|
71
|
+
- `/upload`
|
|
72
|
+
- `/files`
|
|
73
|
+
- `/billing`
|
|
74
|
+
- `/settings`
|
|
75
|
+
- `/token`
|
|
76
|
+
- `/auth`
|
|
77
|
+
- `/debug`
|
|
78
|
+
|
|
79
|
+
同时注意:
|
|
80
|
+
- bulk 操作
|
|
81
|
+
- object IDs in path 或 query
|
|
82
|
+
- 同一资源上隐藏的替代方法
|
|
83
|
+
- 不一致的版本化 endpoints
|
|
84
|
+
|
|
85
|
+
## GraphQL 提示
|
|
86
|
+
|
|
87
|
+
优先:
|
|
88
|
+
- 变更 roles、permissions 或 state 的 mutations
|
|
89
|
+
- 暴露嵌套对象遍历的 fields
|
|
90
|
+
- admin-only resolvers
|
|
91
|
+
- schema introspection 暴露
|
|
92
|
+
- 带敏感链接数据的宽对象图
|
|
93
|
+
- 可能意外扩展访问的 connection 或 pagination 模式
|
|
94
|
+
|
|
95
|
+
## 资产摘要格式
|
|
96
|
+
|
|
97
|
+
优先简洁输出:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
- Base URLs:
|
|
101
|
+
- API type:
|
|
102
|
+
- Auth schemes:
|
|
103
|
+
- Roles observed or assumed:
|
|
104
|
+
- Sensitive objects:
|
|
105
|
+
- High-risk operations:
|
|
106
|
+
- Trust boundaries:
|
|
107
|
+
- Unknown areas:
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## 优先级规则
|
|
111
|
+
|
|
112
|
+
当表面较大时,优先深度在:
|
|
113
|
+
1. auth 和 role 变更
|
|
114
|
+
2. user 和 tenant 数据
|
|
115
|
+
3. export/import 和 bulk 操作
|
|
116
|
+
4. file 和 callback 流程
|
|
117
|
+
5. 金融或行政操作
|
|
118
|
+
|
|
119
|
+
不要在低风险的只读 metadata endpoints 上浪费空间,除非它们支持更广泛的滥用路径。
|