Crlf Injection in Koa with Mutual Tls
Crlf Injection in Koa with Mutual Tls
Crlf Injection occurs when an attacker can inject CRLF sequences (%0D%0A or literal \r\n) into HTTP headers, causing header smuggling or response splitting. In Koa, this typically arises when user-controlled input is reflected in headers such as Location, Content-Type, or custom headers without proper sanitization. For example:
// Risky: user input directly set in a header
const userControlled = ctx.query.next || 'home';
ctx.set('Location', `/${userControlled}`); // vulnerable if userControlled contains \r\n
When Mutual Tls (mTLS) is enforced, the server requests and validates the client certificate during the TLS handshake. This means the server has additional identity information about the client (e.g., subject or certificate fields) and may incorporate certificate metadata into headers or decisions. While mTLS does not directly prevent Crlf Injection, it can change how headers are constructed or logged, and it may introduce new header sources derived from the certificate. If certificate-derived values or other trusted inputs are concatenated into headers without validation, the attack surface remains. For instance, a header derived from certificate fields could become:
// Hypothetical: using certificate subject in a header
const subject = getCertSubject(ctx); // might come from mTLS verification
ctx.set('X-User-Role', subject); // dangerous if subject contains \r\n
Even with mTLS transport security, the application layer remains responsible for sanitizing any data that reaches HTTP headers. Attackers can still exploit reflected inputs, query parameters, or improperly handled certificate attributes to inject CRLF sequences, leading to response splitting, cache poisoning, or request smuggling. The presence of mTLS may give a false sense of security, while the lack of output validation for headers allows the injection to persist. Therefore, Crlf Injection in a Koa app with mTLS hinges on the same root cause: untrusted data in trusted header contexts.
Mutual Tls-Specific Remediation in Koa
Remediation focuses on strict header validation and safe handling of any data used in HTTP headers, including data derived from mTLS client certificates. Always treat external and semi-trusted inputs as untrusted. Use allowlists, canonicalization, and explicit encoding for header values. Below are concrete Koa examples demonstrating secure practices.
1. Enforce mTLS in Koa (basic HTTPS server with request cert validation)
const fs = require('fs');
const https = require('https');
const Koa = require('koa');
const app = new Koa();
const server = https.createServer({
cert: fs.readFileSync('/path/to/server-cert.pem'),
key: fs.readFileSync('/path/to/server-key.pem'),
requestCert: true,
rejectUnauthorized: true, // enforce client cert validation
ca: [fs.readFileSync('/path/to/ca-cert.pem')]
}, app.callback());
server.listen(8443);
2. Sanitize header inputs — disallow CRLF in header values
// A safe header setter utility
function safeSetHeader(ctx, name, value) {
if (typeof value !== 'string') return;
// Reject if value contains CR or LF
if (/[\r\n]/.test(value)) {
ctx.throw(400, 'Invalid header value');
}
ctx.set(name, value);
}
// Usage in a route
app.use(async (ctx) => {
const nextPage = ctx.query.next || 'home';
// Validate before using in Location
if (/[\r\n]/.test(nextPage)) {
ctx.throw(400, 'Invalid redirect target');
}
safeSetHeader(ctx, 'Location', `/redirect/${nextPage}`);
ctx.status = 302;
});
3. Avoid using certificate metadata directly in headers
// Instead of passing certificate fields directly, map to allowed values
const allowedRoles = new Set(['admin', 'user', 'guest']);
function getCertSubject(ctx) {
// Example: extract from verified client cert
return ctx.clientCert && ctx.clientCert.subject && ctx.clientCert.subject.CN;
}
app.use(async (ctx) => {
const subject = getCertSubject(ctx);
if (!allowedRoles.has(subject)) {
ctx.throw(403, 'Unauthorized role');
}
// Use mapped role, not raw subject in headers
safeSetHeader(ctx, 'X-App-Role', subject);
});
4. Use framework middleware for validation
const validator = require('validator');
app.use(async (ctx) => {
const unsafe = ctx.request.header['x-custom'];
if (unsafe && !validator.isAlphanumeric(unsafe)) {
ctx.throw(400, 'Header contains invalid characters');
}
ctx.set('X-Safe', unsafe);
});
5. Logging and observability — avoid CRLF in logged headers
app.use(async (ctx, next) => {
await next();
const safeHeader = (header) => (header && header.replace(/[\r\n]/g, '')) || '';
console.log(`Response X-Request-ID: ${safeHeader(ctx.get('X-Request-ID'))}`);
});
By combining mTLS enforcement with disciplined header validation — rejecting or sanitizing CR/LF in any header value, including those derived from certificates — you mitigate Crlf Injection while preserving the transport security benefits of mutual TLS.
Frequently Asked Questions
Does mTLS alone prevent Crlf Injection in Koa?
What specific input should be validated to prevent Crlf Injection in Koa with mTLS?
\r) and LF (\n) characters, and prefer allowlists for expected values.