Crlf Injection in Express with Dynamodb
Crlf Injection in Express with Dynamodb — how this specific combination creates or exposes the vulnerability
Crlf Injection occurs when an attacker can inject CRLF characters (\r\n) into a header or log entry, causing header splitting or log injection. In Express, this commonly happens when user-controlled input is placed directly into HTTP headers such as Location or custom headers without validation. When the backend also interacts with DynamoDB, the risk pattern extends into how data is stored, retrieved, and reflected back to the client.
Consider an Express route that reads a redirect URL from a DynamoDB table and then uses it in a response. If the stored URL contains a CRLF sequence, and the application uses that value in res.location() or a custom header, the injected CRLF can split the header and inject new headers or a second response body. DynamoDB itself does not introduce the injection, but if the application trusts data stored in DynamoDB as safe and uses it in headers or logs, the stored data becomes a vector.
For example, an attacker might store a crafted value like https://example.com\r\nX-Injected: true in a DynamoDB attribute used for redirects. When the Express route retrieves this value and calls res.redirect(item.redirectUrl) or res.set('X-Location', item.redirectUrl), the injected CRLF can cause response splitting. This may enable HTTP response smuggling, cache poisoning, or the injection of arbitrary headers like Set-Cookie. Logging user input that contains CRLF and then writing those logs to a shared stream can similarly allow log injection, obscuring real events or forging log entries.
The DynamoDB integration in Express often involves fetching items by key and passing fields directly into headers or views. If input validation and output encoding are applied to user input but not to data retrieved from DynamoDB, a stored malicious value can bypass filters that only inspect direct request parameters. This is especially relevant when using the DynamoDB Document Client, which returns plain JavaScript objects that developers may forward to headers without re-sanitization.
Tools like middleBrick detect this by scanning the unauthenticated attack surface of Express endpoints that interact with DynamoDB, identifying places where data from storage is reflected into headers or logs. The scanner checks for missing validation on data retrieved from DynamoDB before it is used in header-setting APIs such as res.location(), res.set(), or res.redirect(). Because scanning takes 5–15 seconds and includes checks such as Input Validation and Data Exposure, it can surface these header-injection risks even without authentication.
Dynamodb-Specific Remediation in Express — concrete code fixes
To prevent Crlf Injection when using DynamoDB data in Express, treat every value retrieved from DynamoDB as untrusted. Apply strict validation and encoding before using DynamoDB fields in headers, redirects, or logs.
Remediation pattern 1: Validate and encode before redirect
Use a whitelist validation for URLs and ensure no CRLF characters are present. Encode or reject values that contain \r or \n. For Express routes that read a redirect target from DynamoDB, validate before calling res.redirect().
const allowedHosts = new Set(['example.com', 'api.example.com']);
function isValidRedirectUrl(url) {
try {
const parsed = new URL(url);
if (!allowedHosts.has(parsed.hostname)) return false;
// Reject if CRLF characters are present
if (parsed.href.includes('\r') || parsed.href.includes('\n')) return false;
return true;
} catch {
return false;
}
}
app.get('/redirect', async (req, res) => {
const key = { id: req.query.id };
const item = await dynamo.get({ TableName: 'Settings', Key: key }).promise();
const redirectUrl = item?.RedirectUrl;
if (!redirectUrl || !isValidRedirectUrl(redirectUrl)) {
return res.status(400).send('Invalid redirect target');
}
return res.redirect(redirectUrl);
});
Remediation pattern 2: Sanitize DynamoDB fields used in custom headers
If you must set a custom header from DynamoDB, strip or encode CRLF characters and avoid passing raw values to res.set().
function sanitizeHeader(value) {
if (typeof value !== 'string') return '';
return value.replace(/[\r\n]+/g, '');
}
app.get('/profile', async (req, res) => {
const item = await dynamo.get({ TableName: 'UserProfiles', Key: { userId: req.user.id } }).promise();
const safeLocation = sanitizeHeader(item?.location || '');
res.set('X-Location', safeLocation);
return res.json(item);
});
Remediation pattern 3: Avoid storing raw user-controlled values in headers or logs
Minimize the surface by storing only safe, normalized values in DynamoDB fields that may be used for headers or logging. If you store full URLs, consider storing a normalized, allowlisted identifier instead and map it server-side.
// Store a safe redirect key instead of raw URLs
const safeRedirects = {
home: '/',
docs: '/docs',
support: '/support'
};
app.get('/safe-redirect', async (req, res) => {
const key = req.query.key; // e.g., 'home'
const item = await dynamo.get({ TableName: 'RedirectMap', Key: { key } }).promise();
const path = item?.path; // expected values: '/', '/docs', '/support'
if (!path || !Object.values(safeRedirects).includes(path)) {
return res.status(400).send('Invalid redirect key');
}
return res.redirect(path);
});
These patterns ensure that data from DynamoDB is validated and sanitized before being used in Express header APIs. Combined with input validation at the point of entry, this reduces the risk of CRLF Injection via stored data. middleBrick can help surface places where DynamoDB values are reflected into headers without sufficient validation, supporting checks aligned with Input Validation and Data Exposure findings.