Crlf Injection in Restify with Hmac Signatures
Crlf Injection in Restify with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Crlf Injection occurs when an attacker can inject carriage return (CR, \r) and line feed (\n) sequences into a header or status-line value. In Restify, if user-controlled data is reflected in HTTP headers and the server does not enforce strict validation, an attacker can chain headers by injecting \r\n. When Hmac Signatures are used for request authentication, the signature is typically computed over a canonical set of headers and a method/URI path. If an attacker can inject additional headers via Crlf Injection, they can change the set of headers included in the Hmac signature verification without the server detecting tampering, because the server may only sign a subset of headers or may include the injected header lines in the signed string in an unexpected way.
For example, consider a Restify server that computes an Hmac signature over the X-API-Key header and the request path. If the application builds the string to sign by concatenating headers in a naive way (e.g., 'X-API-Key:' + apiKey + '\n' + req.path) and the API key value is taken directly from a user-supplied header without stripping CR/LF, an attacker can supply an API key such as abc\r\nX-Forwarded-For:evil. The resulting signed string may include the forged X-Forwarded-For header, altering the semantics or bypassing intended validation. Even if the server uses a standard Hmac approach, if header names or values are not canonicalized and stripped of control characters, the signature may still verify while the request includes injected headers that change authorization or cache behavior.
This combination is particularly dangerous because Hmac Signatures are often treated as tamper-proof; developers may assume that a valid signature guarantees integrity of the entire request. Crlf Injection can break that assumption by allowing an attacker to inject headers that are included in the signature computation or that are processed before signature verification, leading to header smuggling, response splitting, or privilege escalation. For instance, an injected Set-Cookie header could hijack sessions, or an injected Host header could facilitate cache poisoning. Because Restify may parse and normalize headers differently depending on its version and configuration, the exact impact depends on how the framework builds the request object and which headers are exposed to signature logic.
To discover such issues, scans test unauthenticated endpoints that reflect user input into headers and that use Hmac Signatures for integrity. The scanner checks whether header values containing \r or \n are rejected or sanitized, and whether signature computation includes only intended headers. Without explicit validation and canonicalization, an API that appears to be protected by Hmac Signatures can still be vulnerable through Crlf Injection.
Hmac Signatures-Specific Remediation in Restify — concrete code fixes
Remediation focuses on strict input validation, canonical header handling, and safe signature construction. Never include user-controlled header values directly in the string that is Hmac-signed. Instead, normalize and sanitize all header values by removing CR and LF characters, and explicitly define which headers are included in the signature.
Example of a vulnerable pattern in Restify:
// Vulnerable: user-controlled header value used directly in signature string
const crypto = require('crypto');
function verifySignature(req) {
const apiKey = req.get('X-API-Key'); // attacker can inject \r\n here
const payload = 'X-API-Key:' + apiKey + '\n' + req.path;
const expected = crypto.createHmac('sha256', secret).update(payload).digest('hex');
return timingSafeEqual(expected, req.get('X-Signature'));
}
Fixed version with canonicalization and rejection of dangerous characters:
const crypto = require('crypto');
function sanitizeHeaderValue(value) {
if (typeof value !== 'string') return '';
// Remove CR and LF to prevent header injection; reject if removed content changed length
const cleaned = value.replace(/[\r\n]/g, '');
if (cleaned.length !== value.length) {
throw new Error('Invalid header value: CR/LF not allowed');
}
return cleaned;
}
function buildSignatureString(headers, path) {
const apiKey = sanitizeHeaderValue(headers['x-api-key']);
// Canonicalize: include only known-safe headers, sorted and normalized
const sortedKeys = Object.keys(headers).filter(k => k.toLowerCase() === 'x-api-key').sort();
const parts = sortedKeys.map(k => `${k}:${sanitizeHeaderValue(headers[k])}`);
parts.push(path);
return parts.join('\n');
}
function verifySignature(req) {
const headers = {
'x-api-key': req.get('X-API-Key'),
// do not include attacker-controlled headers here
};
const payload = buildSignatureString(headers, req.path);
const expected = crypto.createHmac('sha256', secret).update(payload, 'utf8').digest('hex');
const received = req.get('X-Signature');
return timingSafeEqual(expected, received);
}
function timingSafeEqual(a, b) {
const bufA = Buffer.from(a, 'utf8');
const bufB = Buffer.from(b, 'utf8');
if (bufA.length !== bufB.length) return false;
return crypto.timingSafeEqual(bufA, bufB);
}
Additional recommendations:
- Validate and reject any header values containing \r or \n before using them in signature logic or request processing.
- Use a strict allowlist of headers for Hmac signing rather than including all incoming headers.
- Ensure that the signature string is constructed in a deterministic, canonical form (e.g., lowercased header names, sorted keys).
- Apply the same sanitization to any headers that are used to reconstruct the signature on the server or client side.
- Combine these practices with middleware that normalizes request objects in Restify so that header properties are consistently represented without CR/LF artifacts.