Out Of Bounds Write in Express with Dynamodb
Out Of Bounds Write in Express with Dynamodb — how this specific combination creates or exposes the vulnerability
An Out Of Bounds Write occurs when user-controlled data is used to compute an index or key that falls outside intended memory or collection boundaries. In an Express service that uses DynamoDB as a persistence layer, this typically arises from unchecked numeric inputs used as array-like identifiers (e.g., list positions or version counters) that are then stored as item attributes or used in conditional updates. Because DynamoDB does not perform array index boundary checks at the database level, the application must enforce these constraints itself.
Consider an endpoint that reorders items in a list by shifting positions based on a user-supplied index. If the index is not validated against the current list length, an attacker can supply a large or negative integer, causing the application to write to an unintended item or create a new item with a malformed key. This can corrupt data or overwrite attributes that should be immutable, such as an admin flag or a version number. The risk is compounded when the Express route uses conditional writes (e.g., ConditionExpression) that assume a certain attribute range, allowing an attacker to bypass expected constraints.
Another common pattern is using user input to construct map keys that simulate arrays, such as items.0, items.1, etc. If the index is not bounded, an attacker can write to keys that map to sensitive attributes in the same item, effectively modifying fields outside the intended collection. Because DynamoDB stores data as schemaless documents, the database accepts these writes without error, and the vulnerability is only discovered when the corrupted data is read and processed by other parts of the system.
Middleware that deserializes JSON payloads into JavaScript objects can also contribute to the issue. If an Express route directly passes user-supplied arrays into update expressions without validating lengths or index ranges, the resulting DynamoDB update can shift existing items or inject new entries at positions that violate business logic. For example, an attacker might send an array with thousands of elements to consume excessive memory on the application side during reconstruction, or manipulate numeric attributes that act as sequence counters, leading to unpredictable behavior across subsequent operations.
Real-world attack patterns mirror common weaknesses enumerated in the OWASP API Security Top 10, particularly excessive data exposure and improper restrictions on external entity injection. While DynamoDB itself does not execute code, the surrounding Express logic can misinterpret out-of-bounds indices as valid operations, enabling privilege escalation or data corruption. Because the database does not raise errors for malformed numeric indices, the responsibility falls on the application to validate every user-supplied value that influences storage structure.
Instrumenting this attack surface requires examining how indices are derived, stored, and used in conditional expressions. A robust scan, such as the one provided by middleBrick, checks for missing bounds validation on numeric inputs that affect list-like structures and flags unsafe use of user-controlled values in DynamoDB update expressions. This helps identify scenarios where an attacker could exploit unchecked positions to perform unauthorized writes, even when the database layer appears passive.
Dynamodb-Specific Remediation in Express — concrete code fixes
To mitigate Out Of Bounds Writes, enforce strict validation on all numeric indices before they are used in DynamoDB operations. In Express, this means validating incoming request parameters and body fields against expected ranges and ensuring that any list manipulation occurs within known boundaries.
Validation and length checks
Before performing updates or shifts, compute the actual list length from the retrieved item and compare it against user input. Use explicit checks rather than relying on default behavior.
// Example: Validate index against current list length
const AWS = require('aws-sdk');
const dynamo = new AWS.DynamoDB.DocumentClient();
async function getItem(table, key) {
const { Item } = await dynamo.get({ TableName: table, Key: key }).promise();
return Item;
}
async function updateItemWithValidation(table, key, index, newValue) {
const item = await getItem(table, key);
const list = item.numbers || [];
if (index < 0 || index >= list.length) {
throw new Error('Index out of bounds');
}
const params = {
TableName: table,
Key: { id: key.id },
UpdateExpression: 'SET numbers[index] = :val',
ExpressionAttributeNames: { '#n': 'numbers' },
ExpressionAttributeValues: {
':val': newValue,
':index': index
},
ConditionExpression: 'attribute_exists(#n) AND index >= 0 AND index < listSize(#n)',
// Note: listSize is not a native DynamoDB function; this illustrates intent.
// Implement length checks in application code instead.
};
await dynamo.update(params).promise();
return { success: true };
}
Safe array manipulation with bounds enforcement
When reordering or inserting elements, always compute the new structure in memory within known limits and write the entire updated list back using a transaction or conditional expression.
// Example: Reorder elements safely
async function reorderItems(table, key, fromIndex, toIndex) {
const item = await getItem(table, key);
const list = item.tags || [];
if (fromIndex < 0 || fromIndex >= list.length || toIndex < 0 || toIndex >= list.length) {
throw new Error('Invalid reorder indices');
}
const updated = [...list];
const [moved] = updated.splice(fromIndex, 1);
updated.splice(toIndex, 0, moved);
const params = {
TableName: table,
Key: { id: key.id },
UpdateExpression: 'SET #t = :updated',
ExpressionAttributeNames: { '#t': 'tags' },
ExpressionAttributeValues: { ':updated': updated },
ConditionExpression: 'listSize(#t) = :expected',
// This condition is illustrative; use application-side length checks in practice.
};
await dynamo.update(params).promise();
return { success: true };
}
Controlled attribute updates
When attributes are used as counters or sequence numbers, validate their current values before incrementing. Avoid using user input directly in update expressions that modify numeric fields.
// Example: Safe version increment
async function incrementVersion(table, key) {
const item = await getItem(table, key);
const currentVersion = item.version || 0;
const params = {
TableName: table,
Key: { id: key.id },
UpdateExpression: 'SET version = :next',
ExpressionAttributeValues: {
':next': currentVersion + 1
},
ConditionExpression: 'version = :current',
ExpressionAttributeValues: {
':current': currentVersion,
':next': currentVersion + 1
}
};
await dynamo.update(params).promise();
return { success: true };
}
Use middleware for centralized validation
In Express, apply validation middleware before route handlers to ensure consistent checks across endpoints.
// Express middleware example
const validateIndex = (req, res, next) => {
const { index } = req.params;
const numericIndex = parseInt(index, 10);
if (isNaN(numericIndex) || numericIndex < 0) {
return res.status(400).json({ error: 'Invalid index' });
}
req.validatedIndex = numericIndex;
next();
};
app.put('/items/:id/position/:index', validateIndex, async (req, res) => {
try {
await updateItemWithValidation('ItemsTable', { id: req.params.id }, req.validatedIndex, req.body.value);
res.json({ success: true });
} catch (err) {
res.status(400).json({ error: err.message });
}
});
These patterns ensure that all writes to DynamoDB respect intended boundaries and prevent out-of-bounds modifications. By combining runtime checks with careful update expressions, you reduce the risk of data corruption or unintended attribute changes.