Brute Force Attack in Dynamodb

How Brute Force Attack Manifests in Dynamodb

In DynamoDB, a brute force attack typically targets the absence of rate limiting on item-level operations, exploiting the service's default behavior of allowing high-velocity requests against a single partition key or across an entire table. Unlike traditional SQL databases where a login form is the primary target, DynamoDB's NoSQL nature and partition-key-based architecture create unique attack surfaces.

Attack Pattern 1: Partition Key Enumeration
An attacker who knows or can guess a partition key format (e.g., user_<numeric_id>, order_<YYYYMMDD>) can systematically probe for existing items. DynamoDB's GetItem and Query operations return null or empty results for non-existent keys without a traditional "record not found" error that might trigger account lockouts. This allows unlimited enumeration attempts. For example:

// Attacker script: enumerate user IDs 1-10000
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB();

for (let i = 1; i <= 10000; i++) {
  const params = {
    TableName: 'Users',
    Key: { 'userId': { S: `user_${i}` } }
  };
  dynamodb.getItem(params, (err, data) => {
    if (!err && data.Item) {
      console.log(`Found user: ${i}`);
      // Exfiltrate data or pivot to other attacks
    }
  });
}

Attack Pattern 2: Filter Expression Bypass via Iteration
When a query uses a filter expression (e.g., email = '[email protected]') but the partition key is broad (e.g., tenantId), an attacker can iterate through all partition key values while applying the filter. DynamoDB charges read capacity units (RCUs) for items scanned before filtering, enabling cost-exhaustion attacks while searching for a specific item.

Attack Pattern 3: Write Amplification via Conditional Writes
Attackers can flood a table with PutItem or UpdateItem requests using the same partition key but different sort keys, or with conditional expressions that fail (e.g., attribute_not_exists(userId)). This consumes write capacity units (WCUs) and can cause throttling for legitimate users. DynamoDB's eventual consistency model means failed conditional writes still incur write costs.

DynamoDB-Specific Contributing Factors:

  • No Native Rate Limiting: DynamoDB provides throttling via provisioned capacity or on-demand limits, but these are coarse-grained (per-table) and not per-API-key or per-IP. An attacker hitting a single partition key can exhaust that partition's throughput without triggering table-level throttling.
  • Error Message Uniformity: DynamoDB returns ResourceNotFoundException for non-existent tables and ConditionalCheckFailedException for failed conditionals, but GetItem on a non-existent key simply returns an empty Item field. This lack of distinguishable error messages prevents account lockout logic.
  • High Request Quotas: Default on-demand limits can be as high as 40,000 RCUs and 40,000 WCUs per table, allowing massive brute force campaigns before cloud-level throttling occurs.

These patterns map directly to OWASP API4:2023 – Unrestricted Resource Consumption and OWASP API5:2023 – Broken Function Level Authorization when combined with missing item-level authorization checks.

Dynamodb-Specific Detection

Detecting brute force vulnerabilities in DynamoDB requires analyzing both the API design and runtime behavior. The primary indicator is the absence of rate limiting mechanisms at the item or partition level.

Manual Detection Steps:

  1. Inspect API Endpoints: Identify endpoints that accept partition key values (e.g., /users/{userId}, /orders?orderId=...). Check if these keys are directly used in DynamoDB GetItem or Query calls without additional throttling.
  2. Test for Uniform Responses: Send requests with valid and invalid partition keys. If responses for non-existent items are indistinguishable (e.g., both return 200 OK with empty data fields), the API is vulnerable to enumeration.
  3. Monitor CloudWatch Metrics: Check ThrottledRequests and SuccessfulRequestLatency for the DynamoDB table. A spike in GetItem or Query requests from a single source IP or user agent indicates potential brute forcing.

Automated Scanning with middleBrick:
middleBrick's Rate Limiting check automatically tests for unrestricted request velocity. When scanning a DynamoDB-backed API endpoint, it will:

  • Send rapid sequences of requests with varying partition key values.
  • Analyze HTTP status codes and response bodies for uniform error handling.
  • Measure response times to detect lack of adaptive throttling.
  • Return a severity score based on request volume tolerated before throttling or error differentiation occurs.

For example, scanning an endpoint like https://api.example.com/users/123 with middleBrick's CLI:

middlebrick scan https://api.example.com/users/123

The generated report will include a Rate Limiting category breakdown. A finding like "No rate limiting detected on user enumeration endpoint; 500 requests/second sustained without throttling" indicates a critical brute force vulnerability. The report also maps this to compliance frameworks: PCI-DSS Requirement 8.1.6 (lockout mechanisms) and OWASP API4:2023.

Key DynamoDB Artifacts to Review in middleBrick Reports:

Finding PatternDynamoDB ContextSeverity
Uniform 200 responses for invalid keysGetItem returns empty Item for non-existent keysHigh
No 429 Too Many Requests observedMissing application-layer rate limiting; relies only on DynamoDB's table-level throttlingCritical
High Query scan count without filterAttacker can iterate partition keys with broad queries, consuming RCUsMedium

Dynamodb-Specific Remediation

Remediation must occur at the application layer, as DynamoDB itself does not provide per-key rate limiting. Implement a token bucket or leaky bucket algorithm in your API layer, and leverage DynamoDB's native features for cost control.

1. Application-Level Rate Limiting with DynamoDB as State Store
Use DynamoDB to track request counts per partition key (or per user/IP) with a Time-to-Live (TTL). This is more scalable than in-memory stores for distributed systems. Example using AWS SDK for JavaScript (v3):

import { DynamoDBClient, PutItemCommand, GetItemCommand } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({});
const ddbDoc = DynamoDBDocumentClient.from(client);

const RATE_LIMIT_TABLE = "ApiRateLimits";
const MAX_REQUESTS = 10; // per minute

// Middleware function before DynamoDB GetItem/Query
export async function checkRateLimit(partitionKey, userId) {
  const now = Math.floor(Date.now() / 1000);
  const windowStart = now - 60; // 60-second window

  // Get current count
  const getParams = {
    TableName: RATE_LIMIT_TABLE,
    Key: { pk: `rate_limit#${partitionKey}`, sk: `user#${userId}` },
  };
  const { Item } = await ddbDoc.get(getParams);

  let count = Item ? parseInt(Item.count) : 0;

  if (count >= MAX_REQUESTS) {
    throw new Error("Rate limit exceeded"); // Return 429 to client
  }

  // Increment count with TTL (expire after 60s)
  const putParams = {
    TableName: RATE_LIMIT_TABLE,
    Item: {
      pk: `rate_limit#${partitionKey}`,
      sk: `user#${userId}`,
      count: count + 1,
      ttl: now + 60,
    },
    ConditionExpression: "attribute_not_exists(pk) OR ttl < :now",
    ExpressionAttributeValues: { ":now": now }
  };

  await ddbDoc.put(putParams);
}

// Usage in your API handler
app.get('/users/:userId', async (req, res) => {
  try {
    await checkRateLimit('user', req.params.userId);
    const data = await getUserFromDynamoDB(req.params.userId);
    res.json(data);
  } catch (err) {
    if (err.message === "Rate limit exceeded") {
      res.status(429).json({ error: "Too many requests" });
    } else {
      res.status(500).json({ error: "Internal server error" });
    }
  }
});

2. Enable DynamoDB Adaptive Capacity
For provisioned-mode tables, enable Adaptive Capacity to automatically increase throughput for partitions experiencing hot traffic. This doesn't prevent brute force but mitigates denial-of-service to other partitions. Configure via AWS Console or CLI:

aws dynamodb update-table \
  --table-name Users \
  --provisioned-throughput ReadCapacityUnits=100,WriteCapacityUnits=100 \
  --adaptive-capacity-settings AdaptiveCapacityEnabled=true

3. Item-Level Authorization with Fine-Grained IAM Policies
Ensure IAM policies restrict access to items based on attributes. For example, allow users to access only items where userId = ${cognito-identity.amazonaws.com:sub}. This doesn't stop brute force but limits the blast radius if an attacker guesses a valid key.

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["dynamodb:GetItem", "dynamodb:Query"],
    "Resource": "arn:aws:dynamodb:*:*:table/Users",
    "Condition": {
      "ForAllValues:StringEquals": {
        "dynamodb:LeadingKeys": ["${cognito-identity.amazonaws.com:sub}"]
      }
    }
  }]
}

4. Uniform Error Responses
Normalize responses so that valid and invalid keys return the same status code and body structure, but log discrepancies server-side. This prevents attackers from using response differences to identify valid keys.

// Always return 200 with empty object for non-existent items
app.get('/users/:userId', async (req, res) => {
  const user = await getUserFromDynamoDB(req.params.userId);
  res.json(user || {}); // {} for not found
});

5. Monitoring and Alerts
Use CloudWatch Alarms on ThrottledRequests and UserErrors for the DynamoDB table. Set up alerts for spikes in GetItem operations from a single source. middleBrick's Pro plan offers continuous monitoring with Slack/Teams alerts when security scores drop, which can catch new brute force vulnerabilities in deployed APIs.

FAQ

Q: Does DynamoDB's on-demand mode automatically prevent brute force attacks?
A: No. On-demand mode scales automatically but still lacks per-key or per-IP rate limiting. An attacker can still send high volumes of requests against a single partition key until the account-level limit is reached, causing throttling for all operations in the region.

Q: How does DynamoDB's partition key design affect brute force risk?
A: If partition keys have low cardinality (e.g., tenantId with only 10 values), an attacker can easily cycle through all partitions. High-cardinality keys (e.g., UUIDs) reduce enumeration feasibility but do not eliminate the need for rate limiting on the API layer.

Meta Description

Learn how brute force attacks target DynamoDB APIs through partition key enumeration and missing rate limiting, with detection methods and remediation code examples.

Risk Summary

Severity: Critical
Category: Rate Limiting