flawopen.com/Teardowns/cve-2022-31150-undici-crlf-injection-ssrf

● CVE-2022-31150 · CVSS 6.5 · Medium
FlawOpen Security Research

CVE-2022-31150: Node.js Undici CRLF Injection & SSRF Teardown

How unvetted carriage return characters in HTTP header values allowed attackers to split outgoing HTTP requests in Node.js globalThis.fetch().

💡 Plain English Explainer (ELI5)

Imagine you send a sealed postal letter through the mail that says: "Ship 10 boxes of apples". But you sneak in a fake second official page that says: "STOP. IGNORE PREVIOUS. TRANSFER ALL FUNDS TO EVIL CORP". The postal clerk reads the pages in order and treats the hidden second page as a brand-new official letter, redirecting your money straight to the attacker.

Core Concepts & Subsystem Terms

Undici
The official next-generation HTTP/1.1 client for Node.js, powering the global fetch() implementation.
CRLF Injection
Injecting Carriage Return (\r) and Line Feed (\n) characters into HTTP headers to create a new header or a whole new request.
HTTP Request Splitting
A vulnerability where an attacker splits a single outgoing HTTP connection into two separate requests.
SSRF (Server-Side Request Forgery)
Coercing a backend server to issue unauthorized requests to internal cloud metadata APIs (169.254.169.254).

Root Cause Analysis

The root cause stems from unvalidated boundary parameters in Open Source Systems, permitting state desynchronization and security controls bypass.

Step-by-Step Attack Flow

Step 1

1. Input Submission

An attacker supplies a crafted header value: Admin\r\nHost: 169.254.169.254.

Step 2

2. Application Issues Fetch

The Node.js backend calls fetch(url, { headers: { 'X-User': input } }).

Step 3

3. Header Splitting in Undici

Undici serializes the headers without sanitizing \r\n, injecting the forged Host header.

Step 4

4. Cloud Metadata Exfiltration

The internal proxy directs the request to the cloud metadata service, exposing AWS/GCP credentials.

Source Code: Flaw vs. Secure Implementation

UNPATCHED FLAW
// VULNERABLE: lib/core/request.js before patch
function addHeader(headers, key, value) {
    // ROOT CAUSE:
    // Does not sanitize or reject carriage return (\r) and line feed (\n) in values!
    // Allows attackers to split headers and inject arbitrary HTTP directives!
    headers[key] = value;
}
HARDENED SECURE PATCH
// SECURE: lib/core/request.js patch
function addHeader(headers, key, value) {
    // 1. Strict regex checking for dangerous control characters
    const INVALID_HEADER_CHAR_REGEX = /[\r\n]/;
    
    if (INVALID_HEADER_CHAR_REGEX.test(key) || INVALID_HEADER_CHAR_REGEX.test(value)) {
        throw new TypeError(`Invalid character in header content: ["${key}": "${value}"]`);
    }
    
    headers[key] = value;
}

Engineering & System Hardening Checklist

Sources