Header Injection in Express with Dynamodb
Header Injection in Express with Dynamodb — how this specific combination creates or exposes the vulnerability
Header Injection occurs when user-controlled data is placed into HTTP response headers without validation or encoding. In Express applications that interact with Amazon DynamoDB, this typically arises when developers directly use request parameters, headers, or DynamoDB attribute values to construct response headers. Because DynamoDB often stores user-supplied or derived metadata (such as redirect URLs, location tokens, or custom identifiers), using that data in headers like Location, Refresh, or X-Redirect-URL can lead to injection.
Express does not inherently sanitize header values; if a value from a DynamoDB item (e.g., item.redirectUrl or item.origin) is passed to res.set() or res.header(), newline characters (CRLF) can break header structure. An attacker who can control or influence DynamoDB content might inject sequences like \r\nLocation: https://evil.com, causing header splitting. Common patterns include using user data in authentication callback flows where DynamoDB stores callback_origin and the app sets res.set('Origin', callbackOrigin). Since DynamoDB does not validate HTTP semantics, the application must treat all stored values as untrusted.
The risk is compounded when combined with insecure deserialization or weak access controls (BOLA/IDOR), where an attacker can read or modify DynamoDB items to inject malicious header content. For example, an item used for session tracking might contain a nextUrl attribute used in res.redirect(item.nextUrl); if this value contains newlines, it can lead to response smuggling or open redirects. MiddleBrick detects such header injection risks by correlating DynamoDB-derived values used in headers across its 12 security checks, including Input Validation and Data Exposure.
Dynamodb-Specific Remediation in Express — concrete code fixes
Remediation focuses on strict validation, sanitization, and avoiding direct use of DynamoDB attribute values in headers. Always treat data from DynamoDB as untrusted, even if it originates from your own service. Use allowlists for known-safe values and avoid reflecting DynamoDB content into headers unless absolutely necessary.
Example 1: Unsafe use of DynamoDB data in a redirect
// Unsafe: directly using DynamoDB attribute in res.redirect
const AWS = require('aws-sdk');
const dynamo = new AWS.DynamoDB.DocumentClient();
app.get('/profile', async (req, res) => {
const userId = req.query.userId;
const data = await dynamo.get({ TableName: 'Users', Key: { userId } }).promise();
const redirectUrl = data.Item?.redirectUrl; // Could be attacker-controlled
res.redirect(redirectUrl); // Risk: Header injection via CRLF
}).listen(3000);
Example 2: Safe remediation with validation and sanitization
const AWS = require('aws-sdk');
const validator = require('validator');
const dynamo = new AWS.DynamoDB.DocumentClient();
const allowedDomains = new Set(['example.com', 'app.example.com']);
app.get('/profile', async (req, res) => {
const userId = req.query.userId;
const data = await dynamo.get({ TableName: 'Users', Key: { userId } }).promise();
const storedUrl = data.Item?.redirectUrl;
// Validate URL structure and enforce same-domain policy
if (validator.isURL(storedUrl, { require_protocol: true }) &&
new URL(storedUrl).hostname.endsWith('example.com')) {
res.redirect(storedUrl);
} else {
res.status(400).send('Invalid redirect target');
}
}).listen(3000);
Example 3: Setting headers with sanitized DynamoDB values
app.get('/metadata', async (req, res) => {
const item = await dynamo.get({ TableName: 'Metadata', Key: { id: 'site-config' } }).promise();
const xFrameOption = item.Item?.xFrameOption;
// Only allow known safe values to prevent header injection
const validFrameOptions = ['DENY', 'SAMEORIGIN'];
if (validFrameOptions.includes(xFrameOption)) {
res.set('X-Frame-Options', xFrameOption);
} else {
res.set('X-Frame-Options', 'DENY'); // Default safe value
}
// Avoid using untrusted data in headers like Refresh
const refreshRate = Number(item.Item?.refreshSeconds);
if (Number.isInteger(refreshRate) && refreshRate > 0 && refreshRate <= 3600) {
res.set('Refresh', `${refreshRate}; url=/dashboard`);
}
res.json(item.Item);
}).listen(3000);
Additional best practices
- Use
res.location()for safe URL setting in redirects; it does minimal encoding but still validate input. - Apply output encoding for any header-like data displayed in UI, but remember headers require stricter controls than HTML context.
- Enable DynamoDB fine-grained IAM policies to limit write access to attributes that influence headers, reducing tampering risk.
- Log and monitor unexpected header values in responses as part of runtime security observation.
These patterns ensure DynamoDB-driven applications remain robust against CRLF injection and related attacks while preserving functionality.