Type Confusion in Express with Hmac Signatures
Type Confusion in Express with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Type confusion in Express when using Hmac signatures typically arises when the application compares signed values using loose equality (==) or when it mixes types (string vs. Buffer) during signature construction or verification. In JavaScript, loose equality can coerce types in ways that make an invalid signature appear valid, or it can bypass length or type checks that an attacker can leverage.
Consider an Express route that validates an Hmac signature by reading a token from headers and comparing it to a locally computed value. If the developer uses == instead of a constant-time comparison, an attacker may supply a signature that type-coerces to match the expected string even when the bytes differ. Additionally, if the application accepts both JSON and URL-encoded payloads and the type of the signed field changes between representations (e.g., a number vs. a string), the signature verification logic might treat them inconsistently, creating a type confusion that weakens integrity checks.
Another common pattern involves using crypto.createHmac with a key and message that are not consistently typed. For example, if the key is a string but the message is a Buffer, or if the signature is expected as a hex string but compared to a Buffer, the runtime behavior can differ across Node.js versions or under certain inputs. This inconsistency can lead to mismatched verification results or bypasses when the application does not enforce strict type handling.
In the context of middleware, if signature verification is applied selectively based on content type or route parameters, type confusion can be introduced through inconsistent parameter parsing. An attacker might send a payload where a numeric ID is provided as a string in one request and as a number in another, causing the verification path to diverge and allowing unsigned or tampered requests to pass if the comparison logic is not robust.
These issues map to common weaknesses in API security, such as those outlined in the OWASP API Security Top 10, particularly under security checks like Input Validation and Authentication. They do not involve implementation internals, but they illustrate how improper type handling around Hmac signatures can degrade the security guarantees of cryptographic primitives.
Hmac Signatures-Specific Remediation in Express — concrete code fixes
To remediate type confusion when using Hmac signatures in Express, enforce strict type handling and use constant-time comparison functions. Always treat signatures and keys as Buffers or consistently as hex strings, and avoid loose equality checks.
Example of vulnerable code:
const crypto = require('crypto');
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook', (req, res) => {
const signature = req.headers['x-hub-signature-256'];
const body = JSON.stringify(req.body);
const key = 'super-secret';
const hmac = crypto.createHmac('sha256', key);
const digest = 'sha256=' + hmac.update(body).digest('hex');
if (signature == digest) { // Vulnerable: loose equality
res.status(200).send('OK');
} else {
res.status(401).send('Invalid signature');
}
});
app.listen(3000);
Issues in the above example include the use of == and inconsistent handling of the signature prefix. An attacker could exploit type coercion or mismatched representations to bypass verification.
Secure implementation using constant-time comparison and consistent types:
const crypto = require('crypto');
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook', (req, res) => {
const signatureHeader = req.headers['x-hub-signature-256'];
if (!signatureHeader || !signatureHeader.startsWith('sha256=')) {
return res.status(400).send('Missing or invalid signature header');
}
const receivedSignature = signatureHeader.split('=')[1];
const body = JSON.stringify(req.body);
const key = Buffer.from('super-secret', 'utf8');
const hmac = crypto.createHmac('sha256', key);
const expectedSignature = hmac.update(body).digest('hex');
const isValid = crypto.timingSafeEqual(
Buffer.from(receivedSignature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
res.status(200).send('OK');
});
app.listen(3000);
Key improvements:
- Use
Buffer.fromto ensure consistent binary representation for both the key and the computed digest. - Extract the raw signature value and compare using
crypto.timingSafeEqualto prevent timing attacks and eliminate type coercion issues. - Validate the presence and format of the signature header before processing.
- Keep key material as a Buffer rather than a string to avoid implicit conversions during Hmac computation.
When integrating with CI/CD, the middleBrick CLI (middlebrick scan <url>) can be used in scripts to validate that such endpoints remain secure after changes. For automated enforcement, the GitHub Action can fail builds if security scores drop, and the MCP Server allows scanning APIs directly from AI coding assistants within your IDE.
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 |