Format String in Express with Dynamodb
Format String in Express with Dynamodb — how this specific combination creates or exposes the vulnerability
A format string vulnerability occurs when user-controlled input is passed directly into a function that interprets format specifiers (for example printf-style functions). In an Express route that builds parameters for a DynamoDB operation, this can happen when constructing request parameters or logging data that includes unchecked user input. Consider an endpoint that forwards a table name or key attribute based on a request query value:
app.get('/user', (req, res) => {
const userId = req.query.id;
const params = {
TableName: 'Users',
Key: {
userId: userId
}
};
docClient.get(params, (err, data) => {
if (err) console.log('Error:', err);
else res.json(data.Item);
});
});
If the logging or error handling layer in your application uses unsafe string interpolation to emit logs, and the logging function internally uses a formatting routine that processes format specifiers, an attacker can supply format specifiers such as %s, %d, %n via the id query parameter. In a Node.js context, this can occur if, for example, a custom logger or a lower-level library uses util.format or console.log with user input embedded in the message string. An attacker could attempt:
GET /user?id=%s%d%n
Depending on the logging implementation and runtime, %n can write arbitrary memory addresses, leading to information disclosure or potential code execution when combined with other memory corruption issues. Even in pure JavaScript, format string patterns can expose internal state through verbose error messages or logs, revealing DynamoDB request structures, table names, or key schemas that aid further exploitation such as BOLA/IDOR. Additionally, if the Express app passes user input into DynamoDB condition expressions or projection expressions built via string concatenation, malformed input can change the semantics of the expression, leading to unexpected data exposure or bypass of intended access controls.
Dynamodb-Specific Remediation in Express — concrete code fixes
To prevent format string issues when working with Express and DynamoDB, ensure that user input is never used to construct format strings or interpolated into logging and error reporting. Always validate and sanitize inputs before they reach DynamoDB parameter construction, and use parameterized APIs rather than string building.
1. Safe DynamoDB Get with input validation
Validate the userId to allow only expected characters and length, and avoid injecting it into format-style logging:
const userIdPattern = /^[a-zA-Z0-9_-]{1,64}$/;
app.get('/user', (req, res) => {
const userId = req.query.id;
if (!userIdPattern.test(userId)) {
return res.status(400).json({ error: 'Invalid user ID' });
}
const params = {
TableName: 'Users',
Key: {
userId: userId
}
};
docClient.get(params, (err, data) => {
if (err) {
console.error('DynamoDB get failed', { err_code: err.code, table: 'Users', key: 'userId' });
return res.status(500).json({ error: 'Internal server error' });
}
res.json(data.Item || {});
});
});
2. Safe Update with expression builders
When updating an item, use DynamoDB’s expression parameter objects instead of concatenating user input into expression strings. This avoids both format string risks and injection into update logic:
app.patch('/user/email', (req, res) => {
const userId = req.body.userId;
const newEmail = req.body.email;
if (!userIdPattern.test(userId) || !isValidEmail(newEmail)) {
return res.status(400).json({ error: 'Invalid input' });
}
const params = {
TableName: 'Users',
Key: { userId: userId },
UpdateExpression: 'set #em = :email',
ExpressionAttributeNames: { '#em': 'email' },
ExpressionAttributeValues: { ':email': newEmail },
ReturnValues: 'UPDATED_NEW'
};
docClient.update(params, (err, data) => {
if (err) {
console.error('Update failed', { err_code: err.code, table: 'Users' });
return res.status(500).json({ error: 'Update failed' });
}
res.json(data.Attributes);
});
});
3. Logging and error handling hygiene
Ensure logging utilities do not use format specifiers on raw user input. Prefer structured logging with JSON objects:
const logger = {
info: (msg, obj) => console.log(JSON.stringify({ level: 'info', msg, ...obj })),
error: (msg, obj) => console.error(JSON.stringify({ level: 'error', msg, ...obj }))
};
app.use((err, req, res, next) => {
logger.error('Unhandled error', { error: err.message, stack: err.stack });
res.status(500).json({ error: 'Internal server error' });
});
By combining input validation, expression-based DynamoDB operations, and structured logging, you mitigate format string risks while maintaining correct interaction with DynamoDB from Express.