HIGH injection flawsexpressdynamodb

Injection Flaws in Express with Dynamodb

Injection Flaws in Express with Dynamodb — how this specific combination creates or exposes the vulnerability

Injection flaws occur when an attacker can send data that changes the structure of a command or query. In Express applications that use DynamoDB, this typically happens when request input is concatenated into DynamoDB API parameters such as Key, FilterExpression, or ConditionExpression. Because DynamoDB uses its own expression syntax, unsanitized user input can modify query logic, bypass intended access controls, or cause unintended attribute lookups.

Consider an Express route that builds a GetItem request from URL parameters:

app.get('/user/:id', async (req, res) => {
  const { id } = req.params;
  const params = {
    TableName: 'users',
    Key: {
      userId: { S: id }
    }
  };
  const result = await docClient.get(params).promise();
  res.json(result.Item);
});

If id is used directly to construct the DynamoDB key structure without validation, an attacker cannot inject into the key format itself in this example because the SDK handles attribute value typing. However, injection risk increases when building expressions. For example, a search endpoint that uses FilterExpression with concatenated input enables expression injection:

app.get('/search', async (req, res) => {
  const { status } = req.query;
  const params = {
    TableName: 'orders',
    FilterExpression: `orderStatus = :status`,
    ExpressionAttributeValues: {
      ':status': { S: status }
    }
  };
  const data = await docClient.scan(params).promise();
  res.json(data.Items);
});

An attacker can supply status as "active" OR attribute_exists(/* injected */). Although this specific example may be limited by how ExpressionAttributeValues are used, more complex scenarios where expression parts are concatenated enable attackers to bypass authorization checks or extract unintended data. A common pattern that is vulnerable is building KeyConditionExpression for queries on non-key attributes or using string interpolation for TableName or IndexName, which can lead to metadata exposure or confused deputy issues.

Another injection surface is attribute names. If an application includes user-controlled attribute names in expressions, an attacker can probe for sensitive attributes or cause errors that reveal schema information:

app.get('/items/:attribute', async (req, res) => {
  const { attribute } = req.params;
  const params = {
    TableName: 'products',
    KeyConditionExpression: `${attribute} = :val`,
    ExpressionAttributeValues: { ':val': { S: 'widget' } }
  };
  const result = await docClient.query(params).promise();
  res.json(result.Items);
});

Because DynamoDB expression syntax supports functions like begins_with and operators such as <>, attackers can manipulate expressions to always evaluate to true or to reference attributes that should be hidden. This maps to the OWASP API Top 10 category API1:2023-Broken Object Level Authorization when access controls are bypassed via expression logic, and can be detected by middleBrick’s BOLA/IDOR and Property Authorization checks.

DynamoDB’s strongly typed request format does not eliminate expression injection; it changes the injection surface. Attackers who can influence expression text, attribute names, or table references can bypass authorization, access unauthorized data, or cause service disruptions through malformed expressions. These flaws are especially dangerous when combined with overly permissive IAM policies, as the impact of a successful injection may be broader than expected.

Dynamodb-Specific Remediation in Express — concrete code fixes

Remediation focuses on avoiding expression concatenation with untrusted input and validating attribute names and table references against a strict allowlist. Use the SDK’s built-in expression builders and never directly interpolate user input into expression strings.

1) Use ExpressionAttributeNames and ExpressionAttributeValues for all dynamic components, and keep expression text static:

app.get('/search', async (req, res) => {
  const { status } = req.query;
  const params = {
    TableName: 'orders',
    KeyConditionExpression: 'orderStatus = :statusVal',
    ExpressionAttributeNames: {
      '#status': 'orderStatus'
    },
    ExpressionAttributeValues: {
      ':statusVal': { S: status }
    }
  };
  const data = await docClient.scan(params).promise();
  res.json(data.Items);
});

2) Validate attribute names against a known set before using them in expressions. This prevents attackers from probing arbitrary attributes:

const ALLOWED_ATTRIBUTES = new Set(['id', 'name', 'category', 'createdAt']);

app.get('/items/:attribute', async (req, res) => {
  const { attribute } = req.params;
  if (!ALLOWED_ATTRIBUTES.has(attribute)) {
    return res.status(400).json({ error: 'Invalid attribute' });
  }
  const params = {
    TableName: 'products',
    KeyConditionExpression: '#attr = :val',
    ExpressionAttributeNames: {
      '#attr': attribute
    },
    ExpressionAttributeValues: {
      ':val': { S: 'widget' }
    }
  };
  const result = await docClient.query(params).promise();
  res.json(result.Items);
});

3) For key-based lookups, validate partition and sort key values against type and length constraints rather than trying to sanitize expression syntax:

app.get('/user/:userId', async (req, res) => {
  const { userId } = req.params;
  if (!/^[a-zA-Z0-9_-]{1,64}$/.test(userId)) {
    return res.status(400).json({ error: 'Invalid userId' });
  }
  const params = {
    TableName: 'users',
    Key: {
      userId: { S: userId },
      accountId: { S: req.account.id }
    }
  };
  const result = await docClient.get(params).promise();
  res.json(result.Item || {});
});

4) Avoid using user input to select table or index names. If dynamic table names are required, map user input to a predefined table name using a strict allowlist:

const TABLE_MAP = {
  'prod': 'orders-prod',
  'staging': 'orders-staging'
};

app.get('/orders/:env', async (req, res) => {
  const { env } = req.params;
  const tableName = TABLE_MAP[env];
  if (!tableName) {
    return res.status(400).json({ error: 'Invalid environment' });
  }
  const params = {
    TableName: tableName,
    KeyConditionExpression: 'orderDate = :date',
    ExpressionAttributeValues: { ':date': { S: '2023-01-01' } }
  };
  const data = await docClient.scan(params).promise();
  res.json(data.Items);
});

5) Enable DynamoDB Streams and use middleware to detect unexpected patterns in application behavior, but remember that detection complements prevention. Combine these practices with runtime security checks available in platforms like middleBrick to identify expression injection attempts during automated scans.

By treating DynamoDB expression components as structured inputs rather than raw query strings, you reduce the risk of injection while preserving the flexibility of conditional queries and scans. These patterns align with secure coding practices for NoSQL databases and help meet compliance requirements mapped by frameworks such as OWASP API Top 10 and SOC2 controls.

Frequently Asked Questions

Can DynamoDB injection be detected by scanning Express endpoints that use expression concatenation?
Yes. middleBrick scans identify expression injection risks in Express APIs that build DynamoDB FilterExpression, KeyConditionExpression, or other expressions with untrusted input, highlighting insecure patterns and providing remediation guidance.
Does using ExpressionAttributeNames fully prevent injection in DynamoDB expressions?
Using ExpressionAttributeNames helps prevent attribute name injection, but it does not protect against injection via expression text or values. You must validate and parameterize all parts of the expression and avoid concatenating untrusted input.