Symlink Attack in Dynamodb
How Symlink Attack Manifests in Dynamodb
Symlink attacks in DynamoDB contexts typically exploit path traversal vulnerabilities that allow attackers to manipulate file system references, potentially accessing or modifying data outside intended boundaries. While DynamoDB itself is a managed NoSQL service without traditional file system access, symlink-style attacks emerge when DynamoDB is integrated with application code that handles file operations or when DynamoDB table names are dynamically constructed from user input.
A common manifestation occurs when application code constructs DynamoDB table names or partition keys using unsanitized user input. Consider this vulnerable pattern:
const tableName = req.query.table || 'default';
const params = {
TableName: tableName,
Key: { id: req.query.id }
};
const result = await dynamodb.get(params).promise();
An attacker could manipulate the table parameter to access unauthorized tables or cause application errors. More sophisticated symlink attacks target the relationship between DynamoDB and underlying storage systems when using DynamoDB Streams or Global Tables, where path traversal in stream processing logic could lead to unauthorized data access.
Another attack vector involves DynamoDB's PartiQL queries where user input is directly interpolated:
const query = `SELECT * FROM ${userInput} WHERE id = '${userId}'`;
const result = await dynamodb.executeStatement({ Statement: query }).promise();
This pattern allows attackers to traverse into other tables or modify query logic, effectively creating a symlink-like reference to unauthorized data structures.
Dynamodb-Specific Detection
Detecting symlink-style attacks in DynamoDB applications requires examining both the DynamoDB interactions and the surrounding application logic. middleBrick's scanning engine identifies these vulnerabilities through several DynamoDB-specific checks:
Dynamic Table Name Construction: The scanner analyzes code paths where table names are constructed from user input or configuration variables. It flags patterns like:
TableName: req.params.table || 'users'
TableName: config.prefix + userInput
TableName: `table_${userId}`
Unsafe PartiQL Query Construction: middleBrick detects when user input is directly interpolated into PartiQL statements without proper parameterization:
Statement: `SELECT * FROM ${table} WHERE ${condition}`
Statement: 'SELECT * FROM ' + userInput
Path Traversal in File Operations: When DynamoDB is used alongside file storage, the scanner checks for unsafe file path constructions:
const filePath = path.join(uploadDir, dynamodbItem.filename);
const content = fs.readFileSync(filePath);
middleBrick also examines DynamoDB Stream processing logic for symlink-style vulnerabilities:
streamRecords.forEach(record => {
const tableName = record.eventName === 'INSERT' ? record.dynamodb.NewImage.type.S : 'default';
// Unsafe dynamic table access
});
The scanner provides severity ratings based on the attack surface: direct user input in table names receives a high severity rating, while indirect references through configuration files receive medium severity.
Dynamodb-Specific Remediation
Securing DynamoDB against symlink-style attacks requires strict input validation and architectural patterns that eliminate dynamic references. Here are DynamoDB-specific remediation strategies:
Whitelist Table Access: Instead of allowing dynamic table names, implement a whitelist of permitted tables:
const VALID_TABLES = ['users', 'orders', 'products'];
function getDynamoParams(req) {
const tableName = req.query.table;
if (!VALID_TABLES.includes(tableName)) {
throw new Error('Invalid table access');
}
return {
TableName: tableName,
Key: { id: req.query.id }
};
}
Parameterized PartiQL Queries: Always use parameterized queries instead of string interpolation:
const params = {
Statement: 'SELECT * FROM "Users" WHERE id = ?',
Parameters: [userId]
};
const result = await dynamodb.executeStatement(params).promise();
Input Validation and Sanitization: Implement strict validation for any user input used in DynamoDB operations:
function validateTableName(name) {
const tableNameRegex = /^[a-zA-Z0-9-_]{3,255}$/;
return tableNameRegex.test(name);
}
function validatePartitionKey(key) {
return typeof key === 'string' && key.length > 0 && key.length <= 2048;
}
Safe Stream Processing: When processing DynamoDB Streams, use a mapping table instead of dynamic references:
const STREAM_HANDLERS = {
'users': handleUserStream,
'orders': handleOrderStream
};
async function processStreamRecord(record) {
const tableName = record.eventSourceARN.split('/').pop();
const handler = STREAM_HANDLERS[tableName];
if (!handler) {
console.warn(`No handler for table: ${tableName}`);
return;
}
await handler(record);
}
Least Privilege IAM Policies: Restrict DynamoDB access to only necessary tables and operations:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:Query"
],
"Resource": "arn:aws:dynamodb:region:account:table/users"
}
]
}