SSRF Server-Side Request Forgery Vulnerability and Protection

SSRF Server-Side Request Forgery Vulnerability and Protection

Problem Description
SSRF (Server-Side Request Forgery) is a security vulnerability where an attacker constructs a malicious request, tricking the server into making unintended network requests to internal or external systems. Unlike CSRF, which attacks the client, SSRF exploits the server's trust in user requests. Typical scenarios include: servers proxying user requests for resources without protection, failing to validate target addresses when parsing URLs, or accessing internal metadata services (such as cloud environment's 169.254.169.254).


Vulnerability Principle and Impact

  1. Attack Path:

    • User submits a URL (e.g., image loading, web scraping function) → Server directly requests that URL → Attacker forges an address pointing to the internal network or local services → Server proxies the request and returns the result.
      Example request:
    POST /api/download_image HTTP/1.1
    Content-Type: application/json
    {"url": "file:///etc/passwd"}  # Attempting to read server local files
    
  2. Core Hazards:

    • Internal Network Penetration: Access to firewall-protected internal systems (e.g., database management interfaces).
    • Sensitive Information Leakage: Obtain instance keys, configuration information via cloud metadata services.
    • Port Scanning: Probe internal network port openness by leveraging differences in server response times.

Vulnerability Reproduction and Exploitation Steps
Assume there exists an online tool that allows users to input a URL for the server to fetch a remote image:

  1. Normal Request:

    {"url": "https://example.com/image.jpg"}
    

    The server downloads the image and returns success.

  2. Probing Internal Network Services:
    The attacker attempts to access internal IP ranges:

    {"url": "http://192.168.1.1:8080/admin"}
    

    If the response contains HTML of a login page, it confirms the existence of an internal management backend.

  3. Exploiting Cloud Metadata Services (using AWS as an example):

    {"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"}
    

    The server will return temporary access keys, leading to cloud platform permission leakage.

  4. Protocol Abuse:
    Utilize the file:// protocol to read local files, or the dict:// protocol to interact with services like Redis:

    {"url": "file:///etc/passwd"}
    {"url": "dict://127.0.0.1:6379/info"}  # Get Redis information
    

Protection Solutions

  1. Input Validation and Filtering:

    • Whitelist validation for domains and protocols (only allow trusted domains like https://example.com).
    • Block access to internal IP ranges (e.g., 10.0.0.0/8) and loopback addresses (127.0.0.1).
  2. Network Layer Isolation:

    • Restrict server outbound traffic: prohibit access to cloud metadata IPs and internal network segments.
    • Use an intermediate proxy and set target address rules.
  3. Principle of Least Privilege:

    • The account running the service should have limited permissions to avoid reading system files with high privileges.
  4. Response Content Inspection:

    • After the server fetches remote content, validate the type of returned data (e.g., verify file headers for images) to avoid directly returning sensitive information.

Case Study: Protection Code Example (Node.js)

const url = require('url');
const allowedDomains = ['cdn.example.com']; // Whitelist domains

function validateURL(inputURL) {
  const parsed = url.parse(inputURL);
  // Protocol restriction: only allow HTTP/HTTPS
  if (!['http:', 'https:'].includes(parsed.protocol)) {
    throw new Error('Protocol not allowed');
  }
  // Domain whitelist validation
  if (!allowedDomains.includes(parsed.hostname)) {
    throw new Error('Domain not in whitelist');
  }
  // Block internal IP access
  const ip = parsed.hostname;
  if (isInternalIP(ip)) {
    throw new Error('Access to internal addresses prohibited');
  }
  return true;
}

function isInternalIP(ip) {
  const ranges = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'];
  return ranges.some(range => ipInRange(ip, range));
}