HIGH type confusionhmac signatures

Type Confusion with Hmac Signatures

How Type Confusion Manifests in Hmac Signatures

HMAC (Hash-based Message Authentication Code) signatures are widely used in APIs to verify the integrity and authenticity of requests. A typical pattern involves the client computing an HMAC over a set of request parameters (e.g., method, path, query parameters, body) using a shared secret, then sending the signature in a header (like X-Signature). The server recomputes the HMAC over the same parameters and compares it to the provided signature. If they match, the request is considered legitimate.

Type confusion in this context arises when the server and client (or an attacker) disagree on the data type or serialization format of the signed parameters. Because HMAC operates on bytes, any inconsistency in how parameters are converted to bytes will produce a different HMAC, potentially allowing an attacker to bypass signature validation.

Common attack patterns include:

  • Numeric vs. String Representation: If a parameter like user_id is sent as a number (123) but the server expects a string ("123"), the byte representation differs. An attacker might exploit this by sending the parameter in a different type and adjusting the signature accordingly, if the server's HMAC computation does not normalize types.
  • Boolean and Null Values: JSON distinguishes between true, false, and null. If the server treats null as an empty string or ignores it, while the client includes it, the HMAC will diverge.
  • Encoding Issues: If the server uses a platform-specific default encoding (e.g., utf8 in Node.js vs. ISO-8859-1 in Java) when converting strings to bytes, the same string can produce different HMACs.
  • Serialization Format: The order and formatting of parameters matter. For example, concatenating parameters as key1=value1&key2=value2 vs. using JSON with sorted keys. If an attacker can inject a parameter that changes the serialization order (e.g., by adding a duplicate key), they might control the signed string.

Here is a vulnerable Node.js example that computes an HMAC by simply concatenating parameter values without type normalization:

const crypto = require('crypto');

function computeSignature(params, secret) {
  // Vulnerable: direct concatenation of values without type handling
  const data = Object.values(params).join('');
  return crypto.createHmac('sha256', secret).update(data).digest('hex');
}

// Attacker-controlled params: user_id as number vs. string
const params1 = { user_id: 123, amount: 100 };
const params2 = { user_id: '123', amount: 100 };
console.log(computeSignature(params1, 'secret')); // Different from params2
console.log(computeSignature(params2, 'secret'));

If the server uses the first representation but the client (or attacker) uses the second, the HMAC validation will fail for legitimate requests or be bypassed by an attacker who crafts both the parameters and the signature.

Hmac Signatures-Specific Detection

Detecting type confusion in HMAC signatures requires testing how the API handles parameter types during signature verification. Manual testing involves sending the same logical parameter in different data types (e.g., integer, string, boolean) and observing whether the server accepts the request with a valid signature computed for one type when the parameter is sent in another type.

For example, you can:

  • Capture a legitimate request with a known signature.
  • Replay the request but change a numeric parameter to a string (e.g., user_id=123 to user_id="123" in JSON) and recompute the signature accordingly.
  • If the server accepts the request, it indicates that the server's HMAC computation does not normalize types, leading to a vulnerability.

How middleBrick helps: middleBrick's black-box scanning includes an Authentication check that tests for inconsistencies in HMAC validation. It automatically:

  • Identifies if an API uses HMAC signatures (by looking for headers like X-Signature, Authorization: HMAC, etc.).
  • Attempts to compute signatures for variations of the same request with parameters expressed in different types (numbers as strings, booleans as integers, etc.).
  • Compares server responses to detect if the signature validation is bypassed or if the server returns different error messages for different type representations.

middleBrick also checks for Input Validation issues that could exacerbate type confusion, such as accepting duplicate parameters or non-canonical JSON. The scan output will flag any parameter type inconsistencies as a finding under the Authentication category with a severity rating and remediation guidance.

Hmac Signatures-Specific Remediation

The fix is to ensure that both client and server use a canonical, type-stable serialization for the signed data. This means:

  1. Define a strict data model for all signed parameters (e.g., user_id must be an integer, active a boolean).
  2. Serialize parameters in a deterministic way: use a standard format like JSON with sorted keys and no whitespace, or a well-defined string concatenation scheme with explicit type indicators.
  3. Normalize types before serialization: convert all values to their canonical string representation (e.g., numbers to strings without leading zeros, booleans to "true"/"false").
  4. Always use a fixed character encoding (UTF-8) when converting the serialized string to bytes for HMAC computation.

Here is a corrected Node.js example using canonical JSON serialization with sorted keys and UTF-8 encoding:

const crypto = require('crypto');

function canonicalize(params) {
  // Sort keys and stringify without whitespace
  const sortedKeys = Object.keys(params).sort();
  const sortedParams = {};
  for (const key of sortedKeys) {
    // Normalize values to consistent types if needed
    // For example, ensure numbers are represented as numbers in JSON
    sortedParams[key] = params[key];
  }
  return JSON.stringify(sortedParams);
}

function computeSignature(params, secret) {
  const data = canonicalize(params);
  // Explicitly use UTF-8 encoding
  return crypto.createHmac('sha256', secret).update(data, 'utf8').digest('hex');
}

// Now both params1 and params2 produce the same signature if user_id is a number
const params1 = { user_id: 123, amount: 100 };
const params2 = { user_id: 123, amount: 100 }; // same type
console.log(computeSignature(params1, 'secret'));
console.log(computeSignature(params2, 'secret')); // identical

If the API must accept parameters in multiple formats (e.g., query string vs. JSON body), the server should first parse them into a canonical internal representation before computing the HMAC. Additionally, always validate that the received parameters match the expected types after parsing, and reject requests with type mismatches.

For existing APIs, a migration strategy is to version the signing scheme (e.g., X-Signature-Version: 2) and gradually roll out the canonical serialization while keeping the old version for backward compatibility until all clients are updated.

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

Can middleBrick detect type confusion in HMAC signatures if the API uses custom signing algorithms?
Yes. middleBrick's Authentication check tests for inconsistencies in how parameters are handled during signature verification, regardless of the specific algorithm (HMAC-SHA256, HMAC-SHA1, etc.). It sends parameter type variations and observes if the server's response indicates a bypass or inconsistent behavior.
Does fixing type confusion in HMAC signatures require changing the API's clients?
Ideally, both server and clients must use the same canonical serialization. However, you can often fix it server-side by normalizing incoming parameters to a canonical form before computing the HMAC, without requiring immediate client changes. But for long-term security, all clients should adopt the canonical format to avoid future mismatches.