Xss Cross Site Scripting in Express with Hmac Signatures
Xss Cross Site Scripting in Express with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Cross-site scripting (XSS) in Express applications that use HMAC signatures can still occur when signed values are rendered in the browser without proper output encoding. HMAC signatures are typically used to ensure integrity and authenticity of data (e.g., a signed user ID in a cookie or a signed JSON Web Token). If the application trusts the signature and directly embeds associated data into HTML, JavaScript, or URL contexts without sanitization, an attacker can still inject malicious scripts. This commonly happens when an attacker-controlled payload is included in signed claims that are later rendered in the application’s response and treated as safe because the signature validates correctly.
Express applications often use signed cookies via cookie-parser with a secret to validate values. For example, a server might set a signed cookie containing a user display name and then render that name directly into an HTML template. If the display name includes <script>alert(1)</script>, and the server embeds it without escaping, the browser executes the script even though the cookie signature is valid. Similarly, APIs that return signed tokens and have client-side code interpret the payload without output encoding can lead to reflected or stored XSS when the token’s claims include unsanitized user input.
Another scenario involves template injection where an attacker can control part of the data used to render a template and the application signs that data to avoid tampering. Because the signature only guarantees integrity, not safety, the server must still enforce context-aware escaping. Failing to do so turns HMAC-based integrity into a false sense of security. The vulnerability aligns with OWASP API Top 10 A05:2023 — Security Misconfiguration, and can expose APIs to XSS via JSON injection or DOM-based XSS when the client processes signed data without sanitization.
SSRF and input validation checks within middleBrick do not automatically prevent XSS stemming from trusted signed data; they validate structure and access controls, not output context. Therefore, developers must treat signed data as untrusted in output contexts and apply appropriate encoding based on where the data is used (HTML body, attribute, JavaScript, or URL).
Hmac Signatures-Specific Remediation in Express — concrete code fixes
To remediate XSS when using HMAC signatures in Express, always encode data based on the output context and validate/sanitize inputs before signing or rendering. Below are concrete examples demonstrating secure handling.
Example 1: Signed cookie with safe HTML rendering
Use the cookie-parser with signed cookies and escape data in templates (using a template engine like EJS with escaping enabled by default).
const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
const SECRET = process.env.COOKIE_SECRET; // keep secret in env
app.use(cookieParser(SECRET));
app.get('/set', (req, res) => {
// sign a safe representation of data
const displayName = 'JaneDoe'; // ideally sanitized on input
res.cookie('user', { name: displayName }, { signed: true, httpOnly: false });
res.send('Cookie set');
});
app.get('/profile', (req, res) => {
const user = req.signedCookies.user;
if (!user) return res.status(401).send('Unauthorized');
// Escape output for HTML context
const escapedName = escapeHtml(user.name);
res.send(`Hello, ${escapedName}`);
});
function escapeHtml(str) {
if (typeof str !== 'string') return str;
return str.replace(/[&<>"']/g, (s) => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[s]));
}
Example 2: Signed JSON response with context-aware encoding
If sending signed data to a client that will render it in JavaScript, do not embed directly into inline scripts. Instead, serve JSON via an API endpoint and fetch it securely, or encode for HTML attributes/JS strings.
const express = require('express');
const crypto = require('crypto');
const app = express();
const SECRET = process.env.HMAC_SECRET;
function signData(data) {
const payload = JSON.stringify(data);
const hmac = crypto.createHmac('sha256', SECRET);
hmac.update(payload);
return { payload, sig: hmac.digest('hex') };
}
function verifyData(payload, sig) {
const hmac = crypto.createHmac('sha256', SECRET);
hmac.update(payload);
const expected = hmac.digest('hex');
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
app.get('/data', (req, res) => {
const data = { username: 'alice', role: 'user' };
const { payload, sig } = signData(data);
res.json({ payload: JSON.parse(payload), sig });
});
app.post('/verify', express.json(), (req, res) => {
const { payload, sig } = req.body;
if (!verifyData(payload, sig)) {
return res.status(400).send('Invalid signature');
}
const data = JSON.parse(payload);
// Ensure safe usage in downstream contexts; do not eval or innerHTML
res.send(`Received user: ${escapeHtml(data.username)}`);
});
function escapeHtml(str) {
const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
return String(str).replace(/[&<>"']/g, (ch) => map[ch]);
}
Best practices
- Sign integrity, not safety: HMAC ensures data hasn’t been altered, not that it’s safe for a given output context.
- Context-aware escaping: HTML body, HTML attribute, JavaScript string, and URL each require different escaping functions.
- Validate and sanitize inputs early: Treat all user input as untrusted, even if it will be signed later.
- Use HTTPOnly and Secure flags for sensitive signed cookies to reduce exposure.
- Leverage middleBrick scans to detect XSS and related misconfigurations; treat findings as guidance to review output handling around signed data.
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |