Privilege Escalation in Dynamodb
How Privilege Escalation Manifests in DynamoDB
Privilege escalation in DynamoDB usually occurs when an IAM principal (user, role, or service) gains the ability to perform actions on data it should not be able to access. The most common vector is an overly permissive IAM policy that grants dynamodb:PutItem, dynamodb:UpdateItem, or dynamodb:DeleteItem on a wildcard resource (*) or on a table name pattern that includes tables the principal should not touch.
Consider a serverless Lambda function that processes user uploads. The function’s execution role might have a policy like:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["dynamodb:PutItem", "dynamodb:UpdateItem"],
"Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/*"
}
]
}
With this policy, the function can write to any table in the account, including tables that store administrative data, secrets, or logs. An attacker who can invoke the function (e.g., via an exposed API Gateway endpoint) can escalate privileges by inserting or modifying items in those tables, potentially altering application logic, exfiltrating data, or creating a backdoor.
Another DynamoDB‑specific pattern involves misuse of BatchWriteItem or TransactWriteItems. If a role permits dynamodb:BatchWriteItem on a specific table but the condition keys are missing, an attacker can include PutRequest entries for other tables in the same batch, bypassing table‑level restrictions.
Fine‑grained access controls that rely on condition keys such as dynamodb:LeadingKeys or dynamodb:Attributes can also be bypassed when the application fails to validate user‑supplied keys before constructing the request. For example, a service that builds a Key object directly from a user‑provided userId without checking that the userId matches the authenticated identity can let a user write to another user’s partition.
DynamoDB‑Specific Remediation
The most effective remediation is to apply the principle of least privilege at the IAM level and to enforce fine‑grained access controls within DynamoDB requests. Below are concrete steps and code examples using the AWS SDK for JavaScript (v3).
1. Scope IAM policies to specific resources
Replace wildcard resource ARNs with explicit table ARNs. If the function only needs to write to UserItems:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["dynamodb:PutItem", "dynamodb:UpdateItem"],
"Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/UserItems"
}
]
}
If multiple tables are needed, list each ARN separately rather than using a wildcard.
2. Use condition keys to limit accessible attributes and partition keys
When the application should only allow a user to write items where the partition key matches their identity, add a condition:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["dynamodb:PutItem"],
"Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/UserItems",
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:LeadingKeys": ["${cognito-identity.amazonaws.com:sub}"]
}
}
}
]
}
This condition ensures that the pk (or whatever attribute is defined as the leading key) must equal the authenticated user’s sub.
3. Validate user input before constructing DynamoDB requests
Never trust client‑supplied keys. In the Lambda handler, derive the key from the authenticated context:
import { DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb";
export const handler = async (event) => {
const userId = event.requestContext.authorizer.claims.sub; // from Cognito JWT
const { itemName, itemValue } = JSON.parse(event.body);
const client = new DynamoDBClient({});
const command = new PutItemCommand({
TableName: "UserItems",
Item: {
pk: { S: userId }, // enforced by server, not from client
sk: { S: `item#${itemName}` },
value: { S: itemValue }
}
});
try {
await client.send(command);
return { statusCode: 200, body: JSON.stringify({ message: "Saved" }) };
} catch (err) {
return { statusCode: 500, body: JSON.stringify({ error: err.message })};
}
};
By setting pk server‑side, the client cannot influence which partition is written to, eliminating the ability to write to another user’s data.
4. Leverage DynamoDB Transactions for atomicity when multiple tables are involved
If a legitimate operation must write to two tables, use TransactWriteItems with explicit table names and verify permissions on each table separately. This prevents an attacker from slipping in an extra PutRequest for an unintended table.
After applying these controls, re‑run middleBrick to confirm that the finding no longer appears. The scanner will now return a 403 Forbidden or ValidationException for attempts to access unauthorized tables, indicating that the privilege escalation vector has been closed.