opencode-api-security-testing 2.1.0 → 2.1.2
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/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/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,179 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
HTTP Client - 同步/异步 HTTP 客户端
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import time
|
|
8
|
+
from typing import Dict, Optional, Any, Callable
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
import requests
|
|
11
|
+
from requests.adapters import HTTPAdapter
|
|
12
|
+
from urllib3.util.retry import Retry
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class HTTPClientConfig:
|
|
17
|
+
"""HTTP 客户端配置"""
|
|
18
|
+
max_concurrent: int = 50
|
|
19
|
+
max_retries: int = 3
|
|
20
|
+
timeout: int = 30
|
|
21
|
+
proxy: Optional[str] = None
|
|
22
|
+
verify_ssl: bool = True
|
|
23
|
+
retry_backoff: float = 0.5
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class HTTPClient:
|
|
27
|
+
"""
|
|
28
|
+
HTTP 客户端
|
|
29
|
+
|
|
30
|
+
功能:
|
|
31
|
+
- 同步/异步请求
|
|
32
|
+
- 并发控制
|
|
33
|
+
- 重试机制
|
|
34
|
+
- 代理支持
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, config: Optional[HTTPClientConfig] = None):
|
|
38
|
+
self.config = config or HTTPClientConfig()
|
|
39
|
+
self.session = self._create_session()
|
|
40
|
+
self._semaphore: Optional[asyncio.Semaphore] = None
|
|
41
|
+
|
|
42
|
+
def _create_session(self) -> requests.Session:
|
|
43
|
+
"""创建 requests session"""
|
|
44
|
+
session = requests.Session()
|
|
45
|
+
|
|
46
|
+
retry_strategy = Retry(
|
|
47
|
+
total=self.config.max_retries,
|
|
48
|
+
backoff_factor=self.config.retry_backoff,
|
|
49
|
+
status_forcelist=[429, 500, 502, 503, 504],
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
adapter = HTTPAdapter(max_retries=retry_strategy, pool_maxsize=self.config.max_concurrent)
|
|
53
|
+
session.mount("http://", adapter)
|
|
54
|
+
session.mount("https://", adapter)
|
|
55
|
+
|
|
56
|
+
session.headers.update({
|
|
57
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
|
58
|
+
'Accept': '*/*',
|
|
59
|
+
'Accept-Encoding': 'gzip, deflate',
|
|
60
|
+
'Connection': 'keep-alive',
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
if self.config.proxy:
|
|
64
|
+
session.proxies = {
|
|
65
|
+
'http': self.config.proxy,
|
|
66
|
+
'https': self.config.proxy,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return session
|
|
70
|
+
|
|
71
|
+
def get(self, url: str, **kwargs) -> requests.Response:
|
|
72
|
+
"""GET 请求"""
|
|
73
|
+
kwargs.setdefault('timeout', self.config.timeout)
|
|
74
|
+
kwargs.setdefault('verify', self.config.verify_ssl)
|
|
75
|
+
return self.session.get(url, **kwargs)
|
|
76
|
+
|
|
77
|
+
def post(self, url: str, **kwargs) -> requests.Response:
|
|
78
|
+
"""POST 请求"""
|
|
79
|
+
kwargs.setdefault('timeout', self.config.timeout)
|
|
80
|
+
kwargs.setdefault('verify', self.config.verify_ssl)
|
|
81
|
+
return self.session.post(url, **kwargs)
|
|
82
|
+
|
|
83
|
+
def put(self, url: str, **kwargs) -> requests.Response:
|
|
84
|
+
"""PUT 请求"""
|
|
85
|
+
kwargs.setdefault('timeout', self.config.timeout)
|
|
86
|
+
kwargs.setdefault('verify', self.config.verify_ssl)
|
|
87
|
+
return self.session.put(url, **kwargs)
|
|
88
|
+
|
|
89
|
+
def delete(self, url: str, **kwargs) -> requests.Response:
|
|
90
|
+
"""DELETE 请求"""
|
|
91
|
+
kwargs.setdefault('timeout', self.config.timeout)
|
|
92
|
+
kwargs.setdefault('verify', self.config.verify_ssl)
|
|
93
|
+
return self.session.delete(url, **kwargs)
|
|
94
|
+
|
|
95
|
+
def request(self, method: str, url: str, **kwargs) -> requests.Response:
|
|
96
|
+
"""通用请求"""
|
|
97
|
+
kwargs.setdefault('timeout', self.config.timeout)
|
|
98
|
+
kwargs.setdefault('verify', self.config.verify_ssl)
|
|
99
|
+
return self.session.request(method, url, **kwargs)
|
|
100
|
+
|
|
101
|
+
def batch_request(self, urls: list, method: str = 'GET', **kwargs) -> Dict[str, requests.Response]:
|
|
102
|
+
"""批量请求"""
|
|
103
|
+
import concurrent.futures
|
|
104
|
+
|
|
105
|
+
results = {}
|
|
106
|
+
|
|
107
|
+
def fetch(url):
|
|
108
|
+
try:
|
|
109
|
+
resp = self.request(method, url, **kwargs)
|
|
110
|
+
return url, resp
|
|
111
|
+
except Exception as e:
|
|
112
|
+
return url, None
|
|
113
|
+
|
|
114
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=self.config.max_concurrent) as executor:
|
|
115
|
+
futures = {executor.submit(fetch, url): url for url in urls}
|
|
116
|
+
for future in concurrent.futures.as_completed(futures):
|
|
117
|
+
url, resp = future.result()
|
|
118
|
+
results[url] = resp
|
|
119
|
+
|
|
120
|
+
return results
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class AsyncHTTPClient:
|
|
124
|
+
"""
|
|
125
|
+
异步 HTTP 客户端
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
def __init__(self, config: Optional[HTTPClientConfig] = None):
|
|
129
|
+
self.config = config or HTTPClientConfig()
|
|
130
|
+
self._session: Optional[aiohttp.ClientSession] = None
|
|
131
|
+
self._semaphore = asyncio.Semaphore(self.config.max_concurrent)
|
|
132
|
+
|
|
133
|
+
async def _get_session(self) -> 'aiohttp.ClientSession':
|
|
134
|
+
"""获取或创建 session"""
|
|
135
|
+
if self._session is None or self._session.closed:
|
|
136
|
+
timeout = aiohttp.ClientTimeout(total=self.config.timeout)
|
|
137
|
+
self._session = aiohttp.ClientSession(
|
|
138
|
+
timeout=timeout,
|
|
139
|
+
headers={
|
|
140
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
|
141
|
+
}
|
|
142
|
+
)
|
|
143
|
+
return self._session
|
|
144
|
+
|
|
145
|
+
async def get(self, url: str, **kwargs) -> 'aiohttp.ClientResponse':
|
|
146
|
+
"""GET 请求"""
|
|
147
|
+
session = await self._get_session()
|
|
148
|
+
async with self._semaphore:
|
|
149
|
+
return await session.get(url, **kwargs)
|
|
150
|
+
|
|
151
|
+
async def post(self, url: str, **kwargs) -> 'aiohttp.ClientResponse':
|
|
152
|
+
"""POST 请求"""
|
|
153
|
+
session = await self._get_session()
|
|
154
|
+
async with self._semaphore:
|
|
155
|
+
return await session.post(url, **kwargs)
|
|
156
|
+
|
|
157
|
+
async def request(self, method: str, url: str, **kwargs) -> 'aiohttp.ClientResponse':
|
|
158
|
+
"""通用请求"""
|
|
159
|
+
session = await self._get_session()
|
|
160
|
+
async with self._semaphore:
|
|
161
|
+
return await session.request(method, url, **kwargs)
|
|
162
|
+
|
|
163
|
+
async def batch_request(self, urls: list, method: str = 'GET', **kwargs) -> Dict[str, Any]:
|
|
164
|
+
"""批量请求"""
|
|
165
|
+
tasks = [self.request(method, url, **kwargs) for url in urls]
|
|
166
|
+
responses = await asyncio.gather(*tasks, return_exceptions=True)
|
|
167
|
+
return dict(zip(urls, responses))
|
|
168
|
+
|
|
169
|
+
async def close(self):
|
|
170
|
+
"""关闭 session"""
|
|
171
|
+
if self._session and not self._session.closed:
|
|
172
|
+
await self._session.close()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
import aiohttp
|
|
177
|
+
HAS_AIOHTTP = True
|
|
178
|
+
except ImportError:
|
|
179
|
+
HAS_AIOHTTP = False
|
package/core/models.py
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Data Models - 数据模型
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass, field, asdict
|
|
7
|
+
from typing import Dict, List, Optional, Set, Any
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from enum import Enum
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Severity(Enum):
|
|
13
|
+
"""漏洞严重等级"""
|
|
14
|
+
CRITICAL = "critical"
|
|
15
|
+
HIGH = "high"
|
|
16
|
+
MEDIUM = "medium"
|
|
17
|
+
LOW = "low"
|
|
18
|
+
INFO = "info"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class EndpointSource(Enum):
|
|
22
|
+
"""端点来源"""
|
|
23
|
+
JS_PARSER = "js_parser"
|
|
24
|
+
AST_ANALYZER = "ast_analyzer"
|
|
25
|
+
FUZZ_API = "fuzz_api"
|
|
26
|
+
SWAGGER = "swagger"
|
|
27
|
+
BROWSER = "browser"
|
|
28
|
+
MANUAL = "manual"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class APIEndpoint:
|
|
33
|
+
"""
|
|
34
|
+
API 端点模型
|
|
35
|
+
|
|
36
|
+
属性:
|
|
37
|
+
- path: API 路径
|
|
38
|
+
- method: HTTP 方法
|
|
39
|
+
- parameters: 参数集合
|
|
40
|
+
- source: 端点来源
|
|
41
|
+
- full_url: 完整 URL
|
|
42
|
+
- score: 评分
|
|
43
|
+
- is_high_value: 是否高价值目标
|
|
44
|
+
- status_code: HTTP 状态码
|
|
45
|
+
- content_length: 响应内容长度
|
|
46
|
+
"""
|
|
47
|
+
path: str
|
|
48
|
+
method: str = "GET"
|
|
49
|
+
parameters: Set[str] = field(default_factory=set)
|
|
50
|
+
source: str = "unknown"
|
|
51
|
+
full_url: str = ""
|
|
52
|
+
score: int = 0
|
|
53
|
+
is_high_value: bool = False
|
|
54
|
+
status_code: int = 0
|
|
55
|
+
content_length: int = 0
|
|
56
|
+
headers: Dict[str, str] = field(default_factory=dict)
|
|
57
|
+
tags: Set[str] = field(default_factory=set)
|
|
58
|
+
|
|
59
|
+
def to_dict(self) -> Dict:
|
|
60
|
+
return {
|
|
61
|
+
'path': self.path,
|
|
62
|
+
'method': self.method,
|
|
63
|
+
'parameters': list(self.parameters),
|
|
64
|
+
'source': self.source,
|
|
65
|
+
'full_url': self.full_url,
|
|
66
|
+
'score': self.score,
|
|
67
|
+
'is_high_value': self.is_high_value,
|
|
68
|
+
'status_code': self.status_code,
|
|
69
|
+
'content_length': self.content_length,
|
|
70
|
+
'tags': list(self.tags)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def endpoint_id(self) -> str:
|
|
75
|
+
"""端点唯一标识"""
|
|
76
|
+
return f"{self.method}:{self.path}"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class Vulnerability:
|
|
81
|
+
"""
|
|
82
|
+
漏洞模型
|
|
83
|
+
|
|
84
|
+
属性:
|
|
85
|
+
- vuln_type: 漏洞类型
|
|
86
|
+
- severity: 严重等级
|
|
87
|
+
- endpoint: 关联端点
|
|
88
|
+
- method: HTTP 方法
|
|
89
|
+
- payload: 攻击载荷
|
|
90
|
+
- evidence: 证据
|
|
91
|
+
"""
|
|
92
|
+
vuln_type: str
|
|
93
|
+
severity: Severity = Severity.MEDIUM
|
|
94
|
+
endpoint: str = ""
|
|
95
|
+
method: str = "GET"
|
|
96
|
+
payload: str = ""
|
|
97
|
+
evidence: str = ""
|
|
98
|
+
status_code: int = 0
|
|
99
|
+
req_headers: Dict[str, str] = field(default_factory=dict)
|
|
100
|
+
resp_headers: Dict[str, str] = field(default_factory=dict)
|
|
101
|
+
resp_content: str = ""
|
|
102
|
+
timestamp: str = field(default_factory=lambda: datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
|
103
|
+
|
|
104
|
+
def to_dict(self) -> Dict:
|
|
105
|
+
return {
|
|
106
|
+
'type': self.vuln_type,
|
|
107
|
+
'severity': self.severity.value,
|
|
108
|
+
'endpoint': self.endpoint,
|
|
109
|
+
'method': self.method,
|
|
110
|
+
'payload': self.payload,
|
|
111
|
+
'evidence': self.evidence,
|
|
112
|
+
'status_code': self.status_code,
|
|
113
|
+
'timestamp': self.timestamp
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass
|
|
118
|
+
class SensitiveData:
|
|
119
|
+
"""
|
|
120
|
+
敏感信息模型
|
|
121
|
+
"""
|
|
122
|
+
data_type: str
|
|
123
|
+
severity: Severity = Severity.MEDIUM
|
|
124
|
+
endpoint: str = ""
|
|
125
|
+
value: str = ""
|
|
126
|
+
matched_pattern: str = ""
|
|
127
|
+
context: str = ""
|
|
128
|
+
|
|
129
|
+
def to_dict(self) -> Dict:
|
|
130
|
+
return {
|
|
131
|
+
'data_type': self.data_type,
|
|
132
|
+
'severity': self.severity.value,
|
|
133
|
+
'endpoint': self.endpoint,
|
|
134
|
+
'value': self.value[:50] + "..." if len(self.value) > 50 else self.value,
|
|
135
|
+
'matched_pattern': self.matched_pattern
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass
|
|
140
|
+
class ScanResult:
|
|
141
|
+
"""
|
|
142
|
+
扫描结果模型
|
|
143
|
+
|
|
144
|
+
属性:
|
|
145
|
+
- target_url: 目标 URL
|
|
146
|
+
- start_time: 开始时间
|
|
147
|
+
- end_time: 结束时间
|
|
148
|
+
- status: 扫描状态
|
|
149
|
+
- total_apis: 总 API 数
|
|
150
|
+
- alive_apis: 存活 API 数
|
|
151
|
+
- high_value_apis: 高价值 API 数
|
|
152
|
+
- api_endpoints: API 端点列表
|
|
153
|
+
- vulnerabilities: 漏洞列表
|
|
154
|
+
- sensitive_data: 敏感信息列表
|
|
155
|
+
- collector_data: 采集阶段数据
|
|
156
|
+
- errors: 错误列表
|
|
157
|
+
"""
|
|
158
|
+
target_url: str
|
|
159
|
+
start_time: str = ""
|
|
160
|
+
end_time: str = ""
|
|
161
|
+
status: str = "pending"
|
|
162
|
+
total_apis: int = 0
|
|
163
|
+
alive_apis: int = 0
|
|
164
|
+
high_value_apis: int = 0
|
|
165
|
+
api_endpoints: List[APIEndpoint] = field(default_factory=list)
|
|
166
|
+
vulnerabilities: List[Vulnerability] = field(default_factory=list)
|
|
167
|
+
sensitive_data: List[SensitiveData] = field(default_factory=list)
|
|
168
|
+
collector_data: Dict[str, Any] = field(default_factory=dict)
|
|
169
|
+
errors: List[str] = field(default_factory=list)
|
|
170
|
+
|
|
171
|
+
def to_dict(self) -> Dict:
|
|
172
|
+
return {
|
|
173
|
+
'target_url': self.target_url,
|
|
174
|
+
'start_time': self.start_time,
|
|
175
|
+
'end_time': self.end_time,
|
|
176
|
+
'status': self.status,
|
|
177
|
+
'summary': {
|
|
178
|
+
'total_apis': self.total_apis,
|
|
179
|
+
'alive_apis': self.alive_apis,
|
|
180
|
+
'high_value_apis': self.high_value_apis,
|
|
181
|
+
'vulnerabilities': {
|
|
182
|
+
'critical': len([v for v in self.vulnerabilities if v.severity == Severity.CRITICAL]),
|
|
183
|
+
'high': len([v for v in self.vulnerabilities if v.severity == Severity.HIGH]),
|
|
184
|
+
'medium': len([v for v in self.vulnerabilities if v.severity == Severity.MEDIUM]),
|
|
185
|
+
'low': len([v for v in self.vulnerabilities if v.severity == Severity.LOW]),
|
|
186
|
+
},
|
|
187
|
+
'sensitive_data': len(self.sensitive_data)
|
|
188
|
+
},
|
|
189
|
+
'api_endpoints': [ep.to_dict() for ep in self.api_endpoints],
|
|
190
|
+
'vulnerabilities': [v.to_dict() for v in self.vulnerabilities],
|
|
191
|
+
'sensitive_data': [s.to_dict() for s in self.sensitive_data],
|
|
192
|
+
'errors': self.errors
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
def add_vulnerability(self, vuln: Vulnerability):
|
|
196
|
+
"""添加漏洞"""
|
|
197
|
+
self.vulnerabilities.append(vuln)
|
|
198
|
+
|
|
199
|
+
def add_endpoint(self, endpoint: APIEndpoint):
|
|
200
|
+
"""添加端点"""
|
|
201
|
+
self.api_endpoints.append(endpoint)
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def vuln_count_by_severity(self) -> Dict[str, int]:
|
|
205
|
+
"""按严重等级统计漏洞"""
|
|
206
|
+
return {
|
|
207
|
+
'critical': len([v for v in self.vulnerabilities if v.severity == Severity.CRITICAL]),
|
|
208
|
+
'high': len([v for v in self.vulnerabilities if v.severity == Severity.HIGH]),
|
|
209
|
+
'medium': len([v for v in self.vulnerabilities if v.severity == Severity.MEDIUM]),
|
|
210
|
+
'low': len([v for v in self.vulnerabilities if v.severity == Severity.LOW]),
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def total_vulnerabilities(self) -> int:
|
|
215
|
+
"""漏洞总数"""
|
|
216
|
+
return len(self.vulnerabilities)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@dataclass
|
|
220
|
+
class JSFile:
|
|
221
|
+
"""
|
|
222
|
+
JS 文件模型
|
|
223
|
+
"""
|
|
224
|
+
url: str
|
|
225
|
+
content_hash: str = ""
|
|
226
|
+
content: str = ""
|
|
227
|
+
endpoints: List[Dict] = field(default_factory=list)
|
|
228
|
+
parameter_names: Set[str] = field(default_factory=set)
|
|
229
|
+
routes: List[str] = field(default_factory=list)
|
|
230
|
+
env_configs: Dict[str, str] = field(default_factory=dict)
|
|
231
|
+
is_alive: bool = False
|
|
232
|
+
size: int = 0
|
|
233
|
+
|
|
234
|
+
def to_dict(self) -> Dict:
|
|
235
|
+
return {
|
|
236
|
+
'url': self.url,
|
|
237
|
+
'content_hash': self.content_hash,
|
|
238
|
+
'endpoints': self.endpoints,
|
|
239
|
+
'parameters': list(self.parameter_names),
|
|
240
|
+
'routes': self.routes,
|
|
241
|
+
'env_configs': self.env_configs,
|
|
242
|
+
'is_alive': self.is_alive,
|
|
243
|
+
'size': self.size
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@dataclass
|
|
248
|
+
class APIFindResult:
|
|
249
|
+
"""
|
|
250
|
+
API 发现结果
|
|
251
|
+
"""
|
|
252
|
+
path: str
|
|
253
|
+
method: str = "GET"
|
|
254
|
+
source_type: str = ""
|
|
255
|
+
url_type: str = ""
|
|
256
|
+
parameters: Set[str] = field(default_factory=set)
|
|
257
|
+
confidence: float = 1.0
|
|
258
|
+
|
|
259
|
+
def to_dict(self) -> Dict:
|
|
260
|
+
return {
|
|
261
|
+
'path': self.path,
|
|
262
|
+
'method': self.method,
|
|
263
|
+
'source_type': self.source_type,
|
|
264
|
+
'url_type': self.url_type,
|
|
265
|
+
'parameters': list(self.parameters),
|
|
266
|
+
'confidence': self.confidence
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@dataclass
|
|
271
|
+
class ProxyResult:
|
|
272
|
+
"""
|
|
273
|
+
代理扫描结果
|
|
274
|
+
"""
|
|
275
|
+
method: str
|
|
276
|
+
url: str
|
|
277
|
+
path: str
|
|
278
|
+
headers: Dict[str, str]
|
|
279
|
+
body: str = ""
|
|
280
|
+
status_code: int = 0
|
|
281
|
+
response_headers: Dict[str, str] = field(default_factory=dict)
|
|
282
|
+
response_body: str = ""
|
|
283
|
+
timestamp: str = field(default_factory=lambda: datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
|
284
|
+
|
|
285
|
+
def to_dict(self) -> Dict:
|
|
286
|
+
return {
|
|
287
|
+
'method': self.method,
|
|
288
|
+
'url': self.url,
|
|
289
|
+
'path': self.path,
|
|
290
|
+
'headers': self.headers,
|
|
291
|
+
'body': self.body[:500] if self.body else '',
|
|
292
|
+
'status_code': self.status_code,
|
|
293
|
+
'response_headers': self.response_headers,
|
|
294
|
+
'response_body': self.response_body[:500] if self.response_body else '',
|
|
295
|
+
'timestamp': self.timestamp
|
|
296
|
+
}
|