HIGH type confusionexpresshmac signatures

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.from to ensure consistent binary representation for both the key and the computed digest.
  • Extract the raw signature value and compare using crypto.timingSafeEqual to 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 IDNameSeverity
CWE-20Improper Input Validation HIGH
CWE-22Path Traversal HIGH
CWE-74Injection CRITICAL
CWE-77Command Injection CRITICAL
CWE-78OS Command Injection CRITICAL
CWE-79Cross-site Scripting (XSS) HIGH
CWE-89SQL Injection CRITICAL
CWE-90LDAP Injection HIGH
CWE-91XML Injection HIGH
CWE-94Code Injection CRITICAL

Frequently Asked Questions

Why does using '==' to compare Hmac signatures pose a risk in Express?
Using '==' can allow type coercion that masks differences between the expected and provided signatures. An attacker may craft inputs where a string and a Buffer or a prefixed representation compare as equal, bypassing verification. Always use a constant-time comparison like crypto.timingSafeEqual with consistent Buffer types.
How can I ensure my Hmac signature verification handles types consistently in Express?
Convert keys, messages, and signatures to Buffers using Buffer.from with explicit encodings, normalize the signature format (e.g., strip the 'sha256=' prefix), and compare using crypto.timingSafeEqual. Avoid mixing strings and Buffers in the crypto operations and do not rely on loose equality checks.