Crlf Injection in Koa (Javascript)
Crlf Injection in Koa with Javascript — how this specific combination creates or exposes the vulnerability
Crlf Injection occurs when an attacker can inject a Carriage Return (CR, \r) and Line Feed (LF, \n) sequence into a header or status-line context. In Koa built with JavaScript, this typically arises when user-controlled data is placed into HTTP response headers, the status code line, or cookies without proper sanitization. Because HTTP headers are separated by \r\n, an injected sequence can terminate the intended header early and append a new header or smuggled content. Koa does not inherently sanitize header values, so if your route or middleware directly uses req.query, req.body, or other external inputs in res.set(), res.header(), res.cookie(), or res.status(), you risk header injection.
For example, if you dynamically set a header like X-Request-ID from a query parameter without validation, an attacker can supply a value containing %0D%0A (or literal \r\n when decoded) to inject additional headers. In JavaScript string handling, constructing headers by concatenation or template literals can produce raw CR/LF characters if input is not normalized. The same applies to status code assignment: res.status(Number(userStatus)) is safe, but res.status(userStatus) where userStatus is a string can allow sequences like 200\r\nX-Injected: value to corrupt the status line. Similarly, cookie values that include newline characters can split Set-Cookie headers, enabling session fixation or response splitting.
In a Koa application, a typical vulnerable pattern looks like:
// Vulnerable Koa route in JavaScript
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx) => {
const customId = ctx.query.id; // user-controlled
ctx.set('X-Custom-Id', customId); // no validation
ctx.cookies.set('tracking', ctx.query.tracking); // user-controlled cookie
ctx.body = 'OK';
});
app.listen(3000);
If an attacker sends a request with ?id=foo\r\nX-Attacker: injected&tracking%0D%0ASet-Cookie:%20evil%3D1, the injected CR/LF can smuggle an additional header or cookie, depending on how the framework parses the output. Because Koa relies on Node.js HTTP primitives, the underlying server will process the raw header lines; if Koa’s abstractions do not reject or encode \r\n, the injected content can be interpreted as separate headers. This can lead to response splitting, cache poisoning, or client-side manipulation. The risk is higher when responses are cached or when security controls rely on header integrity.
Another scenario involves status smuggling via res.status(). In JavaScript, if the status is derived from user input without type coercion or range checks, an input like 200\r\nX-Smuggled: true can cause the status line to be parsed incorrectly by intermediaries. While Node.js may throw or coerce in some cases, relying on defensive validation is essential. Input validation libraries in JavaScript, such as validator.js or custom checks, should enforce strict type and format rules before using data in headers or cookies.
Javascript-Specific Remediation in Koa — concrete code fixes
To remediate Crlf Injection in Koa with JavaScript, ensure all user-controlled data that reaches headers, status codes, or cookies is validated, sanitized, and encoded. The safest approach is to avoid direct interpolation of external input into these contexts. Use allowlists, strict type checks, and encoding functions to neutralize CR and LF characters.
Below are concrete, secure JavaScript code examples for a Koa application.
1) Validate and sanitize header values:
// Secure Koa route in JavaScript
const Koa = require('koa');
const app = new Koa();
function sanitizeHeader(value) {
if (typeof value !== 'string') return '';
// Remove CR and LF characters; reject inputs containing \r or \n
return value.replace(/[\r\n]+/g, '');
}
app.use(async (ctx) => {
const customId = sanitizeHeader(ctx.query.id);
ctx.set('X-Custom-Id', customId || 'default');
ctx.body = 'OK';
});
app.listen(3000);
2) Validate cookie values and avoid newline injection:
// Secure cookie handling in JavaScript
const Koa = require('koa');
const app = new Koa();
function safeCookieValue(value) {
if (typeof value !== 'string') return '';
// Strip CR/LF and other control characters that may split headers
return value.replace(/[\r\n\t]/g, '');
}
app.use(async (ctx) =>
ctx.cookies.set('tracking', safeCookieValue(ctx.query.tracking), {
httpOnly: true,
sameSite: 'strict',
})
);
3) Ensure status codes are integers within the valid range:
// Secure status handling in JavaScript
const Koa = require('koa');
const app = new Koa();
function safeStatus(input) {
const num = Number(input);
return Number.isInteger(num) && num >= 100 && num <= 599 ? num : 200;
}
app.use(async (ctx) =>
ctx.status = safeStatus(ctx.query.statusCode)
);
4) Use a validation library for robust checks:
// Using validator.js for header-safe strings
const validator = require('validator');
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx) => {
const id = ctx.query.id;
const safeId = validator.escape(id).replace(/[\r\n]+/g, '');
ctx.set('X-Safe-Id', safeId);
ctx.body = 'OK';
});
5) Centralized middleware to reject dangerous input early:
// Middleware to block CR/LF in headers-relevant inputs
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
const hasCrlf = (str) => typeof str === 'string' && /[\r\n]/.test(str);
const suspectKeys = ['id', 'tracking', 'statusCode', 'redirectUrl'];
for (const key of suspectKeys) {
if (hasCrlf(ctx.query[key]) || hasCrlf(ctx.request.body[key])) {
ctx.status = 400;
ctx.body = 'Invalid input: CR/LF not allowed';
return;
}
}
await next();
});
// Continue with safe routes...
app.use(async (ctx) => {
ctx.set('X-Request-ID', ctx.query.id);
ctx.body = 'Processed';
});
These patterns emphasize strict type checks, removal or rejection of CR/LF characters, and integer validation for status codes. They align with secure coding practices for HTTP header handling in Node.js and help prevent response splitting and header smuggling in Koa applications.