Crlf Injection in Koa
How Crlf Injection Manifests in Koa
CRLF injection in Koa applications occurs when unvalidated user input is incorporated into HTTP headers, allowing attackers to inject carriage return and line feed characters ( ) to manipulate the protocol. Koa's middleware architecture and context object make certain patterns particularly vulnerable.
The most common Koa-specific vulnerability arises from improper handling of the ctx.set() method. When developers dynamically construct header names or values from user input without sanitization, attackers can inject additional headers or split responses:
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx) => {
// VULNERABLE: User input directly in header value
const filename = ctx.query.filename || 'default.txt';
ctx.set('Content-Disposition', `attachment; filename="${filename}"`);
ctx.body = 'This is a file';
});
An attacker could request /download?filename=foo.txt%0D%0A%0D%0AHTTP/1.1%20200%20OK%0D%0AContent-Type%3A%20text/plain%0D%0A%0D%0AHacked! to manipulate the response.
Another Koa-specific pattern involves the ctx.redirect() method. When redirect URLs are constructed from user input without validation, CRLF characters can be injected:
app.use(async (ctx) => {
// VULNERABLE: No URL validation
const redirect = ctx.query.next || '/';
ctx.redirect(redirect);
});
Attackers could redirect to URLs containing CRLF sequences to manipulate headers or initiate HTTP response splitting attacks.
Koa's body parsing middleware can also introduce CRLF vulnerabilities when handling multipart form data. The koa-body middleware processes file uploads and form fields, but improper configuration can allow CRLF injection in content headers:
const koaBody = require('koa-body');
app.use(koaBody({
multipart: true,
formidable: {
keepExtensions: true
}
}));
app.use(async (ctx) => {
// VULNERABLE: Directly using user-provided filename
const file = ctx.request.files.upload;
ctx.set('Content-Disposition', `attachment; filename="${file.name}"`);
ctx.body = file;
});
The ctx.cookies.set() method presents another attack vector. When cookie values contain CRLF characters, they can break the HTTP response structure:
app.use(async (ctx) => {
// VULNERABLE: No sanitization of cookie value
const userId = ctx.query.userId || 'guest';
ctx.cookies.set('user_id', userId);
ctx.body = 'Welcome!';
});
Koa-Specific Detection
Detecting CRLF injection in Koa applications requires both static analysis and dynamic testing. For static analysis, look for patterns where user input flows into header-related operations:
// Patterns to search for:
ctx.set(userInput, value);
ctx.set(header, userInput);
ctx.redirect(userInput);
ctx.cookies.set(name, userInput);
ctx.append(header, userInput);
Dynamic testing should include sending payloads with percent-encoded CRLF sequences (%0D%0A) to endpoints that handle headers, redirects, or file operations. Test with payloads like:
filename=test.txt%0D%0A%0D%0AHTTP/1.1%20200%20OK%0D%0AContent-Type%3A%20text/plain%0D%0A%0D%0AHacked!
next=%2F%3Ffoo%3Dbar%0D%0ASet-Cookie%3A%20hacked%3Dtrue
middleBrick's black-box scanning approach is particularly effective for Koa applications because it tests the actual runtime behavior without requiring source code access. The scanner automatically tests for CRLF injection across all 12 security categories, including header manipulation attempts.
When middleBrick scans a Koa API endpoint, it sends specialized payloads to detect CRLF injection vulnerabilities. The scanner checks for:
- Header injection through
ctx.set()operations - Response splitting via
ctx.redirect() - Cookie manipulation through
ctx.cookies.set() - Content-Disposition header manipulation
- Custom header injection
The scanner provides specific findings with severity levels and remediation guidance. For example, a typical finding might indicate that a specific endpoint is vulnerable to HTTP response splitting via the filename parameter, with the exact payload that triggered the vulnerability.
For Koa applications using TypeScript, additional detection considerations apply. The static type system doesn't prevent CRLF injection, so runtime validation remains essential:
app.use(async (ctx: Context) => {
const filename = ctx.query.filename as string;
// Type checking alone doesn't prevent CRLF injection
ctx.set('Content-Disposition', `attachment; filename="${filename}"`);
});
Koa-Specific Remediation
Remediating CRLF injection in Koa requires input validation and sanitization before header operations. The most effective approach combines whitelist validation with proper encoding.
For filename parameters in Content-Disposition headers, implement strict validation:
const { extname, basename } = require('path');
function sanitizeFilename(input) {
// Allow only alphanumeric, hyphens, underscores, and dots
if (!/^[a-zA-Z0-9._-]+$/.test(input)) {
throw new Error('Invalid filename');
}
// Prevent directory traversal
if (input.includes('/') || input.includes('\')) {
throw new Error('Invalid filename');
}
return input;
}
app.use(async (ctx) => {
try {
const filename = sanitizeFilename(ctx.query.filename || 'default.txt');
ctx.set('Content-Disposition', `attachment; filename="${filename}"`);
ctx.body = 'This is a file';
} catch (err) {
ctx.status = 400;
ctx.body = 'Invalid filename';
}
});
For redirect parameters, validate against a whitelist of allowed URLs or use a safe redirect helper:
function safeRedirect(ctx, url) {
// Validate URL against whitelist
const allowedRedirects = ['/dashboard', '/profile', '/settings'];
if (!allowedRedirects.includes(url)) {
ctx.throw(400, 'Invalid redirect URL');
return;
}
ctx.redirect(url);
}
app.use(async (ctx) => {
const redirect = ctx.query.next || '/';
safeRedirect(ctx, redirect);
});
For cookie values, implement strict sanitization:
function sanitizeCookieValue(value) {
// Remove any CRLF characters
return value.replace(/[
]/g, '');
}
app.use(async (ctx) => {
const userId = sanitizeCookieValue(ctx.query.userId || 'guest');
ctx.cookies.set('user_id', userId, {
httpOnly: true,
secure: true
});
ctx.body = 'Welcome!';
});
Koa's built-in context methods provide some protection, but don't eliminate the need for validation. The ctx.assert() method can help enforce input constraints:
app.use(async (ctx) => {
const filename = ctx.query.filename;
// Assert that filename contains no dangerous characters
ctx.assert(filename, 400, 'Filename required');
ctx.assert(!/[\r\n%]/.test(filename), 400, 'Invalid filename');
ctx.set('Content-Disposition', `attachment; filename="${filename}"`);
ctx.body = 'This is a file';
});
For applications using third-party middleware like koa-body, configure proper validation at the middleware level:
const koaBody = require('koa-body');
app.use(koaBody({
multipart: true,
formidable: {
onPart: (part) => {
// Validate header names and values
if (part.headers['content-disposition']) {
// Check for CRLF in content-disposition
if (/[\r\n]/.test(part.headers['content-disposition'])) {
part.resume(); // Skip this part
return;
}
}
part.read();
}
}
}));
middleBrick's remediation guidance for Koa applications includes specific code examples like these, mapped to the exact vulnerability detected. The scanner's findings help developers understand which endpoints and parameters are vulnerable, making targeted remediation straightforward.