Strobesstrobes
Platform
Solutions
Resources
Customers
Company
Pricing
Book a Demo
Strobesstrobes

Strobes connects every exposure signal to autonomous action, so security teams fix what matters, prove what works, and stop chasing noise.

Book a DemoTalk to an expert
ISO 27001SOC 2CREST
  • Platform
  • Platform Overview
  • Agentic Exposure Management
  • AI Agents
  • Integrations
  • API & Developers
  • Workflows & Automation
  • Analytics & Reporting
  • Solutions
  • Exposure Assessment (EAP)
  • Attack Surface Management
  • Application Security Posture
  • Risk-Based Vulnerability Management
  • Adversarial Exposure Validation (AEV)
  • AI Pentesting
  • Pentesting as a Service
  • CTEM Framework
  • By Industry
  • Financial Institutions
  • Technology
  • Retail
  • Healthcare
  • Manufacturing
  • By Roles
  • CISOs
  • Security Directors
  • Cloud Security Leaders
  • App Sec Leaders
  • Resources
  • Quick Agentic Pentest
  • Blog
  • Customer Stories
  • eBooks
  • Datasheets
  • Videos & Demos
  • Exposure Management Academy
  • CTEM Maturity Assessment
  • Pentest Health Check
  • Security Tool ROI Calculator
  • Company
  • About Strobes
  • Meet the Team
  • Trust & Security
  • Contact Us
  • Careers
  • Become a Partner
  • Technology Partner
  • Partner Deal Registration
  • Press Release

Weekly insight for security leaders

CTEM research, agentic AI trends, and what's actually moving the needle.

© 2026 Strobes Security Inc. All rights reserved.

Privacy PolicyTerms of ServiceCookie PolicyAccessibilitySitemap
Back to Blog
CRLF Injection: How It Works and How to Test for It
Application Security

CRLF Injection: How It Works and How to Test for It

Akhil ReniFebruary 24, 20256 min read

Table of Contents

  • What is CRLF injection?
  • Two bytes turn a value into a forged header.
  • What does a confirmed response-splitting exploit look like?
  • How do you test for CRLF injection?
  • What can an attacker do with CRLF injection?
  • Modern stacks strip newlines, which is exactly why the misses are sneaky.
  • How do you prevent CRLF injection?
  • Frequently asked questions
  • Sources and references

Authors

A
Akhil Reni

Share

Table of Contents

  • What is CRLF injection?
  • Two bytes turn a value into a forged header.
  • What does a confirmed response-splitting exploit look like?
  • How do you test for CRLF injection?
  • What can an attacker do with CRLF injection?
  • Modern stacks strip newlines, which is exactly why the misses are sneaky.
  • How do you prevent CRLF injection?
  • Frequently asked questions
  • Sources and references

Authors

A
Akhil Reni

Share

TL;DR
  • ✓CRLF injection abuses the carriage return (%0d) and line feed (%0a) bytes that HTTP uses to separate headers, letting you forge new headers or split the response.
  • ✓If user input reaches a response header (a redirect Location, a Set-Cookie value) without newline filtering, %0d%0a lets you inject headers and %0d%0a%0d%0a lets you inject a whole body.
  • ✓It enables HTTP response splitting, web cache poisoning, reflected XSS through the header channel, session fixation, and log forging.
  • ✓CRLF injection is tested under WSTG-INPV-15 (HTTP Splitting and Smuggling) and maps to A03:2021 Injection in the OWASP Top 10.
  • ✓The fix is one rule applied everywhere a header is built: strip or reject CR and LF, and use framework header APIs that refuse raw newlines.

Two bytes. That is all CRLF injection needs: a carriage return and a line feed, %0d%0a, the exact pair HTTP uses to end one header and start the next. Slip them into a value the server writes into a response header and you stop being a user supplying data; you start being the one deciding where headers end. The surprising part is why this still appears in 2026: header values are usually assembled before any output-encoding layer runs, so a string that is perfectly HTML-encoded for the body is wide open in a header.

This post explains the mechanics, walks a confirmed response-splitting exploit byte for byte, shows the cookie-injection and cache-poisoning escalations, and ends on the one-line fix. CRLF injection is tested under WSTG-INPV-15 and maps to A03:2021 Injection.

Table of contents
  1. What is CRLF injection?
  2. Two bytes turn a value into a forged header.
  3. What does a confirmed response-splitting exploit look like?
  4. How do you test for CRLF injection?
  5. What can an attacker do with CRLF injection?
  6. Modern stacks strip newlines, which is exactly why the misses are sneaky.
  7. How do you prevent CRLF injection?

What is CRLF injection?

CRLF injection inserts carriage-return (CR, %0d, \r) and line-feed (LF, %0a, \n) characters into application output that becomes part of an HTTP response, breaking the intended structure of the message. HTTP uses \r\n to end each header line and a doubled \r\n\r\n to mark the end of headers and the start of the body, so injecting those bytes lets you forge structure the server never intended.

The vulnerable pattern is any place user input lands in a response header: a redirect reflecting a url parameter into Location, a language preference written into Set-Cookie, or a custom header echoing a request value. Reverse proxies and CDNs that copy request headers into responses are a frequently overlooked sink. CRLF injection is the gateway to header injection and response splitting, both tested in the input-validation portion of a web application pentesting checklist.

Two bytes turn a value into a forged header.

It works by smuggling encoded newlines into a value the server writes into a header, so your text after the newline becomes a new header line. Take an endpoint that redirects using a parameter it reflects into Location. Supplying an encoded CRLF appends a header of your choosing, and the raw response confirms it:

GET /redirect?url=/home%0d%0aSet-Cookie:%20sessionid=attacker HTTP/1.1
Host: app.target.tld

HTTP/1.1 302 Found
Location: /home
Set-Cookie: sessionid=attacker          <-- forged header, injected by you
Content-Length: 0

That injected Set-Cookie appearing as its own line in the raw response is the confirmation. The reason output encoding does not save you here is that the value was newline-stripped for the body, not for the header, and those are different channels. Pinning a known session ID into the victim's browser this way is session fixation; the same primitive scaled up becomes response splitting.

Strobes insight
CRLF injection is the bug that resurrects XSS after you've encoded the body. If a header reflects user input unfiltered, a doubled %0d%0a lets you write your own response body, encoding be damned.

What does a confirmed response-splitting exploit look like?

Doubling the CRLF closes the header block entirely and lets you write your own body, which is full HTTP response splitting, and the injected body is where XSS comes back from the dead. Here is the request and the raw response, with the injected second response and script body called out:

GET /redirect?url=%0d%0aContent-Length:%200%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>alert(document.domain)</script> HTTP/1.1
Host: app.target.tld

HTTP/1.1 302 Found
Location:
Content-Length: 0

HTTP/1.1 200 OK              <-- a second, fully attacker-controlled response
Content-Type: text/html

<script>alert(document.domain)</script>      <-- script body the encoder never touched

The script tag landing in a fresh body is the confirmation. On a recent assessment of a retail site, I chained this with the CDN sitting in front: the response on that path was cacheable, so the poisoned variant got served to every subsequent visitor of the URL, turning a self-only reflection into a stored, cross-user XSS and a far higher CVSS score. Always view the unrendered response in Burp Suite, never the browser, because the browser hides exactly the byte-level structure you are confirming.

How do you test for CRLF injection?

Find every parameter that influences a response header, inject encoded CRLF sequences, and read the raw response for a line you control. Watch redirect (Location), cookie, and any reflected custom headers, and test the double-encoded variant for filters that decode once.

# Core payloads (try each in every header-reflecting param)
%0d%0aSet-Cookie:%20test=crlf
%0d%0aX-Injected:%20yes
%0d%0a%0d%0a<script>alert(1)</script>        # splitting + body

# Double-encoded, for a filter that URL-decodes once
%250d%250aX-Injected:%20yes

# Fuzz header-reflecting params at scale
ffuf -w params.txt -u "https://target/redirect?FUZZ=%0d%0aX-Injected:yes"
# match on responses where 'X-Injected: yes' appears as a header line

If your injected header or body shows up in the raw response, it is confirmed. This is WSTG-INPV-15 and belongs in your penetration testing test cases as an explicit line item.

What can an attacker do with CRLF injection?

CRLF injection is rarely the end goal; it is a primitive that enables several higher-impact attacks depending on what you can inject. The severity ranges from log noise to full cross-user response control.

  • HTTP response splitting: inject a full second response, leading to web cache poisoning that serves your content to other users.
  • Reflected XSS via the header channel: if body output is encoded but headers are not, an injected body reintroduces script execution, as the exploit above shows.
  • Session fixation: inject a Set-Cookie to pin a victim's session ID to a value you know.
  • Log injection: inject newlines into logged values to forge entries or break the parsing your SOC relies on.
  • Open redirect chaining: combine header control with the redirect itself for convincing phishing.
Where to Hunt for CRLF Injection
Header sinks
  • ✓Redirect Location parameters
  • ✓Set-Cookie / preference values
  • ✓Reflected custom headers
  • ✓Host / X-Forwarded-* echoes at the proxy
Payload variants
  • ✓%0d%0a (standard)
  • ✓%250d%250a (double-encoded)
  • ✓%0d%0a%0d%0a + body (splitting)
  • ✓\r\n inside JSON/API values

Modern stacks strip newlines, which is exactly why the misses are sneaky.

Scanners detect the simple reflected-header case but miss the variants that matter: filters defeated by double encoding, input that reaches a header only after a second backend hop, and the escalation from header injection to cache poisoning. A tool sends %0d%0aX-Injected:yes, sees its own header echoed, and stops, even when the same sink lets you write a full body for XSS. The false positive to watch is a reflected header the framework later strips or the browser normalizes away; confirm by reading the raw bytes.

The false negatives are worse on modern stacks precisely because most HTTP libraries silently strip \r\n, so a naive payload returns clean and the scanner clears the endpoint, but a Unicode or container-decoded variant still slips through on a proxy or legacy gateway in the chain. Header-reflecting values buried in JSON or in X-Forwarded-* echoes sit outside a scanner's parameter model entirely. Fuzz every header-influencing parameter with both %0d%0a and %250d%250a, then check cacheability of any reflected response. Continuous coverage of injection classes like this is exactly what agentic pentesting is built to automate.

How do you prevent CRLF injection?

Prevent CRLF injection by removing or rejecting CR and LF from any value before it is written into an HTTP header, and by relying on framework APIs that do this for you. Most modern HTTP libraries already refuse header values with raw newlines: Java's HttpServletResponse.setHeader throws on CR/LF, Node's http module rejects them with ERR_INVALID_CHAR, and ASP.NET strips them by default. The bug therefore surfaces where developers build raw header strings by hand, run an older platform, or set headers at a reverse proxy or CDN edge that does its own parsing.

Concretely: strip or URL-encode \r and \n in redirect targets, cookie values, and any reflected header; validate redirect destinations against an allowlist of paths rather than reflecting raw input; mark cache keys so a poisoned variant cannot be served to others; and never log unsanitized request data on the same line as structured fields. As with every injection class, the root fix is the same principle from the OWASP Top 10: keep data and protocol structure strictly separate, and never let a client-supplied byte decide where one header ends and the next begins.

Sample CRLF Findings Excerpt
FindingSeverity (CVSS)EvidenceRemediation
Response splitting -> cached XSS (WSTG-INPV-15, A03)8.1 High%0d%0a%0d%0a injected a script body; CDN cached it cross-userStrip CR/LF in Location; exclude tampered responses from cache
Set-Cookie injection / session fixation (WSTG-INPV-15)6.5 Medium%0d%0aSet-Cookie appeared as its own response headerReject newlines in cookie/header values; use framework APIs
Header injection via X-Forwarded-* echo5.3 MediumProxy copied a CRLF-laden request header into the responseSanitize at the edge; do not reflect raw forwarded headers
Log forging via newline in username (A09)4.3 MediumInjected \n forged a fake auth-success log lineEncode newlines before logging; use structured logging

Frequently asked questions

What is the difference between CRLF injection and HTTP response splitting?
CRLF injection is the underlying technique of inserting carriage-return and line-feed characters into HTTP output. HTTP response splitting is what you achieve when that injection lets you terminate the headers and write a full second response. Response splitting is the more severe outcome of a CRLF flaw.
What characters cause CRLF injection?
The carriage return (CR, \r, encoded %0d) and line feed (LF, \n, encoded %0a). HTTP uses the pair \r\n to separate headers and \r\n\r\n to end the header section, so injecting these characters into a response lets an attacker forge headers or an entire body.
Which OWASP category does CRLF injection belong to?
CRLF injection falls under A03:2021 Injection in the OWASP Top 10. In the Web Security Testing Guide it is covered by WSTG-INPV-15, Testing for HTTP Splitting and Smuggling, alongside related request and response manipulation attacks.
Can CRLF injection lead to XSS?
Yes. If body output is properly encoded but response headers reflect user input without filtering newlines, a doubled %0d%0a lets you inject an entire response body containing a script tag. That reintroduces reflected XSS through the header channel even when normal output encoding is in place, and a caching layer can make it cross-user.
Why does double-encoding (%250d%250a) matter when testing?
Some filters URL-decode the input once before checking it, so a single %0d%0a is caught but a double-encoded %250d%250a passes the filter and is decoded again downstream into a real CRLF. Testing both forms catches sinks that a single-pass payload would clear as safe.
How do you prevent CRLF injection?
Strip or reject CR and LF in any value written to an HTTP header, validate redirect targets against an allowlist, and use modern HTTP framework APIs that refuse header values containing raw newlines. Treat user input as data, never as protocol structure.

Sources and references

  • OWASP CRLF Injection
  • OWASP WSTG: Testing for HTTP Splitting/Smuggling (WSTG-INPV-15)
  • PortSwigger: HTTP response header injection
A
Akhil Reni
Co-founder and CTO, Strobes
Akhil Reni is co-founder and CTO of Strobes, building AI-driven penetration testing and exposure management for security teams.
Tags
Web SecurityCRLF InjectionOWASP

Stop chasing vulnerabilities Start reducing exposure

See how Strobes AI agents validate and fix your most critical exposures automatically.

Book a Demo
Continue Reading

Related Posts

Vulnerability validation: why most of your scanner backlog is noise - Strobes
Exposure ValidationApplication Security

Vulnerability Validation: Why Most of Your Scanner Backlog Is Noise

Vulnerability validation proves which scanner findings are real, reachable, and exploitable. Why manual triage fails and how agentic validation scales.

Jun 9, 202619 min
How to pentest single-page applications - React, Angular and Vue SPA security testing guide
Penetration TestingApplication Security

How to Pentest Single-Page Applications (React, Angular, Vue)

Learn how to pentest React, Angular, and Vue SPAs. Covers DOM XSS, client-side routing bypass, JS bundle secrets, and why traditional DAST scanners fail.

Jun 4, 202623 min
Bug bounty vs pentesting vs AI pentesting comparison featured image
Penetration TestingApplication Security

Bug Bounty vs. Pentesting vs. AI Pentesting: Which Model Fits Your AppSec Program?

Bug bounty vs pentesting vs AI pentesting: compare costs, coverage, compliance, and when to use each model. Build a layered AppSec testing strategy.

Jun 4, 202621 min