HIGH credential stuffingsailsdynamodb

Credential Stuffing in Sails with Dynamodb

Credential Stuffing in Sails with Dynamodb — how this combination creates or exposes the vulnerability

Credential stuffing is an automated attack where valid credentials obtained from one breach are reused to access other services. When Sails.js applications use Amazon DynamoDB as the user store without additional protections, several design patterns can unintentionally enable or amplify this risk.

Sails is convention-driven and often relies on model-based interactions. If the authentication logic performs username/email lookups via DynamoDB without rate-limiting or adaptive controls, attackers can run large credential lists with minimal per-request cost. DynamoDB’s high request throughput can allow many attempts in a short window if the application does not enforce strict request governance.

A typical vulnerable Sails controller might look like this, where a login action queries DynamoDB directly by email:

// api/controllers/AuthController.js
const AWS = require('aws-sdk');
const dynamo = new AWS.DynamoDB.DocumentClient();

module.exports = {
  login: async function (req, res) {
    const { email, password } = req.allParams();
    const params = {
      TableName: process.env.USER_TABLE,
      KeyConditionExpression: 'email = :email',
      ExpressionAttributeValues: { ':email': email }
    };
    try {
      const data = await dynamo.query(params).promise();
      if (data.Count && data.Items[0].password === password) {
        return res.ok({ token: 'abc123' });
      }
      return res.unauthorized('Invalid credentials');
    } catch (err) {
      return res.serverError(err);
    }
  }
};

This pattern exposes issues:

  • No per-identity rate limiting: each email is queried independently, allowing high request volume against common emails.
  • Timing differences: DynamoDB conditional writes or existence checks can leak whether an email exists via response time variance.
  • Weak password comparison: plaintext comparison in application code is unsafe and bypassable via timing attacks.
  • Missing account lockout or suspicious behavior detection: DynamoDB does not enforce throttling; controls must be implemented in the app layer.

Additionally, Sails adapters and policies may inadvertently expose authentication endpoints to unauthenticated access if route policies are not strict. In multi-tenant setups, misconfigured DynamoDB queries can scan across partitions, making enumeration easier for attackers who understand the access patterns.

Credential stuffing against Sails+Dynamodb is effective when request throttling, strong password storage, and anomaly detection are absent. Attackers use lists of email/password pairs, iterate quickly through the login endpoint, and exploit DynamoDB’s scalability to stay under naive rate limits.

Dynamodb-Specific Remediation in Sails — concrete code fixes

To mitigate credential stuffing in Sails with DynamoDB, implement layered controls: robust password storage, per-identity rate limiting, existence obfuscation, and monitoring. Below are concrete, executable patterns you can adopt.

1. Secure password storage with bcrypt

Never compare passwords directly. Hash with a work factor and compare hashes in constant time.

const bcrypt = require('bcrypt');
const HASH_ROUNDS = 12;

async function verifyPassword(input, storedHash) {
  return await bcrypt.compare(input, storedHash);
}

// When creating or updating a user:
const hash = await bcrypt.hash(plainPassword, HASH_ROUNDS);
// store hash in DynamoDB

2. Parameterized query with existence-safe responses

Use a fixed hash for non-existent users to reduce timing variance. Fetch a single item by partition key only (avoid key-condition expressions that enumerate).

const params = {
  TableName: process.env.USER_TABLE,
  Key: { email: email } // assuming email is the partition key
};
const data = await dynamo.get(params).promise();
const user = data.Item;
const match = user && await verifyPassword(password, user.passwordHash);
if (!match) {
  // Always consume similar time: compute a dummy hash to hide existence
  await bcrypt.hash('dummy', 10);
  return res.unauthorized('Invalid credentials');
}

3. Per-identity rate limiting using DynamoDB conditional writes

Track failed attempts per identity with a TTL attribute. Use conditional writes to atomically increment and enforce limits.

const now = Date.now();
const FIVE_MINUTES = 5 * 60 * 1000;

// Attempt to record a failed login (atomic counter with TTL)
const rateParams = {
  TableName: 'auth_risk',
  Item: {
    identity: email,
    lastFailAt: now,
    expiresAt: now + FIVE_MINUTES * 1000,
    attempts: 1
  },
  ConditionExpression: 'attribute_not_exists(identity)',
  ReturnValues: 'ALL_OLD'
};

try {
  await dynamo.put(rateParams).promise();
} catch (condErr) {
  // Item exists, increment atomically
  const updateParams = {
    TableName: 'auth_risk',
    Key: { identity: email },
    UpdateExpression: 'SET attempts = attempts + :one, lastFailAt = :now',
    ConditionExpression: 'attempts < :max',
    ExpressionAttributeValues: { ':one': 1, ':now': now, ':max': 10 },
    ReturnValues: 'UPDATED_NEW'
  };
  try {
    await dynamo.update(updateParams).promise();
  } catch (updateErr) {
    // Condition failed — attempts >= max
    return res.tooManyRequests('Too many attempts, try later');
  }
}

4. Global rate limiting and anomaly detection

In addition to per-identity limits, enforce a global cap on authentication endpoints using a sliding window stored in DynamoDB or an external cache. Log suspicious patterns (many identities from one IP) and temporarily elevate review for those identities.

5. Secure API surface via policies

Ensure Sails policies do not expose authentication actions to unauthenticated routes inadvertently. Use strict allow/deny rules and avoid broad wildcard policies that bypass controls.

middleBadge scans can validate these patterns by checking for unsafe password handling, missing rate-limiting attributes, and overly permissive route policies. For teams needing continuous oversight, middleBrick Pro provides ongoing monitoring and CI/CD integration to prevent regressions in Sails+Dynamodb deployments.

Frequently Asked Questions

Does DynamoDB expose credential stuffing risks by default?
DynamoDB does not introduce inherent risks, but application design determines exposure. High throughput and key-based access can enable rapid enumeration if the app lacks per-identity rate limits and secure password handling.
Can middleBrick detect weak password storage in DynamoDB-backed Sails apps?
Yes, middleBrick scans for insecure authentication patterns, including plaintext password comparisons and missing adaptive hashing, and maps findings to frameworks like OWASP API Top 10 and SOC2 controls.