Container Escape in Restify with Dynamodb
Container Escape in Restify with Dynamodb — how this specific combination creates or exposes the vulnerability
A container escape in a Restify service that uses DynamoDB typically arises when the application processes untrusted input and uses it to construct system commands or dynamic SDK parameters that reach the container host. In this stack, an attacker may supply a malicious payload that manipulates how Restify routes requests or how the DynamoDB DocumentClient is invoked, leading to unintended behavior such as probing host paths or spawning processes.
For example, if a route parameter like tableName is directly interpolated into a DynamoDB operation without validation, and that operation is chained with a function that executes shell commands (e.g., via child_process), an attacker can leverage injection to escape the containerized environment. Common patterns include:
- Command injection through unsanitized input fed to AWS CLI or SDK calls that include shell metacharacters.
- Path traversal in file reads/writes triggered by DynamoDB attribute values that reference host paths, enabling interaction with sensitive host files.
- Overly permissive IAM roles assigned to the container allowing the process to execute privileged operations on the host filesystem or runtime.
Because middleBrick tests unauthenticated attack surfaces, it can flag endpoints where DynamoDB inputs indirectly influence process execution or container interaction. The scanner checks for SSRF and unsafe consumption patterns that could enable an attacker to reach the container runtime from outside the intended API boundary.
Dynamodb-Specific Remediation in Restify — concrete code fixes
To reduce the risk of container escape when using DynamoDB in Restify, validate and sanitize all inputs that affect SDK calls or command construction. Use parameterized operations, avoid shell invocation, and enforce strict schema checks.
1. Validate table names and keys before SDK use
Do not trust client-supplied table names. Use an allowlist or strict regex to ensure only expected table names are used.
const validTables = new Set(['users', 'orders', 'products']);
function getTableParam(input) {
if (!validTables.has(input)) {
throw new Error('Invalid table name');
}
return input;
}
const aws = require('aws-sdk');
const docClient = new aws.DynamoDB.DocumentClient();
app.get('/items/:table', (req, res, next) => {
try {
const tableName = getTableParam(req.params.table);
const params = {
TableName: tableName,
Key: { id: req.query.id }
};
docClient.get(params, (err, data) => {
if (err) return next(err);
res.send(data.Item);
});
} catch (err) {
return next(err);
}
});
2. Avoid shell execution when interacting with DynamoDB
Never construct shell commands from DynamoDB attribute values. Use the SDK directly and avoid child_process for AWS operations.
const aws = require('aws-sdk');
const { exec } = require('child_process');
// Unsafe: do not do this
// exec(`aws dynamodb get-item --table-name ${userSuppliedTable}`, (err, stdout) => { ... });
// Safe: use SDK with validated input
const docClient = new aws.DynamoDB.DocumentClient();
app.get('/safe-item', (req, res, next) => {
const params = {
TableName: 'users',
Key: { id: req.query.id }
};
docClient.get(params, (err, data) => {
if (err) return next(err);
res.send(data.Item);
});
});
3. Enforce least privilege and schema validation
Ensure the container’s IAM role only permits required DynamoDB actions on specific resources. Combine this with runtime input validation and output encoding to prevent data leakage that could aid an escape attempt.
const aws = require('aws-sdk');
const { body, validationResult } = require('express-validator');
app.post('/record', [
body('partitionKey').isString().isLength({ min: 1, max: 255 }),
body('sortKey').isString().isLength({ max: 1024 })
], (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).send({ errors: errors.array() });
}
const docClient = new aws.DynamoDB.DocumentClient();
const params = {
TableName: 'app_records',
Item: {
pk: req.body.partitionKey,
sk: req.body.sortKey,
data: req.body.data
}
};
docClient.put(params, (err) => {
if (err) return next(err);
res.status(204).end();
});
});