Log Injection in Express with Dynamodb
Log Injection in Express with Dynamodb — how this specific combination creates or exposes the vulnerability
Log injection occurs when untrusted input is written directly into log entries without sanitization or formatting control. In an Express application that uses Amazon DynamoDB as a data store, this typically arises when request data—such as user identifiers, query parameters, or object keys—is embedded into log messages before being sent to DynamoDB or after reading from it. Because DynamoDB does not perform log-specific formatting, newline characters or structured delimiters in attribute values can corrupt log streams when those values are later written to logs by application code or monitoring tools.
Consider an Express route that retrieves a user record from DynamoDB and logs the response for debugging:
const AWS = require('aws-sdk');
const dynamo = new AWS.DynamoDB.DocumentClient();
const express = require('express');
const app = express();
app.get('/user/:userId', async (req, res) => {
const userId = req.params.userId;
const params = {
TableName: 'Users',
Key: { userId: userId }
};
try {
const data = await dynamo.get(params).promise();
console.log('User retrieved:', JSON.stringify(data.Item));
res.json(data.Item);
} catch (err) {
console.error('DynamoDB error:', err.message);
res.status(500).send('Server error');
}
});
If the userId contains a newline (e.g., attacker\nAdmin: true), and the logging library or runtime writes this value directly into a log file, the injected newline can split a single log line into multiple entries. This breaks log parsers, obscures audit trails, and can facilitate log forging or injection attacks where an attacker manipulates log appearance without needing to compromise the logging system itself. When DynamoDB is used as a backend, the risk is compounded if attribute values include control characters that are later interpolated into log statements by application code or middleware.
Another scenario involves logging DynamoDB query or scan responses that include user-controlled data. For example, an Express handler might log the full response from DynamoDB, inadvertently exposing sensitive fields if those fields contain newline or structured characters that distort log formatting:
app.post('/search', async (req, res) => {
const { query } = req.body;
const params = {
TableName: 'Products',
FilterExpression: 'contains(name, :q)',
ExpressionAttributeValues: { ':q': query }
};
const data = await dynamo.scan(params).promise();
console.log('Search results:', JSON.stringify(data.Items));
res.json(data.Items);
});
Here, if query includes carriage returns or other control sequences, and the resulting items are logged without sanitization, the log stream becomes ambiguous. This can impair incident response, enable injection-based log manipulation, and complicate correlation with other telemetry. The combination of Express routing, dynamic user input, and DynamoDB as a data store does not introduce new injection primitives but provides a pathway where unchecked data flows from DynamoDB responses into log entries, creating observable and exploitable log integrity issues.
Dynamodb-Specific Remediation in Express — concrete code fixes
To mitigate log injection when integrating Express with DynamoDB, ensure that any data written to logs is sanitized and that log formatting is controlled. The primary defense is to avoid interpolating raw DynamoDB attribute values directly into log messages. Instead, use structured logging with explicit field serialization and character escaping.
1. Sanitize user input before logging:
function sanitizeLog(value) {
if (typeof value !== 'string') return value;
return value.replace(/[\r\n]+/g, ' ');
}
app.get('/user/:userId', async (req, res) => {
const userId = sanitizeLog(req.params.userId);
const params = {
TableName: 'Users',
Key: { userId: req.params.userId }
};
const data = await dynamo.get(params).promise();
console.log('User retrieved:', { userId, hasItem: !!data.Item });
res.json(data.Item);
});
2. Use structured logging for DynamoDB responses instead of raw stringification:
app.post('/search', async (req, res) => {
const query = sanitizeLog(req.body.query);
const params = {
TableName: 'Products',
FilterExpression: 'contains(name, :q)',
ExpressionAttributeValues: { ':q': query }
};
const data = await dynamo.scan(params).promise();
console.log('Search results', {
query,
count: data.Items.length,
ids: data.Items.map(item => ({ id: sanitizeLog(item.id) }))
});
res.json(data.Items);
});
3. Ensure DynamoDB client configuration and error handling do not leak raw values:
app.get('/profile/:email', async (req, res) => {
const email = sanitizeLog(req.params.email);
const params = {
TableName: 'Profiles',
KeyConditionExpression: 'email = :email',
ExpressionAttributeValues: { ':email': email }
};
try {
const data = await dynamo.query(params).promise();
console.log('Profile lookup', { email, count: data.Count });
res.json(data.Items);
} catch (err) {
console.error('Profile lookup failed', { email, code: err.code });
res.status(500).send('Server error');
}
});
These practices reduce the risk that newline or control characters in DynamoDB-stored data distort log structure. They align with secure logging guidelines that emphasize controlled formatting and avoiding direct inclusion of untrusted data. In an Express context, this complements middleware that normalizes request inputs and ensures that logging behavior remains predictable regardless of DynamoDB content.