HIGH nosql injectiondynamodb

Nosql Injection in Dynamodb

How Nosql Injection Manifests in Dynamodb

Nosql Injection in DynamoDB exploits how the service interprets query parameters. Unlike SQL injection where attackers manipulate SQL syntax, DynamoDB injection targets the query language's object structure and operators.

The most common attack vector involves manipulating FilterExpression, KeyConditionExpression, and ExpressionAttributeValues parameters. DynamoDB's flexible schema allows attackers to inject additional operators or modify existing ones through carefully crafted input.

Consider this vulnerable code pattern:

const userId = req.query.userId; // User-controlled input
const params = {
  TableName: 'Users',
  KeyConditionExpression: 'userId = :userId',
  ExpressionAttributeValues: {
    ':userId': userId
  }
};
const result = await dynamodb.query(params).promise();

An attacker could supply: userId=123 OR attribute_exists(password) to retrieve all items where userId equals 123 OR any item with a password attribute. The injected OR operator bypasses intended filtering.

Another common pattern exploits DynamoDB's between operator:

const startId = req.query.startId;
const endId = req.query.endId;

const params = {
  TableName: 'Orders',
  KeyConditionExpression: 'orderId between :start and :end',
  ExpressionAttributeValues: {
    ':start': startId,
    ':end': endId
  }
};

If an attacker supplies startId=1 and endId=9999 OR 1=1, the query becomes orderId between 1 and 9999 OR 1=1, which evaluates to true for all items.

Attribute value substitution also presents risks. DynamoDB uses placeholders like :value that get replaced in expressions. If input isn't properly sanitized, attackers can manipulate the structure:

// Vulnerable
const status = req.query.status;
const params = {
  TableName: 'Orders',
  FilterExpression: 'contains(status, :status)',
  ExpressionAttributeValues: {
    ':status': status
  }
};

// Malicious input: ' OR contains(status, 'admin' OR 1=1
// Transforms to: contains(status, '' OR contains(status, 'admin' OR 1=1')

The attack succeeds because DynamoDB evaluates the entire expression, not just the substituted value.

Dynamodb-Specific Detection

Detecting NoSQL injection in DynamoDB requires understanding how the service processes expressions and validates input. Traditional SQL injection detection tools don't work because DynamoDB uses a different query language.

middleBrick's DynamoDB scanning specifically tests for injection vulnerabilities by:

  • Analyzing ExpressionAttributeValues for unescaped special characters that could modify query logic
  • Testing FilterExpression and KeyConditionExpression with operator injection payloads
  • Verifying proper escaping of attribute names in ExpressionAttributeNames
  • Checking for hardcoded or predictable attribute values that could be manipulated

Common injection patterns middleBrick detects include:

Injection Type Payload Example Effect
Operator Injection 123 OR 1=1 Bypasses filtering
Attribute Manipulation name OR attribute_exists(password) Access unauthorized attributes
Logical Bypass anything OR true Always returns true
Range Manipulation 1 AND 1=1 Modifies range queries

Manual detection techniques include:

# Test for operator injection
curl -X POST https://api.example.com/orders \
  -H "Content-Type: application/json" \
  -d '{"startId":"1","endId":"9999 OR 1=1"}'

# Test for attribute manipulation
curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d '{"userId":"123 OR attribute_exists(admin_role)"}'

Look for responses that return more data than expected or error messages that reveal query structure. DynamoDB typically returns generic errors, but timing differences can indicate successful injection attempts.

Dynamodb-Specific Remediation

Remediating NoSQL injection in DynamoDB requires a defense-in-depth approach using the service's built-in security features and proper input validation.

1. Strict Input Validation

const { isSafeString, isSafeNumber } = require('./input-sanitizer');

function validateUserId(userId) {
  if (!isSafeString(userId) || userId.includes(' ') || userId.includes(';')) {
    throw new Error('Invalid userId format');
  }
  return userId;
}

function validateOrderId(orderId) {
  const orderIdNum = Number(orderId);
  if (isNaN(orderIdNum) || orderIdNum < 0 || orderIdNum > 999999) {
    throw new Error('Invalid orderId range');
  }
  return orderIdNum;
}

// Usage
const userId = validateUserId(req.query.userId);
const orderId = validateOrderId(req.query.orderId);

2. Parameterized Queries with Whitelisting

const validOperators = ['=', 'between', '>', '<', '>=', '<='];

function createSafeQuery(table, keyCondition, values) {
  const [attr, operator, value] = keyCondition.split(' ');
  
  if (!validOperators.includes(operator)) {
    throw new Error('Invalid operator');
  }
  
  const safeValues = {};
  Object.keys(values).forEach(key => {
    if (typeof values[key] === 'string') {
      safeValues[key] = values[key].replace(/["';]/g, '');
    } else {
      safeValues[key] = values[key];
    }
  });
  
  return {
    TableName: table,
    KeyConditionExpression: keyCondition,
    ExpressionAttributeValues: safeValues
  };
}

// Usage
const params = createSafeQuery(
  'Orders', 
  'orderId between :start and :end', 
  { ':start': startId, ':end': endId }
);

3. IAM Policy Restrictions

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:Query",
        "dynamodb:Scan"
      ],
      "Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/Orders",
      "Condition": {
        "ForAllValues:StringEquals": {
          "dynamodb:Attributes": ["orderId", "userId", "status"]
        }
      }
    }
  ]
}

This policy restricts queries to specific attributes, preventing injection of arbitrary attribute names.

4. Using DynamoDB's Built-in Protection

const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();

// Use strongly typed inputs
function getOrdersByUser(userId, limit = 100) {
  if (typeof userId !== 'string' || userId.length > 255) {
    throw new Error('Invalid userId');
  }
  
  if (typeof limit !== 'number' || limit < 1 || limit > 1000) {
    throw new Error('Invalid limit');
  }
  
  const params = {
    TableName: 'Orders',
    KeyConditionExpression: 'userId = :userId',
    ExpressionAttributeValues: {
      ':userId': userId
    },
    Limit: limit
  };
  
  return dynamodb.query(params).promise();
}

5. Regular Security Scanning

Integrate middleBrick into your CI/CD pipeline to automatically scan DynamoDB endpoints:

# .github/workflows/dynamodb-security.yml
name: DynamoDB Security Scan

on:
  pull_request:
    paths: ['src/dynamodb/**']

jobs:
  security-scan:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Scan DynamoDB endpoints
      run: |
        npm install -g middlebrick
        middlebrick scan https://api.example.com/dynamodb/query

This ensures NoSQL injection vulnerabilities are caught before deployment.

Frequently Asked Questions

How is NoSQL injection in DynamoDB different from SQL injection?
SQL injection manipulates SQL query syntax using keywords like SELECT, WHERE, and OR. DynamoDB injection targets the query language's object structure and operators like between, contains, and attribute_exists. Instead of injecting SQL keywords, attackers inject DynamoDB operators and modify expression logic through carefully crafted input.
Can DynamoDB's IAM policies prevent NoSQL injection?
IAM policies provide defense-in-depth but cannot prevent all NoSQL injection attacks. They can restrict which attributes can be queried and limit query complexity, but injection within allowed attributes is still possible. Combine IAM policies with input validation, parameterized queries, and regular security scanning for comprehensive protection.