Your API returns a 302 to a redirect_uri the client supplied. The value is URL-decoded before placement in the Location header. That omission gives an attacker control over every header that follows: Set-Cookie, Cache-Control, X-Frame-Options. Every CDN node caching the response delivers the attacker's structure to every client.
CRLF injection in API responses is not an XSS variant. When a backend writes user-controlled values into Location or Content-Disposition without stripping \r\n, the injected structure propagates through every intermediary. The 2024 CVE wave in API client libraries proves the attack surface has moved upstream into the request chain itself.
CRLF Breaks HTTP/1.1 at the Protocol Level
CRLF injection is a parser-state exploit. HTTP/1.1 (RFC 7230) uses \r\n as the sole line delimiter between headers. A value like https://example.com/cb\r\nX-Injected: evil produces two syntactically valid headers, and every HTTP/1.1 intermediary processes both as real structure.
CVE-2023-38709 documents how Apache httpd through 2.4.58 failed to sanitize CRLF from backend-generated headers. The fix arrived in 2.4.59. CVE-2024-42516 confirmed the initial patch was partial: two rounds were required to close the vector in httpd.
CWE-113 classifies the problem as sequence neutralization, not input validation. WAFs oriented toward validation fail systematically against this vector because they operate at the wrong layer.
APIs Expose More Injectable Surfaces Than HTML Pages
API responses reflect user input into headers across 3 surfaces that security teams rarely audit: redirect handlers, file download endpoints, and echo correlation headers.
The Location header appears in OAuth flows where redirect_uri is placed verbatim after URL-decode. Content-Disposition: filename= in file download APIs exposes the same vector: the user supplies the filename, and the backend constructs the header. A CRLF in the filename splits it into arbitrary fields.
Go stdlib issue #75557 (mime/multipart) remains open in 2026: escapeQuotes() escapes quotes and backslashes but leaves \r\n intact in Content-Disposition. Headers like X-Request-ID and X-Correlation-ID reflect client-supplied IDs without validation in many API gateways. HackerOne #413115 confirmed CRLF via URL parameter reflected into the Location header in a production 8x8 SaaS API.
RestSharp and Refit: The Server-to-Server Vector
CVE-2024-45302 affects RestSharp versions 107 through 111. The methods AddHeader(), AddOrUpdateHeader(), and AddDefaultHeader() use .NET's TryAddWithoutValidation, which skips CRLF validation. A service that appends a user token to outbound call headers propagates any embedded \r\n directly to the target server.
// Vulnerable: RestSharp <= 111.x
var client = new RestClient("https://api.partner.com");
var request = new RestRequest("/resource");
request.AddHeader("X-User-Token", userSuppliedToken); // no CRLF validation
The fix shipped in RestSharp 112.0.0. CVE-2024-51501 affects Refit in all versions before 8.0.0. The [Header], [HeaderCollection], and [Authorize] attributes pass bearer tokens directly into outbound requests without sanitization.
// Vulnerable: Refit < 8.0.0
[Get("/resource")]
Task<Response> GetResource([Authorize] string bearerToken);
// bearerToken with \r\n splits the outbound HTTP request
Both CVEs confirm the vector is not limited to user-facing responses. When your backend calls another backend and passes user-controlled data in headers, the injection happens server-to-server, outside any edge WAF's field of view.
HTTP/2 at the Edge Does Not Protect Your HTTP/1.1 Backend
The most common dismissal: "we use HTTP/2, so CRLF does not apply." HTTP/2 uses binary framing and rejects CR/LF in field values (RFC 9113 Section 8.2.1). That protection ends at the reverse proxy.
Nginx forwards to upstream over HTTP/1.1 by default. The $uri directive in return URL-decodes first, converting %0d%0a into literal CRLF before it reaches the backend. PortSwigger's CRLF-Powered Desync research documents Response Queue Poisoning inside CDN infrastructure, harvesting responses across unrelated tenants.
PortSwigger explicitly recommends HTTP/2 upstream, edge to origin, as mitigation. That confirms HTTP/2 at the edge alone does not close the gap.
CVE-2024-37404 (Ivanti Connect Secure through 22.7R2) shows where CRLF travels beyond HTTP. A certificate generation API parameter injected new OpenSSL config directives via CRLF, producing authenticated root RCE.
CVE-2024-52875: From CRLF to RCE Across 23,800 Instances
GFI KerioControl versions 9.2.5 through 9.4.5 exposed the dest GET parameter at /nonauth/addCertException.cs without sanitization. The value went directly into the Location header of the 302 response. The full chain: CRLF in dest creates response splitting, which injects XSS, steals the CSRF token, and allows malicious firmware upload. The firmware upload executes a root shell.
GreyNoise confirmed active exploitation from December 28, 2024, with 7 IPs from Singapore and Hong Kong. Censys counted over 23,800 internet-exposed instances. An injection rated "medium" in isolation produced complete corporate network compromise.
Detection and Version Exposure
Manual detection is straightforward: append %0d%0aX-Injected:1 to any parameter that ends up in a response header (redirect_uri, filename=, correlation ID). If X-Injected appears in the response headers, the endpoint is vulnerable. Bypass variants include %0a (LF-only, sufficient for many parsers), double-encode %250d%250a, and Unicode %E5%98%8A%E5%98%8D for some decoders.
The MAGO Intel tool (intel.mago.team) identifies service version exposure across API infrastructure. It flags Apache httpd versions below 2.4.59 and library fingerprints matching vulnerable RestSharp and Refit ranges before active exploitation.
The Fix Happens at Header Construction
WAF rules for CRLF are bypassed by encoding variants. The only reliable fix is stripping \r and \n at header construction time using language-native sanitization, not regex.
def strip_crlf(value: str) -> str:
return value.replace('\r', '').replace('\n', '')
Go's Header.Set() sanitizes since version 1.7. Go's mime/multipart.Writer does not: it is a separate code path with open bug #75557. PHP's header() rejects CRLF since 5.1.2. Node.js's http module was patched in 2021; undici (fetch) had a CVE fixed in 2022 via HackerOne #1878489. In FastAPI and Starlette, behavior defers to the underlying ASGI: verify per framework, do not assume.
Never pass raw user input to header values. Strip before any header.set() call. Treat the presence of \r or \n as a reason to return 400, not as data to sanitize silently. Add CWE-113 payloads as a CI gate on every endpoint that constructs headers dynamically.
A CRLF injection rated "medium" in isolation becomes CDN-wide cache poison or session fixation at scale. The test takes 30 seconds: append %0d%0aX-Injected:1 to any parameter that ends up in a response header and inspect. If the header appears, every user sharing that CDN cache path is in scope, including the ones who never open a browser.
Top comments (0)