Server-Side Request Forgery (SSRF) Detection — ShipSafe

ShipSafe ships 67 SSRF rules that detect user-controlled URLs in fetch, axios, http.get, got, needle, and request/superagent calls. It understands the specific risks of cloud environments — blocking access patterns for AWS IMDSv1 (169.254.169.254), GCP metadata (metadata.google.internal), and Azure IMDS (169.254.169.254 with Metadata: true header).

67 detection rulesLocal-only scanning

What is SSRF?

Server-Side Request Forgery (SSRF) occurs when an attacker can make the server send HTTP requests to arbitrary destinations. This can expose internal services, cloud metadata endpoints (like AWS IMDSv1 at 169.254.169.254), and private network resources that should not be accessible from the internet.

Why It Matters

SSRF is the number one way attackers escalate from a web vulnerability to full cloud account compromise. On AWS, a successful SSRF against the IMDSv1 metadata endpoint returns temporary IAM credentials — giving the attacker the same permissions as the EC2 instance role. The 2019 Capital One breach (100 million customer records) was caused by SSRF to the metadata endpoint. Even without cloud metadata, SSRF lets attackers scan internal networks, access administrative interfaces (Redis, Elasticsearch, Kubernetes API), and reach services that are only supposed to be accessible from within the VPC.

What ShipSafe Detects

Example: Vulnerable Code

Vulnerable URL preview endpoint with SSRF

// Vulnerable: user-controlled URL in server-side fetch
app.post("/preview", async (req, res) => {
  const { url } = req.body;
  const response = await fetch(url);
  const html = await response.text();
  res.json({ preview: html.slice(0, 500) });
});

// An attacker sends url: "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
// The server fetches its own AWS IAM credentials and returns them to the attacker.

ShipSafe Catches It

$ shipsafe scan

  CRITICAL  ssrf/user-controlled-url
  src/routes/preview.ts:3
  User input from req.body controls the URL in a server-side HTTP request.
  Fix: Validate URL against an allowlist of permitted domains. Block private IPs and cloud metadata endpoints.

What to Do Instead

Safe alternative: domain allowlist, private IP blocking, and redirect prevention

// SAFE: URL validation with domain allowlist and private IP blocking
import { URL } from "url";
import dns from "dns/promises";

const ALLOWED_PROTOCOLS = ["http:", "https:"];
const BLOCKED_HOSTS = ["169.254.169.254", "metadata.google.internal"];

async function isPrivateIP(hostname: string): Promise<boolean> {
  const { address } = await dns.lookup(hostname);
  return /^(127\.|10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|169\.254\.)/.test(address)
    || address === "::1" || address === "0.0.0.0";
}

app.post("/preview", async (req, res) => {
  const { url: rawUrl } = req.body;

  // Parse and validate the URL structure
  let parsed: URL;
  try { parsed = new URL(rawUrl); }
  catch { return res.status(400).json({ error: "Invalid URL" }); }

  // Enforce protocol allowlist
  if (!ALLOWED_PROTOCOLS.includes(parsed.protocol)) {
    return res.status(400).json({ error: "Only HTTP(S) allowed" });
  }

  // Block known metadata endpoints and private IPs
  if (BLOCKED_HOSTS.includes(parsed.hostname) || await isPrivateIP(parsed.hostname)) {
    return res.status(403).json({ error: "Blocked destination" });
  }

  // Fetch with redirect disabled to prevent redirect-based SSRF
  const response = await fetch(rawUrl, { redirect: "error" });
  const html = await response.text();
  res.json({ preview: html.slice(0, 500) });
});

Frequently Asked Questions

What is the AWS metadata endpoint?

AWS EC2 instances expose a metadata service at http://169.254.169.254 that returns information about the instance, including IAM role credentials. IMDSv1 has no authentication — any process that can make HTTP requests from the instance can read these credentials. IMDSv2 requires a session token obtained via a PUT request, which mitigates SSRF because most SSRF vectors only support GET requests.

Does ShipSafe detect SSRF in Next.js Server Actions?

Yes. ShipSafe traces data flow from form inputs through Server Actions to fetch/axios calls, flagging any path where user-controlled data ends up in a URL without validation.

How does DNS rebinding bypass SSRF protections?

DNS rebinding exploits the gap between URL validation and the actual HTTP request. An attacker's domain initially resolves to a safe public IP (passing validation), then quickly changes its DNS record to 127.0.0.1. When the server makes the request, it connects to localhost instead. ShipSafe flags code that validates a URL then uses it in a separate fetch call, recommending that DNS resolution and request happen atomically.

Does ShipSafe detect redirect-based SSRF?

Yes. ShipSafe flags server-side HTTP requests that follow redirects by default. An attacker can host a URL that returns a 302 redirect to http://169.254.169.254, bypassing domain allowlists. The fix is to set redirect: 'error' or redirect: 'manual' in fetch options.

Detect SSRF in Your Code

Install ShipSafe and scan your project in under 60 seconds.

npm install -g @shipsafe/cli

Related Security Categories