Command Injection in Feathersjs with Basic Auth
Command Injection in Feathersjs with Basic Auth — how this specific combination creates or exposes the vulnerability
Command Injection occurs when an attacker can inject and execute arbitrary system commands through an application. In a Feathersjs service that uses Basic Auth for authentication, the risk arises when user-controlled input (e.g., credentials, query parameters, or request body fields) is passed to a shell or system-executing function without proper sanitization or validation.
Feathersjs does not inherently introduce Command Injection, but developers commonly extend services with custom logic that executes shell commands (for example, calling exec, spawn, or child processes) to perform operations such as file manipulation, external tool invocation, or system diagnostics. When Basic Auth is used, the framework typically adds a hook that reads the Authorization header, decodes the base64-encoded credentials, and attaches the parsed username and password to the request object. If these values or any derived data (e.g., username used to construct a filename or command argument) are later used in a shell command without escaping, an attacker can manipulate the credentials to execute arbitrary code.
Consider a Feathersjs service that, after authentication, runs a system command using the provided username to generate a report:
const { exec } = require('child_process');app.use('/reports', {
async find(params) {
const username = params.authentication.strategy === 'basic' ? params.user.username : 'anonymous';
return new Promise((resolve, reject) => {
exec(`generate-report --user ${username}`, (error, stdout, stderr) => {
if (error) return reject(error);
resolve({ stdout, stderr });
});
});
}
});An attacker with valid Basic Auth credentials could supply a username like admin; cat /etc/passwd or $(id). If the input is not sanitized, the shell interprets the semicolon or command substitution, leading to unintended command execution. This exposes sensitive system information or allows further exploitation. Even when authentication succeeds, the combination of trusted-seeming credentials and unchecked command construction creates a pathway for attackers to leverage the application’s own privileges.
The risk is compounded if the service exposes other inputs (such as query parameters or body fields) alongside the Basic Auth context. For example, an endpoint that accepts a path parameter to locate files could allow an authenticated user to traverse directories and inject commands via carefully crafted input. Because Basic Auth is often perceived as a secure transport-layer mechanism, developers may overlook the need for input validation when credentials influence command construction.
Basic Auth-Specific Remediation in Feathersjs — concrete code fixes
Remediation focuses on preventing user-controlled data from reaching the shell and ensuring that commands are constructed safely. The most robust approach is to avoid shell command construction entirely, using built-in Node.js APIs or trusted libraries for file and process operations. When shell execution is unavoidable, inputs must be validated, and commands should be constructed using parameterized methods or strict allowlists.
First, validate and sanitize any data derived from authentication or request parameters. If you must use shell commands, prefer spawn with an array of arguments to prevent the shell from interpreting metacharacters. For example:
const { spawn } = require('child_process');app.use('/reports', {
async find(params) {
const username = params.authentication?.strategy === 'basic' ? params.user.username : 'anonymous';
// Validate username against an allowlist or regex pattern
if (!/^[a-zA-Z0-9_-]+$/.test(username)) {
throw new Error('Invalid username');
}
return new Promise((resolve, reject) => {
const generate = spawn('generate-report', ['--user', username]);
let stdout = '';
let stderr = '';
generate.stdout.on('data', data => stdout += data);
generate.stderr.on('data', data => stderr += data);
generate.on('close', code => {
if (code !== 0) return reject(new Error(`Process exited with code ${code}`));
resolve({ stdout, stderr });
});
});
}
});This approach ensures that the username is treated as a single argument and not subject to shell parsing. Additionally, enforce strict allowlists for usernames and avoid using raw credentials in paths or filenames.
Second, if you rely on Basic Auth hooks, ensure that the authentication strategy does not automatically propagate credentials into command contexts. You can isolate authenticated user data by transforming it before use:
const safeUsername = (params.user?.username || '').replace(/[^a-z0-9]/gi, '_');Finally, consider moving sensitive operations to a separate service with restricted permissions, and use inter-process communication instead of direct shell invocation. These practices reduce the attack surface without requiring changes to the authentication mechanism itself.
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 |