Stack Overflow in Express with Dynamodb
Stack Overflow in Express with Dynamodb — how this specific combination creates or exposes the vulnerability
When an Express service uses DynamoDB as a backend and dynamically builds query parameters from uncontrolled client input, it can create conditions that resemble a stack overflow or resource exhaustion attack pattern, even if DynamoDB does not use a traditional call stack. In this context, “Stack Overflow” refers to application logic that can enter an unbounded recursion or iterative fan-out due to unsafe consumption of user-controlled data, leading to high latency, high consumed request units, or process instability.
Express routes often parse items from the request path or query string and use those values as keys or pagination tokens for DynamoDB operations. If input validation is missing or inconsistent, an attacker can craft deeply nested or extremely large request parameters that cause the application to perform repeated, recursive lookups or construct large scan-like iterations in application logic. Because the scan runs in black-box fashion against the unauthenticated endpoint, middleBrick would flag missing rate limiting and unsafe consumption as concurrent findings.
DynamoDB-specific risks in this scenario include:
- Unbounded fan-out in application code: recursive or iterative calls to
GetItemorQuerydriven by attacker-controlled identifiers, without early termination or depth limits. - High request unit consumption: repeated reads on large keys or indexes can inflate consumed read capacity, leading to throttling or elevated costs.
- Path or query parameter confusion: if Express route parameters map directly to DynamoDB key attributes without normalization or strict allowlists, attackers can probe unexpected partition or sort key combinations.
These patterns are captured by middleBrick’ checks for Input Validation, Rate Limiting, Unsafe Consumption, and BFLA/Privilege Escalation, because the API surface is exposed without authentication during a scan.
Dynamodb-Specific Remediation in Express — concrete code fixes
Secure Express applications using DynamoDB by validating and constraining all inputs before constructing requests, and by enforcing strict limits on recursion and fan-out in application logic. Below are concrete, working examples for Express routes that interact with DynamoDB using the AWS SDK for JavaScript (v3).
1. Validate and constrain path parameters before using them in DynamoDB calls. Use an allowlist for known keys and enforce length/size limits.
import { DynamoDBClient, GetItemCommand } from "@aws-sdk/client-dynamodb";
import express from "express";
const app = express();
const client = new DynamoDBClient({ region: "us-east-1" });
const ALLOWED_TYPES = new Set(["product", "user", "order"]);
app.get("/items/:type/:id", async (req, res) => {
const { type, id } = req.params;
// Input validation: strict allowlist and format checks
if (!ALLOWED_TYPES.has(type)) {
return res.status(400).json({ error: "Invalid type" });
}
if (!/^[a-zA-Z0-9_-]{1,64}$/.test(id)) {
return res.status(400).json({ error: "Invalid id" });
}
const command = new GetItemCommand({
TableName: process.env.DYNAMO_TABLE,
Key: {
pk: { S: `TYPE#${type}` },
sk: { S: `ID#${id}` },
},
});
try {
const response = await client.send(command);
if (!response.Item) {
return res.status(404).json({ error: "Not found" });
}
res.json({ data: response.Item });
} catch (err) {
res.status(500).json({ error: "Internal error" });
}
});
2. Avoid recursive or unbounded fan-out by using a bounded loop and early exit when traversing related items. Prefer Query with a limit over recursive GetItem calls.
app.get("/user-orders/: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 MAX_DEPTH = 3;
let depth = 0;
let lastSk = null;
const results = [];
while (depth < MAX_DEPTH) {
const cmd = new Command(
{
TableName: process.env.DYNAMO_TABLE,
KeyConditionExpression: "pk = :pk AND begins_with(sk, :prefix)",
ExpressionAttributeValues: {
":pk": { S: `USER#${userId}` },
":prefix": { S: lastSk ? `RELATED#${lastSk}` : "RELATED#" },
},
Limit: 10,
},
client
);
const data = await client.send(cmd);
if (!data.Items || data.Items.length === 0) break;
results.push(...data.Items);
lastSk = data.Items[data.Items.length - 1].sk.S;
depth += 1;
if (data.LastEvaluatedKey) {
// If you need pagination on the client side, handle next token explicitly with a client-supplied page parameter
break;
}
}
res.json({ orders: results });
});
3. Enforce server-side rate limiting and capacity controls to mitigate high request unit consumption and resource exhaustion. Use token-bucket style checks before issuing expensive operations.
const RATE_LIMIT = 100; // requests per window
const windowMs = 60_000;
const recent = new Map();
function allowRequest(id) {
const now = Date.now();
const entry = recent.get(id) || { count: 0, start: now };
if (now - entry.start > windowMs) {
entry.count = 1;
entry.start = now;
} else {
entry.count += 1;
}
recent.set(id, entry);
return entry.count <= RATE_LIMIT;
}
app.use((req, res, next) => {
const id = req.ip;
if (!allowRequest(id)) {
return res.status(429).json({ error: "Too many requests" });
}
next();
});
By combining strict input validation, bounded traversal, and rate limiting, you reduce the risk of Stack Overflow-like patterns and resource abuse in Express services that rely on DynamoDB.