Cache Poisoning in Restify with Dynamodb
Cache Poisoning in Restify with Dynamodb — how this specific combination creates or exposes the vulnerability
Cache poisoning occurs when an attacker manipulates cached responses so that subsequent users receive malicious or incorrect data. In a Restify service that uses DynamoDB as a persistence layer, this typically arises when user-supplied input is used both to query DynamoDB and to influence cache keys or cache-control behavior without proper normalization and validation.
Consider a user-profile endpoint implemented with Restify that reads from DynamoDB and caches responses. If the route uses a raw user identifier directly in the cache key while also passing untrusted query parameters to DynamoDB, an attacker can vary the parameter to produce distinct responses that share the same cache entry. For example, an endpoint like /profile?format=detailed may cache the first user’s response under a key derived only from the authenticated user ID. Subsequent requests with a poisoned query parameter, such as /profile?format=detailed&role=admin, may return the cached response even though the expected content should differ, thereby leaking data or misrepresenting permissions.
DynamoDB-specific aspects amplify the risk. If the query uses a FilterExpression that does not enforce strict partition-key scoping, an attacker might leverage crafted parameters to cause the application to read from a different item than intended, and then cache that result under a benign key. In addition, conditional writes or ConditionExpression checks that rely on user input can be bypassed if the cached response is reused, because the validation step is skipped. This can lead to issues such as CWE-74 (Improper Neutralization of Special Elements) or CWE-113 (Improper Neutralization of Input During Web Page Generation) depending on how the cached data is rendered.
To illustrate, a vulnerable Restify handler might look like this, showing the problematic composition:
const restify = require('restify');
const { DynamoDBClient, GetItemCommand } = require('@aws-sdk/client-dynamodb');
const server = restify.createServer();
const db = new DynamoDBClient({});
server.get('/profile', async (req, res, next) => {
const userId = req.query.userId; // unsanitized
const format = req.query.format || 'summary';
// Intentionally vulnerable: cache key depends only on userId, not on format
const cacheKey = `profile:${userId}`;
const cached = cache.get(cacheKey);
if (cached) return res.send(cached);
const cmd = new GetItemCommand({
TableName: 'Users',
Key: { userId: { S: userId } },
FilterExpression: 'format = :f',
ExpressionAttributeValues: { ':f': { S: format } }
});
const item = await db.send(cmd);
cache.set(cacheKey, item);
res.send(item);
return next();
});
Here, the cache key ignores the format parameter, so a poisoned format can cause the wrong data to be served from cache. Moreover, because DynamoDB’s FilterExpression is applied after reading the item, a cached response for one format may be reused for another, enabling information disclosure across different representations of the same resource.
middleBrick can detect such issues by scanning the unauthenticated attack surface of a Restify endpoint, identifying mismatches between cache-key derivation and input parameters, and flagging missing output validation or encoding. Its checks include input validation and data exposure, helping to surface these weaknesses before they are exploited.
Dynamodb-Specific Remediation in Restify — concrete code fixes
To remediate cache poisoning when using DynamoDB with Restify, ensure that cache keys incorporate all significant request dimensions that affect the response, and that DynamoDB queries are scoped strictly by partition key with safe expression construction. Below are concrete fixes aligned with the earlier example.
1) Include request parameters that affect the response in the cache key. Encode them consistently to avoid collisions:
const cacheKey = `profile:${userId}:${format}`;
2) Use strong partition-key scoping and avoid filter-based authorization. Instead of filtering after reading, structure your DynamoDB access patterns so that the partition key includes context such as tenant or user scope:
const cmd = new GetItemCommand({
TableName: 'Users',
Key: {
userId: { S: userId },
format: { S: format } // model a composite key or use a GSI
}
});
3) If you must use FilterExpression, validate and normalize inputs and avoid string concatenation. Use expression attribute values exclusively:
const cmd = new GetItemCommand({
TableName: 'Users',
Key: { userId: { S: userId } },
FilterExpression: '#f = :f',
ExpressionAttributeNames: { '#f': 'format' },
ExpressionAttributeValues: {
':f': { S: format }
}
});
4) Encode cached outputs and validate before use. For example, if returning JSON, ensure you do not inject unsafe content:
const safeItem = JSON.parse(JSON.stringify(item));
cache.set(cacheKey, safeItem);
5) Use middleware in Restify to normalize and validate inputs before they reach handlers:
server.pre(restify.plugins.validateRequest({
contentType: 'application/json',
allowedHeaders: ['authorization'],
schema: {
querystring: {
type: 'object',
properties: {
userId: { type: 'string', pattern: '^[a-zA-Z0-9_-]+$' },
format: { type: 'string', enum: ['summary', 'detailed'] }
},
required: ['userId']
}
}
}));
These steps reduce the risk that a cached response for one set of parameters is incorrectly reused for another. By combining strict cache-key design with precise DynamoDB access patterns, you align with secure coding practices such as those referenced in frameworks like OWASP API Security Top 10.
For ongoing assurance, the middleBrick CLI can be integrated into scripts or the middleBrick GitHub Action can add API security checks to your CI/CD pipeline, failing builds if risk scores drop below your chosen threshold. The dashboard helps track how such fixes improve your API security posture over time.