Implementing SSRF Allowlists and Metadata Service Protection: A Secure Coding Guide

Server-Side Request Forgery (SSRF) targeting cloud metadata endpoints remains a critical infrastructure compromise vector. Attackers exploit user-controlled URL parameters to pivot into internal networks, directly querying link-local addresses (e.g., 169.254.169.254, 100.100.100.200, metadata.google.internal) to exfiltrate temporary IAM credentials, instance metadata, and network topology. Traditional perimeter firewalls and regex-based denylists fail against encoding bypasses, DNS rebinding, IPv6 translation, and newly provisioned internal CIDR blocks. The only defensible baseline is strict, parsed URI allowlisting combined with mandatory metadata service hardening.

This guide provides exact implementation steps, secure validation patterns, and infrastructure-as-code enforcement for engineering and compliance teams. For foundational threat modeling, refer to Server-Side Request Forgery (SSRF) Prevention before deploying runtime controls. We will cover schema validation, IMDSv2 enforcement, CI/CD policy-as-code, and runtime egress controls to meet SOC 2, ISO 27001, and PCI-DSS 4.0 requirements.

1. Threat Modeling Metadata Exposure in SSRF Vectors

SSRF bypasses network segmentation by leveraging the application’s own outbound request capabilities. When an application fetches user-supplied URLs, image assets, or webhook endpoints, the underlying HTTP client resolves DNS and routes traffic through the host’s network stack. If the host resides in a cloud environment with default metadata routing, a single unvalidated request to http://169.254.169.254/latest/meta-data/iam/security-credentials/ grants the attacker the instance role’s temporary credentials.

Attack paths typically follow:

  1. Input Vector: File upload via URL, PDF generation, webhook registration, or image proxy.
  2. Resolution Phase: DNS lookup translates attacker-controlled domain to 169.254.169.254 via DNS rebinding or internal DNS override.
  3. Execution Phase: HTTP client follows redirects, bypasses IPv4/IPv6 translation, or exploits open redirects to reach the metadata endpoint.
  4. Credential Exfiltration: Temporary STS tokens are captured and used for lateral movement or cloud account takeover.

Network-level denylists are insufficient because they cannot account for dynamic IP allocation, IPv4-mapped IPv6 addresses (::ffff:169.254.169.254), or HTTP/2 multiplexing bypasses. Secure architecture requires explicit trust boundaries at the application layer, enforced before socket connection. Comprehensive mitigation strategies align with established Vulnerability Patterns & Web Mitigation Strategies frameworks, prioritizing allowlist validation and metadata endpoint session enforcement.

2. Architecting Strict URL Allowlists

Strict allowlisting requires parsing the URI, validating the scheme, resolving the hostname, verifying the resolved IP against an explicit allowlist, and executing the request using the resolved IP to prevent DNS rebinding.

Implementation Requirements

  • Protocol Enforcement: Allow https only. Reject http, file, gopher, ftp, and custom schemes.
  • Domain/IP Allowlist: Maintain a cryptographically signed or environment-managed list of allowed FQDNs and CIDR blocks.
  • DNS Rebinding Mitigation: Resolve DNS before connection. Validate the resolved IP. Pass the resolved IP directly to the HTTP client with the original Host header.
  • Punycode & Normalization: Convert internationalized domain names (IDN) to ASCII punycode before validation. Strip default ports and path traversal sequences.

Production Python Implementation

import socket
import ipaddress
import urllib.parse
from typing import Set, Tuple

ALLOWED_DOMAINS: Set[str] = {"api.trusted-partner.com", "cdn.internal-corp.net"}
ALLOWED_CIDRS: Set[ipaddress.IPv4Network] = {
    ipaddress.ip_network("10.0.0.0/8"),
    ipaddress.ip_network("172.16.0.0/12")
}

def validate_and_prepare_request(user_url: str) -> Tuple[str, dict]:
    parsed = urllib.parse.urlparse(user_url)

    # 1. Protocol enforcement
    if parsed.scheme.lower() != "https":
        raise ValueError("Only HTTPS is permitted")

    # 2. Domain normalization & allowlist check
    domain = parsed.hostname.lower()
    if domain not in ALLOWED_DOMAINS:
        raise ValueError(f"Domain {domain} not in allowlist")

    # 3. DNS resolution & IP validation (prevents DNS rebinding)
    try:
        resolved_ips = socket.getaddrinfo(domain, 443, socket.AF_INET, socket.SOCK_STREAM)
        resolved_ip = ipaddress.ip_address(resolved_ips[0][4][0])
    except (socket.gaierror, ValueError) as e:
        raise RuntimeError(f"DNS resolution failed: {e}")

    if not any(resolved_ip in cidr for cidr in ALLOWED_CIDRS):
        raise ValueError(f"Resolved IP {resolved_ip} not in allowed CIDRs")

    # 4. Construct safe request parameters
    safe_url = f"https://{resolved_ip}{parsed.path}"
    headers = {"Host": domain}

    return safe_url, headers

Security Boundary: The application must never pass the original user-supplied URL directly to requests.get() or httpx. Always use the resolved IP with a sanitized Host header.

3. Metadata Service Hardening & IMDSv2 Enforcement

Cloud providers expose metadata endpoints via link-local routing. Hardening requires disabling legacy IMDSv1, enforcing token-based authentication, and restricting network hop counts to prevent container or VM escape.

AWS IMDSv2 Enforcement

Instance metadata service v2 requires a PUT request to retrieve a session token before any GET request. Enforce this at the infrastructure layer.

Terraform Implementation (AWS, Azure, GCP)

# AWS EC2: Enforce IMDSv2, disable fallback, restrict hops
resource "aws_instance" "secure_app" {
 ami = "ami-0c55b159cbfafe1f0"
 instance_type = "t3.medium"

 metadata_options {
 http_endpoint = "enabled"
 http_tokens = "required" # Disables IMDSv1
 http_put_response_hop_limit = 1 # Prevents container escape
 instance_metadata_tags = "disabled"
 }
}

# Azure VM: Disable IMDSv1 equivalent, enforce managed identity
resource "azurerm_linux_virtual_machine" "secure_vm" {
 name = "secure-vm"
 resource_group_name = azurerm_resource_group.main.name
 location = azurerm_resource_group.main.location
 size = "Standard_B2s"
 admin_username = "azureuser"
 
 identity {
 type = "SystemAssigned"
 }

 os_profile {
 computer_name = "secure-vm"
 admin_password = "REPLACE_WITH_KEY_VAULT_REF"
 }
}

# GCP Compute: Restrict metadata server access
resource "google_compute_instance" "secure_instance" {
 name = "secure-instance"
 machine_type = "e2-medium"
 zone = "us-central1-a"

 metadata = {
 enable-oslogin = "TRUE"
 }

 # Block legacy metadata queries via firewall rules (not instance-level)
 # Use service accounts with minimal IAM scopes
 service_account {
 email = google_service_account.app.email
 scopes = ["https://www.googleapis.com/auth/cloud-platform"]
 }
}

Compliance Mapping: CIS AWS Foundations Benchmark v1.5.0 requires http_tokens = "required". PCI-DSS 4.0 Requirement 1.3.1 mandates network segmentation and least-privilege access, enforced here via hop limits and scoped IAM roles.

4. CI/CD Integration & Automated Compliance Checks

Shift-left enforcement prevents metadata exposure before deployment. Integrate static analysis (SAST) to flag unvalidated HTTP clients, and deploy policy-as-code to block insecure infrastructure provisioning.

OPA/Rego Policy for IaC Scanning

This Rego policy blocks Terraform plans that enable IMDSv1 or expose metadata endpoints without token enforcement.

package terraform.metadata_security

import rego.v1

deny[msg] {
    resource := input.plan.resource_changes[_]
    resource.type == "aws_instance"
    resource.change.after.metadata_options.http_tokens != "required"
    msg := sprintf("AWS Instance '%s' does not enforce IMDSv2. Set http_tokens = 'required'.", [resource.address])
}

deny[msg] {
    resource := input.plan.resource_changes[_]
    resource.type == "aws_instance"
    resource.change.after.metadata_options.http_put_response_hop_limit > 1
    msg := sprintf("AWS Instance '%s' hop_limit > 1. Restrict to 1 to prevent SSRF pivot.", [resource.address])
}

Pipeline Integration Steps

  1. Pre-commit: Run tfsec or checkov with custom Rego policies. Fail on CRITICAL metadata misconfigurations.
  2. SAST Scan: Configure SonarQube or Semgrep rules to detect requests.get(user_input) without preceding validation middleware.
  3. Dynamic Validation: Deploy a canary container that attempts to reach 169.254.169.254 during integration tests. Assert HTTP 401/403 or connection refusal.
  4. Policy Enforcement Gate: Integrate opa eval into the CI pipeline. Reject PRs if deny rules trigger.

5. Edge-Case Handling & Runtime Mitigation

Strict allowlists and IaC policies cover baseline scenarios, but advanced bypass techniques require runtime defenses and egress controls.

Advanced Bypass Vectors

  • Open Redirect Chaining: Attacker-controlled domains redirect to metadata endpoints after initial validation. Mitigation: Disable automatic redirect following (allow_redirects=False) or validate the final URL against the allowlist after each 3xx response.
  • WebSocket SSRF: ws:// or wss:// protocols bypass HTTP allowlists. Mitigation: Explicitly block ws/wss schemes unless explicitly required, and enforce origin validation.
  • gRPC/HTTP/2 Multiplexing: HTTP/2 connection reuse can bypass per-request validation. Mitigation: Use connection-per-request pools or validate the underlying socket IP before multiplexing.

Runtime Egress Architecture

Deploy an egress proxy (e.g., Envoy, Squid) with TLS termination and outbound allowlisting. Configure the application to route all external traffic through the proxy. The proxy enforces:

  • Strict domain allowlists at the network layer
  • Certificate pinning for critical endpoints
  • Request rate limiting and anomaly detection

Implement Runtime Application Self-Protection (RASP) hooks to intercept outbound socket connections. Log all connection attempts, including resolved IPs, request paths, and response codes. For compliance, retain audit logs for 12 months with immutable storage (WORM) to satisfy SOC 2 CC7.2 and ISO 27001 A.12.4.

Fallback Behavior

When validation fails, return a generic 400 Bad Request or 403 Forbidden. Never expose internal DNS resolution errors, IP addresses, or stack traces. Implement circuit breakers to prevent resource exhaustion from malicious validation loops.

Common Implementation Mistakes

  1. Relying on regex denylists instead of strict domain/IP allowlists: Denylists cannot enumerate all malicious encodings, IPv6 variants, or newly allocated cloud IPs.
  2. Failing to validate resolved IPs after DNS lookup: DNS rebinding attacks swap the resolved IP post-validation. Always validate the IP immediately before socket connection.
  3. Ignoring IPv6 link-local and IPv4-mapped IPv6 address bypasses: ::ffff:169.254.169.254 and fe80::/10 bypass IPv4-only filters. Normalize all addresses to canonical form before comparison.
  4. Deploying IMDSv2 without disabling fallback to IMDSv1: If http_tokens = "optional", legacy clients bypass token requirements. Enforce required at the infrastructure level.
  5. Hardcoding allowlists without environment-aware configuration management: Static allowlists break across staging/production. Use environment variables, secret managers, or dynamic configuration services with cryptographic signing.

FAQ

Why are denylists insufficient for SSRF mitigation against metadata services? Denylists fail due to encoding bypasses (%2e%2e/), DNS rebinding, IPv6/IPv4 translation (::ffff:169.254.169.254), and dynamic cloud IP allocation. Attackers can easily rotate domains or use newly provisioned internal CIDRs. Allowlists enforce explicit trust boundaries, reducing the attack surface to known, audited endpoints.

How do I prevent DNS rebinding when validating SSRF allowlists? Resolve the domain to an IP address, verify the IP against your allowlist, and then initiate the HTTP request using the resolved IP directly while preserving the original Host header. Alternatively, use a secure HTTP client that binds to the resolved IP and validates the final connection IP before transmitting the request payload.

What compliance frameworks mandate metadata service protection? SOC 2 CC6.1 and CC7.2 require network segmentation and monitoring of credential access. ISO 27001 A.13.1.1 mandates network controls to prevent unauthorized access. PCI-DSS 4.0 Requirement 1.3.1 and 2.2.7 explicitly require least-privilege IAM and metadata endpoint hardening to prevent credential exfiltration. CIS Benchmarks provide technical baselines for IMDSv2 enforcement across AWS, Azure, and GCP.