Email Injection in Express with Dynamodb
Email Injection in Express with Dynamodb — how this specific combination creates or exposes the vulnerability
Email injection occurs when user-controlled data is placed into email headers without proper validation or sanitization. In an Express application that stores or processes user data in DynamoDB, this typically happens when attacker-supplied input (e.g., name or email fields) is used to construct email headers or to build messages that are later sent by the server. If the application uses unsafe string concatenation to form headers like To: or Cc:, an attacker can inject additional headers such as Bcc:, Subject:, or even newline sequences to inject SMTP commands.
Dynamodb-Specific Remediation in Express — concrete code fixes
To mitigate email injection while interacting with DynamoDB in Express, treat all user input as untrusted and validate it before using it in email-related logic. Below are concrete examples showing unsafe usage and a secure alternative with DynamoDB integration.
Unsafe example (vulnerable to email injection)
An Express route that builds email headers by directly concatenating user input is vulnerable:
// UNSAFE: vulnerable to email injection
app.post('/notify', (req, res) => {
const { email, name } = req.body;
const headerLine = 'To: ' + name + ' <' + email + '>';
// If name contains \r\n, an attacker can inject extra headers
sendMail(headerLine);
res.send('Notification sent');
});
Secure example with DynamoDB record storage and header sanitization
Validate and sanitize inputs, use parameterized libraries for email construction, and store only safe values in DynamoDB.
const AWS = require('aws-sdk');
const validator = require('validator');
const nodemailer = require('nodemailer');
const dynamo = new AWS.DynamoDB.DocumentClient();
const transporter = nodemailer.createTransport({ /* SMTP config */ });
app.post('/notify', async (req, res) => {
const { email, name } = req.body;
// Strict validation and sanitization
if (!validator.isEmail(email)) {
return res.status(400).send('Invalid email');
}
const safeName = validator.escape(name ? name.trim() : 'User');
// Send mail using a safe, constructed address object
const mailOptions = {
to: { name: safeName, address: email },
from: '[email protected]',
subject: 'Notification'
};
try {
await transporter.sendMail(mailOptions);
// Store a safe record in DynamoDB (no header content)
await dynamo.put({
TableName: 'AuditLog',
Item: {
id: `log#${Date.now()}`, // Partition key
email: email,
name: safeName,
timestamp: new Date().toISOString()
}
}).promise();
res.send('Notification sent and logged');
} catch (err) {
console.error(err);
res.status(500).send('Failed to send');
}
});
Additional DynamoDB-specific guidance
- Do not store raw message headers or concatenated header strings that include user input; store only validated, canonical values.
- When querying DynamoDB for audit or logs, avoid reflecting stored header-like fields directly into email construction logic.
- Apply the same validation/sanitization regardless of whether data comes from query parameters, body, or headers.