Ssrf Server Side in Feathersjs with Dynamodb
Ssrf Server Side in Feathersjs with Dynamodb — how this specific combination creates or exposes the vulnerability
Server-Side Request Forgery (SSRF) in a Feathersjs service that uses DynamoDB as a backend can occur when user-supplied data influences HTTP requests made by server-side code or by an integration layer. In Feathers, services are typically implemented as custom classes with find, get, create, and update methods. If a service method accepts parameters such as a URL or host header and uses them to make an outbound HTTP request—often via a library like axios or Node’s http/https—an attacker can supply a malicious URI that causes the server to interact with internal or unexpected endpoints.
DynamoDB itself does not make SSRF more likely, but the way your Feathers service uses DynamoDB SDK calls can create risk if those calls are chained to untrusted inputs. For example, a service might accept a DynamoDB table name or a key condition parameter derived from user input without strict validation. If the same service also performs outbound HTTP calls (e.g., to validate or enrich data), an attacker could leverage SSRF to reach metadata services (e.g., 169.254.169.254 on EC2), internal services, or cloud instance metadata endpoints. This can lead to exposure of credentials, secrets, or internal network topology.
In a typical Feathersjs app, a vulnerable custom service may look like this:
const axios = require('axios');
class DashboardService {
async find(params) {
const { externalUrl } = params.query;
// Risk: externalUrl is user-controlled and used in an SSRF-prone request
const response = await axios.get(externalUrl, { timeout: 2000 });
const enrichment = response.data;
// DynamoDB call using parameters that should not be directly derived from externalUrl
const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient();
const table = params.query.tableName || 'defaultTable';
const key = params.query.itemId || 'defaultId';
const data = await docClient.get({
TableName: table,
Key: { id: key }
}).promise();
return { enrichment, data };
}
}
module.exports = function () {
const app = this;
app.use('/dashboard', new DashboardService());
};
Here, externalUrl enables SSRF because the service makes an HTTP request to a user-supplied URL. Meanwhile, DynamoDB inputs such as tableName and itemId may be influenced indirectly by the same untrusted context, increasing the blast radius if SSRF is combined with insecure IAM permissions. Attack patterns include probing internal AWS metadata endpoints, bypassing network ACLs, or using SSRF to chain attacks across services. This specific combination increases the likelihood of sensitive data exposure or further compromise within your API surface.
Dynamodb-Specific Remediation in Feathersjs — concrete code fixes
Mitigating SSRF when using DynamoDB in Feathersjs requires strict input validation, separation of trusted data from external lookups, and safe usage of the AWS SDK. Below are concrete, safe patterns you can apply.
1. Validate and whitelist external URLs
Never forward user input directly into HTTP calls. Instead, accept only known, trusted domains or use an allowlist approach.
const axios = require('axios');
function safeEnrichData(url) {
const allowedHosts = ['https://api.example.com', 'https://cdn.example.net'];
const parsed = new URL(url, 'http://localhost');
if (!allowedHosts.includes(parsed.origin)) {
throw new Error('External origin not allowed');
}
return axios.get(url, { timeout: 2000 });
}
class DashboardService {
async find(params) {
const { externalUrl } = params.query;
const enrichment = externalUrl ? await safeEnrichData(externalUrl) : null;
// Proceed with DynamoDB call using validated, non-external inputs
const data = await fetchDynamoItem(params.query.tableName, params.query.itemId);
return { enrichment, data };
}
}
2. Avoid deriving DynamoDB table names or keys from untrusted input
Use constant table names and map user-facing identifiers to internal keys using a trusted lookup or configuration.
const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient();
const TABLE_MAP = {
users: 'prod-users-table',
products: 'prod-products-table'
};
async function fetchDynamoItem(tableKey, itemId) {
const tableName = TABLE_MAP[tableKey];
if (!tableName) {
throw new Error('Invalid table key');
}
// Ensure itemId is a safe string pattern (e.g., UUID or integer)
if (!/^[a-zA-Z0-9_-]+$/.test(itemId)) {
throw new Error('Invalid item identifier');
}
const data = await docClient.get({
TableName: tableName,
Key: { id: itemId }
}).promise();
return data.Item;
}
class DashboardService {
async find(params) {
const enrichment = params.query.externalUrl
? await safeEnrichData(params.query.externalUrl)
: null;
const item = await fetchDynamoItem(params.query.tableKey, params.query.itemId);
return { enrichment, item };
}
}
3. Apply least-privilege IAM and isolate network paths
Ensure the Lambda or EC2 instance role used by DynamoDB has only the permissions needed for the specific table and actions. Avoid allowing outbound traffic to the internet from functions that process user input unless strictly necessary. If you must make external calls, place them behind a controlled proxy with egress filtering.
4. Use Feathers hooks for centralized validation
Leverage Feathers hooks to sanitize and validate query and body parameters before they reach your service methods.
const { iff, isProvider } = require('feathers-hooks-common');
const { sanitizeQuery } = require('./sanitizers');
const beforeHooks = {
before: {
all: [
iff(isProvider('external'), sanitizeQuery)
],
find: []
}
};
// In your service initialization
app.use('/dashboard', new DashboardService(), beforeHooks);
// Example sanitizer
function sanitizeQuery() {
return async context => {
const query = context.query || {};
if (query.externalUrl) {
const allowedHosts = ['https://api.example.com'];
const u = new URL(query.externalUrl, 'http://localhost');
if (!allowedHosts.includes(u.origin)) {
throw new Error('Blocked: external origin not allowed');
}
}
// Normalize tableKey and itemId
context.query.tableKey = query.tableKey || 'users';
context.query.itemId = query.itemId || '';
return context;
};
}
By combining input validation, strict allowlists, and safe DynamoDB usage patterns, you reduce the risk of SSRF while maintaining the ability to enrich data and interact with your DynamoDB backend securely in Feathersjs.