Brute Force Attack in Feathersjs with Dynamodb
Brute Force Attack in Feathersjs with Dynamodb — how this specific combination creates or exposes the vulnerability
A brute force attack against a FeathersJS service backed by DynamoDB typically exploits weak authentication or missing account lockout mechanisms. FeathersJS does not enforce authentication by default; if an endpoint like /authentication or a custom users service allows unbounded login attempts without rate limiting, an attacker can iteratively guess credentials. DynamoDB itself does not introduce the brute force vector, but its behavior under high request rates can amplify risk: without proper throttling on the table or via API Gateway, rapid queries may succeed at a scale that overwhelms the application layer controls.
Consider a typical FeathersJS authentication flow using @feathersjs/authentication-local with a DynamoDB adapter. If the service does not enforce per-user or per-IP rate limits, an attacker can send many POST requests to /authentication with varying passwords. DynamoDB read capacity may be high enough to respond to each attempt quickly, enabling rapid guessing. Additionally, if user enumeration is possible—for example, returning different error messages for missing users versus incorrect passwords—an attacker can enumerate valid accounts and focus brute force efforts on those accounts. Findings from middleBrick scans often highlight such authentication weaknesses alongside missing rate limiting, noting that unauthenticated attack surfaces (black-box scanning) remain exposed when controls are absent.
Specific OWASP API categories relevant here include Broken Object Level Authorization (BOLA) when IDs are predictable, and Authentication Bypass if tokens are issued after trivial guesses. While DynamoDB stores the user records, the vulnerability lives in the API design: lack of progressive delays, account lockout, or CAPTCHA after repeated failures. middleBrick’s checks for Rate Limiting and Authentication will surface these gaps, emphasizing that detection and remediation must be implemented at the application and service layer rather than relying on DynamoDB alone.
Dynamodb-Specific Remediation in Feathersjs — concrete code fixes
Remediation centers on enforcing rate limits, securing authentication flows, and hardening DynamoDB access patterns. Below are concrete code examples for a FeathersJS service using the AWS SDK for JavaScript v3 with DynamoDB.
1. Rate limiting with a token bucket stored in DynamoDB
Implement a custom rate limiting hook that uses a DynamoDB table to track request counts per user or IP. This example uses a partition key of userId and updates a timestamp and count with conditional writes to enforce a threshold.
import { DynamoDBClient, UpdateItemCommand } = require('@aws-sdk/client-dynamodb');
const ddb = new DynamoDBClient({});
const rateLimitHook = options => async context => {
const { userId, ip } = context.params.query;
const key = userId || ip;
const now = Date.now();
const windowMs = 60_000; // 1 minute
const maxRequests = 30;
const cmd = new UpdateItemCommand({
TableName: 'api_rate_limits',
Key: { pk: { S: `user#${key}` } },
UpdateExpression: 'SET #ts = list_append(#ts, :now) REMOVE #old',
ConditionExpression: 'attribute_not_exists(pk) OR #ts_size < :max',
ExpressionAttributeNames: { '#ts': 'timestamps', '#ts_size': 'timestamps_size', '#old': 'old' },
ExpressionAttributeValues: { ':now': { N: now.toString() }, ':max': { N: maxRequests.toString() } },
ReturnValues: 'UPDATED_NEW'
});
try {
await ddb.send(cmd);
// Optionally prune timestamps older than windowMs here
return context;
} catch (err) {
if (err.name === 'ConditionalCheckFailedException') {
const error = new Error('Rate limit exceeded');
error.code = 429;
throw error;
}
throw err;
}
};
// Apply to your Feathers service hooks
const app = require('./app');
app.service('users').hooks({
before: {
create: [rateLimitHook({})]
}
});
2. Secure authentication with hashed passwords and account lockout
Ensure passwords are hashed with a strong algorithm (e.g., bcrypt) and track failed attempts in DynamoDB. On excessive failures, set a lockout timestamp to temporarily block login.
import { DynamoDBClient, GetItemCommand, UpdateItemCommand } = require('@aws-sdk/client-dynamodb');
const bcrypt = require('bcrypt');
const ddb = new DynamoDBClient({});
async function authenticateUser(email, password) {
const getCmd = new GetItemCommand({
TableName: 'users',
Key: { email: { S: email } }
});
const { Item } = await ddb.send(getCmd);
if (!Item) throw new Error('Invalid credentials');
const lockedUntil = new Date(Item.lockedUntil?.S || 0);
if (new Date() < lockedUntil) {
const remaining = Math.floor((lockedUntil - new Date()) / 1000);
const error = new Error('Account temporarily locked');
error.code = 403;
throw error;
}
const match = await bcrypt.compare(password, Item.password.S);
if (!match) {
await recordFailedAttempt(email);
throw new Error('Invalid credentials');
}
// Reset failed attempts on success
await resetFailedAttempts(email);
return { token: 'generated-token' };
}
async function recordFailedAttempt(email) {
const cmd = new UpdateItemCommand({
TableName: 'users',
Key: { email: { S: email } },
UpdateExpression: 'SET failedAttempts = if_not_exists(failedAttempts, :zero) + :inc, lastFailTS = :now',
ConditionExpression: 'attribute_not_exists(email) OR lockedUntil < :now',
ExpressionAttributeValues: {
':zero': { N: '0' },
':inc': { N: '1' },
':now': { N: Date.now().toString() }
}
});
await ddb.send(cmd);
// Check threshold and set lockout
const user = await ddb.send(new GetItemCommand({ TableName: 'users', Key: { email: { S: email } } }));
if (user.Item.failedAttempts.N >= '5') {
const lockCmd = new UpdateItemCommand({
TableName: 'users',
Key: { email: { S: email } },
UpdateExpression: 'SET lockedUntil = :lock',
ExpressionAttributeValues: { ':lock': { S: new Date(Date.now() + 15 * 60_000).toISOString() } }
});
await ddb.send(lockCmd);
}
}
async function resetFailedAttempts(email) {
const cmd = new UpdateItemCommand({
TableName: 'users',
Key: { email: { S: email } },
UpdateExpression: 'REMOVE failedAttempts, lockedUntil'
});
await ddb.send(cmd);
}
3. Use middleware to enforce per-route limits
Combine the above with Feathers hooks to protect authentication endpoints specifically. middleBrick scans can verify that rate limiting hooks are present and that authentication services do not leak enumeration clues.
4. Secure DynamoDB access patterns
Avoid queries that expose timing differences by ensuring consistent read patterns where possible and using projections to minimize data leakage in error messages. Always validate and sanitize inputs before constructing DynamoDB expressions to prevent injection.