HIGH sql injectionexpresshmac signatures

Sql Injection in Express with Hmac Signatures

Sql Injection in Express with Hmac Signatures — how this specific combination creates or exposes the vulnerability

SQL injection in Express applications that rely on HMAC signatures for request integrity can still occur when signatures are validated without verifying the trustworthiness and correctness of the incoming data used in query construction. A common pattern is to sign a subset of request parameters (e.g., an identifier or filter value) using a shared secret and include the signature in headers such as X-API-Signature. If the server validates the HMAC to confirm the parameter has not been tampered with but then directly interpolates that parameter into a SQL string, the signature merely confirms that the attacker provided a valid signature for their chosen input, not that the input is safe.

Consider an Express endpoint that uses query parameters for filtering and signs the filter value. An attacker can supply any value, and as long as they provide a correct HMAC (e.g., computed with the known secret), the server treats the value as trusted. If the server uses the unsigned value directly in a SQL query—such as concatenating it into a WHERE clause—the signature does nothing to prevent injection; it only ensures the value hasn’t been altered in transit, not that it is safe. This situation commonly arises when developers assume integrity implies safety, leading to vulnerabilities tied to improper validation and unsafe SQL construction.

Real-world examples include endpoints that build queries using template literals or string concatenation with user-controlled data that has an HMAC, such as SELECT * FROM products WHERE category = '${category}', where category is signed but not sanitized or parameterized. Attackers can exploit this by providing payloads like A' OR '1'='1 with a valid HMAC, potentially bypassing authentication checks or extracting data. The root cause is treating HMAC-protected input as sanitized input, neglecting parameterized queries and strict allowlists, which enables classic SQL injection despite the presence of cryptographic integrity checks.

Hmac Signatures-Specific Remediation in Express — concrete code fixes

To remediate SQL injection risks in Express when using HMAC signatures, you must treat signed data as untrusted for SQL construction and enforce strict input validation and parameterized queries. HMACs are useful for ensuring data integrity and detecting tampering, but they do not replace safe data handling practices. Always validate the format and type of the input before using it, and use parameterized queries or an ORM that enforces separation between code and data.

Example: Unsafe usage with HMAC

const express = require('express');
const crypto = require('crypto');
const app = express();

const SHARED_SECRET = 'super-secret-key';

function verifySignature(value, signature) {
  const hmac = crypto.createHmac('sha256', SHARED_SECRET);
  hmac.update(value);
  return hmac.digest('hex') === signature;
}

app.get('/products', (req, res) => {
  const { category, sig } = req.query;
  if (!category || !sig || !verifySignature(category, sig)) {
    return res.status(400).send('Invalid signature');
  }
  // Unsafe: concatenating user input into SQL even though it is HMAC-signed
  const sql = `SELECT * FROM products WHERE category = '${category}'`;
  db.query(sql, (err, results) => {
    if (err) return res.status(500).send('Error');
    res.json(results);
  });
});

Example: Safe remediation with parameterized queries

const express = require('express');
const crypto = require('crypto');
const app = express();

const SHARED_SECRET = 'super-secret-key';

function verifySignature(value, signature) {
  const hmac = crypto.createHmac('sha256', SHARED_SECRET);
  hmac.update(value);
  return crypto.timingSafeEqual(Buffer.from(hmac.digest('hex')), Buffer.from(signature));
}

app.get('/products', (req, res) => {
  const { category, sig } = req.query;
  if (!category || !sig || !verifySignature(category, sig)) {
    return res.status(400).send('Invalid signature');
  }
  // Validate format: only allow alphanumeric and common category characters
  if (!/^[a-zA-Z0-9_\-\s]+$/.test(category)) {
    return res.status(400).send('Invalid category format');
  }
  // Safe: parameterized query ensures user input is never interpreted as SQL
  const sql = 'SELECT * FROM products WHERE category = ?';
  db.query(sql, [category], (err, results) => {
    if (err) return res.status(500).send('Error');
    res.json(results);
  });
});

Additional best practices

  • Use an allowlist for expected values where feasible (e.g., known category IDs or slugs).
  • Prefer using an ORM or query builder that enforces parameterization by default.
  • Use crypto.timingSafeEqual for signature comparisons to prevent timing attacks.
  • Log invalid signature attempts for monitoring, but do not expose internal SQL errors to clients.

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

Does an HMAC signature make user input safe for SQL queries?
No. An HMAC signature confirms integrity (the value was not altered), but it does not prevent SQL injection. Always validate format and use parameterized queries to keep data separate from SQL commands.
What should I do if my endpoint receives a valid HMAC but unsafe input?
Reject the request with a 400 response after validation. Enforce strict allowlists for expected formats or values, and use parameterized queries regardless of signature validity.