Crlf Injection in Feathersjs with Jwt Tokens
Crlf Injection in Feathersjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Crlf Injection occurs when an attacker can inject CRLF sequences (%0d%0a or \r\n) into HTTP headers or other delimiter-controlled contexts. In FeathersJS, a typical service route can inadvertently reflect attacker-controlled input into response headers, for example via an Authorization or custom header, especially when the framework is used with JWT token handling. JWT tokens are often passed in the Authorization: Bearer <token> header; if a FeathersJS app builds redirects, sets custom headers, or logs incoming values using data from the request without sanitization, a CRLF sequence in a token or a manipulated header can split the response and inject new headers or body content.
Consider a FeathersJS service that takes an Authorization header value and reflects it into a custom response header for debugging. If the JWT token contains a newline (e.g., crafted as header%0d%0aSet-Cookie:%20evil=1), the server may treat the injected sequence as a new header line. This can lead to response splitting, HTTP response smuggling, or the injection of arbitrary headers such as Set-Cookie or Location, which may enable session fixation or open redirects. The vulnerability is not in JWT verification itself, but in how FeathersJS routes or hooks handle and forward untrusted input that reaches headers, status codes, or redirect URLs.
FeathersJS often uses Express under the hood, and Express does not inherently sanitize inputs that reach res.location(), res.redirect(), or custom header assignments. If a developer passes an unsanitized value derived from a JWT claim (e.g., payload.origin) into these APIs, CRLF injection becomes feasible. For example, an attacker who can influence a JWT claim (perhaps via a weaker issuer or a token obtained through another vector) could supply a crafted string that terminates the current header line and appends malicious ones. This can bypass intended access controls, manipulate caching behavior, or corrupt the response stream, depending on how the application uses the tainted data.
Middleware that logs or echoes headers without validation also increases risk. If a FeathersJS hook logs the incoming Authorization header and that header contains a CRLF, log parsers may misinterpret entries, complicating monitoring and potentially enabling log injection attacks. Therefore, the combination of FeathersJS flexibility in routing and hooks, plus the presence of JWT tokens in headers, creates conditions where unsanitized input can corrupt protocol-level boundaries if developers do not explicitly validate and encode data before it touches headers or redirects.
Jwt Tokens-Specific Remediation in Feathersjs — concrete code fixes
Remediation focuses on strict input validation, avoiding direct use of untrusted data in headers and redirects, and using framework-safe patterns. Never pass raw values from JWT claims into res.location(), res.redirect(), or custom headers without thorough sanitization. Instead, use allowlists for expected values and treat all external input as untrusted.
Example of a vulnerable pattern in a FeathersJS hook that echoes a JWT claim into a header:
// Vulnerable: directly using a JWT claim in a header
app.hooks({
before: {
create: [context => {
const { location } = context.params.query; // attacker-controlled
// Unsafe: location may contain CRLF
context.result.headers = { 'X-Debug-Location': location };
return context;
}]
}
});
Safer approach: validate and sanitize before use. Use a strict allowlist for redirects or locations, and avoid reflecting untrusted input into headers.
// Safer: validate and use only safe values
const validLocations = ['dashboard', 'profile', 'settings'];
app.hooks({
before: {
create: [context => {
const requested = context.params.query.location;
if (!validLocations.includes(requested)) {
throw new Error('Invalid location');
}
// Safe: no user input in headers
context.result.headers = { 'X-Validated-Location': requested };
return context;
}]
}
});
When dealing with JWT tokens in the Authorization header, ensure your authentication layer verifies the token cryptographically and extracts claims safely without reusing raw header values for redirects. For example, using @feathersjs/authentication and @feathersjs/authentication-jwt:
// Secure: rely on the auth library to validate the token and expose a verified payload
const authentication = require('@feathersjs/authentication');
const jwt = require('@feaths/authentication-jwt');
app.configure(authentication({
secret: 'your-secret',
strategies: ['jwt']
}));
// In a hook, use the verified payload from context.params.user
app.hooks({
before: {
create: [context => {
const user = context.params.user; // verified JWT payload
// Use only trusted, application-controlled values for redirects/headers
context.result.headers = { 'X-User-ID': user.userId };
return context;
}]
}
});
If you must construct a redirect or location header, use a whitelist of allowed paths and encode any dynamic parts. Avoid concatenating strings that may contain CRLF. For example:
// Secure redirect using a controlled target map
const redirectMap = {
home: '/',
dashboard: '/dashboard',
settings: '/settings'
};
app.hooks({
before: {
create: [async context => {
const target = context.params.query.redirect;
if (!redirectMap[target]) {
throw new Error('Invalid redirect');
}
// Express will handle header safety; do not inject raw input
context.app.set('location', redirectMap[target]);
context.result = { redirect: redirectMap[target] };
return context;
}]
}
});
Additionally, ensure your JWT library is configured to reject tokens with unexpected characters in claims and that you set appropriate CORS and header policies to mitigate the impact of any residual injection vectors. Regularly update dependencies to benefit from security patches related to JWT parsing and header handling.