Email Injection in Express with Hmac Signatures
Email Injection in Express with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Email Injection in Express applications occurs when user-controlled data is concatenated into email headers without validation or sanitization. Attackers can inject additional headers or newline characters (e.g., %0D%0A or \r\n) to add fields like Cc:, Bcc:, or extra To: addresses, enabling spam or phishing campaigns. Using Hmac Signatures in Express is intended to ensure request integrity and authenticity by signing payloads with a shared secret; however, if the signature is computed over incomplete or improperly canonicalized data, or if the signature verification does not cover all user-controlled fields that eventually influence email composition, the combination can expose injection risks.
Consider an Express route that accepts JSON containing email and action_token, signs the payload with Hmac, and uses the email value directly in a mail header. If the server uses the incoming email to build a mail header without validating format (e.g., disallowing newlines), an attacker can supply email: "[email protected]\r\nCc: [email protected]". If the Hmac signature is verified only on the raw body but the server still processes the injected newline and adds the forged header, the signature does not prevent the injection because the trust boundary was misaligned: the signature guarantees the payload wasn’t tampered with in transit, but the server’s misuse of the email field bypasses intent. Common frameworks like nodemailer may allow multiple header lines when newline characters are present, and if the Express app does not sanitize or explicitly set headers via a safe library, the injected lines are interpreted as additional headers.
With Hmac Signatures, the risk is not that the signature itself is broken, but that developers may assume a valid signature implies safe data handling. For example, an Express route might verify the Hmac to authorize an action (e.g., password reset), then compose an email using concatenated strings: 'To: ' + userEmail + '\r\n' + .... If userEmail contains newline sequences, the resulting header chain can be manipulated. This becomes especially dangerous when combined with other headers like Subject: or when the email library allows header folding. The OWASP API Top 10 category ‘2023-A1: Improper Inventory of APIs’ and related BOLA/IDOR concerns may intersect if the email field is also subject to authorization flaws, but the core issue here is input validation and canonicalization before using data in headers.
Real-world examples include scenarios where an unauthenticated attacker probes an endpoint accepting user-controlled email fields, attempting CRLF injection sequences to validate whether the server reflects or acts on injected headers. While this is not SSRF or command injection, it can lead to mail header injection, a classic web vulnerability. The signature mechanism remains cryptographically intact, yet the application logic fails to enforce strict email format rules, allowing the injected header to alter routing or recipients. This highlights the need to treat signed payloads as trusted but still validate and sanitize individual fields based on context.
Hmac Signatures-Specific Remediation in Express — concrete code fixes
Remediation centers on strict input validation, canonical data representation before signing, and safe header construction. Always validate and sanitize user inputs before using them in any context, including headers. For emails, enforce a strict format (e.g., RFC 5322 with length and character constraints) and reject any input containing newline or carriage return characters. When using Hmac Signatures in Express, ensure the signature covers all fields that influence business logic, and do not rely on the signature to sanitize data.
Below are concrete Express code examples demonstrating secure handling with Hmac Signatures.
Example 1: Safe Hmac Signing and Verification
Sign a canonical JSON string that includes only the necessary fields, and verify before processing. Use a constant-time comparison for signature validation.
const crypto = require('crypto');
const express = require('express');
const app = express();
app.use(express.json());
const SHARED_SECRET = process.env.HMAC_SECRET; // store securely
function generateHmac(payload) {
const canonical = JSON.stringify({
email: payload.email.trim().toLowerCase(),
action: payload.action,
timestamp: Math.floor(Date.now() / 1000)
});
return crypto.createHmac('sha256', SHARED_SECRET).update(canonical).digest('hex');
}
function verifyHmac(payload, receivedSignature) {
const expected = generateHmac(payload);
// timing-safe compare
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(receivedSignature));
}
app.post('/action', (req, res) => {
const { email, action, signature } = req.body;
if (!email || !action || !signature) {
return res.status(400).json({ error: 'Missing fields' });
}
// Validate email format strictly
const emailRegex = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
if (!emailRegex.test(email)) {
return res.status(400).json({ error: 'Invalid email format' });
}
// Reject newlines to prevent header injection
if (email.includes('\r') || email.includes('\n')) {
return res.status(400).json({ error: 'Invalid email format' });
}
const payload = { email, action };
if (!verifyHmac(payload, signature)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Safe header construction: set To explicitly without concatenation
const mailOptions = {
to: email,
subject: 'Action required',
text: `Action: ${action}`
};
// Send mail using a trusted library; do not concatenate user input into headers
res.json({ status: 'ok' });
});
Example 2: Express Middleware for Header Safety
Add middleware that rejects unsafe characters in headers and ensures signed routes only accept canonical data.
function validateNoCrlf(req, res, next) {
const fields = ['email', 'username', 'display_name'];
for (const field of fields) {\n if (req.body[field] && (req.body[field].includes('\r') || req.body[field].includes('\n'))) {
return res.status(400).json({ error: `Invalid ${field}` });
}
}
next();
}
app.post('/profile', validateNoCrlf, (req, res) => {
const { email, token, signature } = req.body;
const payload = { email, token };
if (!verifyHmac(payload, signature)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Continue safe processing
res.json({ status: 'updated' });
});
These examples show how to combine Hmac Signatures with strict input checks to mitigate email injection. By canonicalizing before signing, validating formats, and explicitly setting headers, you reduce the risk of injection while maintaining integrity checks.