HIGH credential stuffingstrapihmac signatures

Credential Stuffing in Strapi with Hmac Signatures

Credential Stuffing in Strapi with Hmac Signatures — how this specific combination creates or exposes the vulnerability

Credential stuffing relies on automated login attempts using breached username and password pairs. When an API endpoint in Strapi uses Hmac Signatures for request authentication but does not enforce additional anti-automation controls, attackers can replay captured signed requests at scale without needing to crack or re-derive the signing secret.

In Strapi, a typical Hmac flow might have the client compute an Hmac over selected request components (HTTP method, path, selected headers, and a timestamp) using a shared secret or a per-user secret. The client sends the signature, key identifier, and the included components in headers. If the endpoint only validates the Hmac and does not enforce rate limiting, nonce/timestamp replay detection, or multi-factor controls, each captured request can be replayed by a bot to perform credential stuffing.

Consider a login endpoint that accepts username and password in the body and requires an Hmac signature over selected headers. An attacker who obtains a valid pair of signed request components can replay those exact headers and body to the endpoint. Because the Hmac is valid and the endpoint does not bind the signature to a single use or a strict nonce cache, the request is accepted. Without protections such as one-time nonces, replay windows, IP-based rate limiting, or device fingerprinting, the attacker can iterate over thousands of credential pairs by replaying the signed requests.

Another scenario involves user-specific secrets where the Hmac key is derived from a user password. If an attacker knows or guesses the username and can obtain a signed request for that user (for example from a different compromised service), they can replay the request to Strapi. Because the Hmac validates without additional checks, the attacker may authenticate as that user. This is particularly risky when Strapi endpoints do not enforce per-request nonces or timestamps, allowing attackers to reuse captured authentication material across accounts.

During a middleBrick scan, such weaknesses are surfaced as findings under BOLA/IDOR and Authentication checks, with references to the OWASP API Top 10 and common implementation pitfalls. The scanner checks whether replay protections, rate limiting, and secure handling of timestamps are present and highlights the absence of these controls when Hmac Signatures are used for authentication in Strapi.

Hmac Signatures-Specific Remediation in Strapi — concrete code fixes

Remediation focuses on preventing replay, binding requests to a narrow context, and ensuring that Hmac verification is coupled with anti-automation and uniqueness controls.

1. Include a server-supplied nonce or timestamp and require uniqueness

Require the server to provide a one-time nonce or a strict timestamp window and ensure the client incorporates it into the Hmac. Validate that each nonce/timestamp is used only once within a short window.

2. Scope the Hmac to the user and the action

Include user identifier and a request-specific context in the signed string to prevent cross-user replay.

3. Enforce rate limiting and anomaly detection

Apply per-identity and per-IP rate limits on authentication endpoints regardless of Hmac validity.

Example Hmac implementation in Strapi (Node.js)

These examples illustrate a safer approach. They are educational and should be adapted to your threat model and compliance requirements.

Server: Generating a signed login response with nonce

const crypto = require('crypto');

// Generate a one-time nonce and store it with the user session (e.g., in Redis or memory)
function generateNonce() {
  return crypto.randomBytes(16).toString('hex');
}

// Build the payload to sign: include method, path, nonce, and a timestamp
function buildStringForSign(method, path, nonce, timestamp) {
  return `${method.toUpperCase()}|${path}|${nonce}|${timestamp}`;
}

// Server endpoint: issue a login response that includes a nonce
async function loginWithHmac(ctx) {
  const { username, password } = ctx.request.body;
  // Validate credentials against Strapi (pseudocode)
  const user = await strapi.db.query('plugin::users-permissions.user').findOne({ where: { username } });
  if (!user || !(await validatePassword(password, user.password))) {
    ctx.status = 401;
    ctx.body = { error: 'Invalid credentials' };
    return;
  }

  const nonce = generateNonce();
  const timestamp = Date.now().toString();
  const secret = process.env.HMAC_SECRET; // stored securely
  const payload = buildStringForSign(ctx.method, '/auth/login', nonce, timestamp);
  const signature = crypto.createHmac('sha256', secret).update(payload).digest('hex');

  // Store nonce with user session and set a short TTL
  await redis.set(`nonce:${nonce}`, user.id, 'EX', 300); // 5 minutes

  ctx.body = {
    user: { id: user.id, username: user.username },
    nonce,
    timestamp,
    signature,
  };
}

Client: Signing a request including nonce and timestamp

function buildStringForSign(method, path, nonce, timestamp) {
  return `${method.toUpperCase()}|${path}|${nonce}|${timestamp}`;
}

async function makeSignedRequest(userCredentials) {
  // First obtain a nonce from the server (login endpoint)
  const res = await fetch('/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(userCredentials),
  });
  const data = await res.json();

  const { nonce, timestamp, signature } = data;
  const secret = 'shared-secret'; // securely stored

  // Now sign a request to a protected endpoint using the received nonce
  const method = 'GET';
  const path = '/api/me';
  const payload = buildStringForSign(method, path, nonce, timestamp);
  const sig = crypto.createHmac('sha256', secret).update(payload).digest('hex');

  const response = await fetch(path, {
    method,
    headers: {
      'x-nonce': nonce,
      'x-timestamp': timestamp,
      'x-signature': sig,
    },
  });
  return response.json();
}

Server: Verifying nonce uniqueness and Hmac in a Strapi middleware

async function verifyHmacWithReplayProtection(ctx, next) {
  const method = ctx.method;
  const path = ctx.path;
  const nonce = ctx.request.headers['x-nonce'];
  const timestamp = ctx.request.headers['x-timestamp'];
  const receivedSignature = ctx.request.headers['x-signature'];

  if (!nonce || !timestamp || !receivedSignature) {
    ctx.status = 400;
    ctx.body = { error: 'Missing authentication headers' };
    return;
  }

  // Reject if timestamp is outside allowed window (e.g., 5 minutes)
  const now = Date.now();
  const ts = parseInt(timestamp, 10);
  if (Math.abs(now - ts) > 5 * 60 * 1000) {
    ctx.status = 401;
    ctx.body = { error: 'Request expired' };
    return;
  }

  // Check nonce replay: ensure nonce has not been used recently
  const userId = await redis.get(`nonce:${nonce}`);
  if (userId) {
    // Already used — reject to prevent replay
    ctx.status = 401;
    ctx.body = { error: 'Replay detected' };
    return;
  }

  // Compute expected signature
  const payload = `${method.toUpperCase()}|${path}|${nonce}|${timestamp}`;
  const secret = process.env.HMAC_SECRET;
  const expected = crypto.createHmac('sha256', secret).update(payload).digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(receivedSignature))) {
    ctx.status = 401;
    ctx.body = { error: 'Invalid signature' };
    return;
  }

  // Mark nonce as used (set with TTL)
  if (userId) {
    await redis.set(`nonce:${nonce}`, 'used', 'EX', 300);
  }

  await next();
}

Additional recommendations

  • Combine Hmac with rate limiting on authentication endpoints to mitigate bulk submission regardless of signature validity.
  • Use short-lived nonces or timestamp windows and reject out-of-window requests.
  • Bind the Hmac scope to user identity and include a per-request context to prevent reuse across users or endpoints.
  • Audit your signing string to ensure it does not omit critical components that an attacker could alter without detection.

Frequently Asked Questions

Can replay attacks still occur if Hmac Signatures are used without nonce or rate limiting?
Yes. Without nonces or replay detection, a valid Hmac-signed request can be replayed by an attacker to authenticate or perform actions as the original user.
Does middleBrick test for Hmac replay weaknesses in Strapi endpoints?
Yes. middleBrick checks for missing nonce/timestamp replay protections and flags missing rate limiting alongside Hmac-based authentication in Strapi scans.