Formula Injection in Feathersjs with Hmac Signatures
Formula Injection in Feathersjs with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Formula Injection occurs when an attacker can control part of a formula, expression, or configuration that is later evaluated by a downstream system. In FeathersJS applications that use Hmac Signatures for request authentication or internal service identification, this typically manifests when user-supplied input is incorporated into the data used to generate or influence the Hmac without proper validation or escaping.
Consider a FeathersJS service that accepts query parameters to filter or transform data, and those parameters are also included in a payload that is Hmac-signed before being sent to another internal component or external API. If the service trusts the reflected values and does not strictly validate or sanitize them, an attacker can inject crafted data such as __proto__, nested objects, or expression-like strings into the Hmac input. When the downstream component reconstructs or parses the data, it may interpret the injected payload as code, prototype modifications, or configuration directives, leading to unauthorized behavior.
For example, an endpoint that builds an Hmac over a JSON string that includes a user-controlled format or fields parameter can be abused if the parameter is not constrained. An attacker might supply a value like {"fields":"*","__exploit":"extra"} and, depending on how the consuming code parses the Hmac context, cause logic bypasses or data exposure. Because Hmac Signatures rely on deterministic input, any unchecked injection point that changes the signed payload alters verification expectations and can expose the service to privilege escalation or inconsistent authorization checks.
In FeathersJS, this risk is heightened when the framework’s hooks or services dynamically build messages for authentication or event propagation without canonicalization. If an attacker can influence fields that participate in the Hmac generation—such as timestamps, identifiers, or action names—they may shift the effective scope of signed operations. This can lead to BOLA/IDOR-like outcomes where one user’s signed context is reused or misinterpreted by the service, particularly when combined with weak input validation on query or body fields that feed the signing process.
Real-world patterns include accepting user-controlled id or service fields that are embedded into Hmac-signed payloads without strict type and format enforcement. Attackers can probe these endpoints using crafted strings that exploit parser differences, such as numeric coercion or object injection in JavaScript runtimes. Since FeathersJS often integrates with transports like REST and Socket.io, inconsistent validation across transports can create subtle gaps where signed data is constructed differently on each path, enabling injection that bypasses intended access controls.
To detect such issues, scanning tools like middleBrick analyze how user input propagates into Hmac generation logic and whether runtime observations differ from spec-defined expectations. By correlating OpenAPI definitions with executed requests, they highlight mismatches where parameters involved in signing are not adequately constrained, helping teams identify Formula Injection surfaces before they can be exploited in production.
Hmac Signatures-Specific Remediation in Feathersjs — concrete code fixes
Remediation focuses on strict input validation, canonicalization of data used in Hmac generation, and ensuring that no attacker-influenced fields alter the semantic meaning of signed payloads. Below are concrete code examples demonstrating secure practices in FeathersJS.
1. Validate and sanitize all inputs that participate in Hmac creation
Never trust query, body, or route parameters that affect the signed payload. Use a validation layer (for example, using @feathersjs/validation) to enforce strict types and allowed values.
// src/hooks/validate-hmac-input.js
const { iff, isProvider, discard } = require('feathers-hooks-common');
const { validator } = require('@feathersjs/validation');
const schema = {
type: 'object',
properties: {
format: { enum: ['json', 'xml'] },
fields: { type: 'string', pattern: '^[a-zA-Z0-9_,]+$' },
timestamp: { type: 'integer', minimum: 1700000000 }
},
required: ['format', 'fields', 'timestamp'],
additionalProperties: false
};
const validateHmacInput = validator({ body: schema, query: schema });
module.exports = {
before: iff(isProvider('external'), validateHmacInput),
after: discard()
};
2. Canonicalize data before signing to prevent injection-induced variations
Ensure that the data used to compute the Hmac is serialized in a deterministic way (sorted keys, no extra whitespace) so that injection attempts cannot create semantically different but syntactically valid variants.
// src/utils/hmac-sign.js
const crypto = require('core-js/stable/actual/json/stringify');
const createHmac = require('crypto').createHmac;
function canonicalStringify(obj) {
return JSON.stringify(obj, Object.keys(obj).sort());
}
function signPayload(payload, secret) {
const data = canonicalStringify(payload);
return createHmac('sha256', secret).update(data).digest('hex');
}
// Example usage inside a service method
app.service('orders').hooks({
before: {
create: [context => {
const { amount, currency, userId } = context.data;
const payload = { amount, currency, userId, ts: Date.now() };
context.params.headers['x-hmac'] = signPayload(payload, process.env.HMAC_SECRET);
return context;
}]
}
});
3. Isolate Hmac-signed fields from user-controlled data
Do not allow user input to directly set fields that are part of the Hmac computation. Instead, derive those values server-side and only expose non-sensitive identifiers to the client.
// src/hooks/separate-hmac-context.js
module.exports = {
before: {
all: [context => {
// Move Hmac-critical fields out of user-controlled data
const userInput = context.data || context.params.query;
const safeContext = {
amount: Number(userInput.amount),
currency: userInput.currency === 'USD' ? 'USD' : 'USD',
userId: context.params.user.id,
ts: Date.now()
};
context.meta = { safeContext };
return context;
}]
},
after: {
all: [context => {
const { safeContext } = context.meta;
const signature = signPayload(safeContext, process.env.HMAC_SECRET);
context.result = { ...context.result, signature };
return context;
}]
}
};
4. Verify Hmac integrity before processing and reject ambiguous payloads
When consuming signed data from external sources or internal hooks, recompute the Hmac over the canonical representation and compare using a constant-time function to avoid timing attacks. Reject requests where user-influenced fields alter the expected scope of the signed operation.
// src/hooks/verify-hmac.js
const crypto = require('crypto');
function verifyHmac(requestData, receivedSignature, secret) {
const expected = signPayload(requestData, secret);
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(receivedSignature)
);
}
module.exports = {
before: {
create: [context => {
const received = context.params.headers['x-hmac'];
const payload = context.meta.safeContext;
if (!verifyHmac(payload, received, process.env.HMAC_SECRET)) {
throw new Error('Invalid Hmac');
}
return context;
}]
}
};
5. Enforce strict schema alignment between spec and runtime
Use middleBrick’s OpenAPI/Swagger analysis to ensure that parameters involved in Hmac generation are explicitly defined, constrained, and validated at the framework level. This prevents drift between documented behavior and actual implementation, reducing Formula Injection risk.
# .openapi definition excerpt (YAML)
parameters:
- name: format
in: query
schema:
type: string
enum: [json, xml]
- name: fields
in: query
schema:
type: string
pattern: '^[a-zA-Z0-9_,]+$'