Command Injection in Feathersjs
How Command Injection Manifests in Feathersjs
Command injection vulnerabilities in Feathersjs applications typically arise when user-controlled data flows directly into system commands without proper sanitization. Feathersjs, being a lightweight Node.js framework, doesn't inherently protect against command injection—developers must implement these safeguards themselves.
The most common pattern occurs in Feathersjs services that use Node.js's child_process module to execute shell commands. Consider this vulnerable service implementation:
const { exec } = require('child_process');
class FileService {
async getFileMetadata(context) {
const { filename } = context.params.query;
// VULNERABLE: Direct interpolation of user input
const command = `file -b "${filename}"`;
return new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => {
if (error) return reject(error);
resolve({ metadata: stdout.trim() });
});
});
}
}
module.exports = FileService;In this example, an attacker could pass filename=test.txt; rm -rf / as the query parameter, causing the shell to execute both the file command and the destructive rm command.
Another Feathersjs-specific scenario involves using the app.setup() method or custom hooks that execute shell commands during service initialization or data processing. For instance:
const { execSync } = require('child_process');
class ImportService {
async importData(context) {
const { filePath } = context.data;
// VULNERABLE: execSync with interpolated user input
const output = execSync(`python3 import_script.py "${filePath}"`);
return { success: true, output: output.toString() };
}
}Feathersjs's event-driven architecture can also create command injection opportunities when event data is used in shell commands. A hook that processes events might look like:
const { exec } = require('child_process');
module.exports = function() {
return async (context) => {
const { type, payload } = context.dispatch;
// VULNERABLE: Event data used in shell command
if (type === 'process_file') {
exec(`process_file.sh "${payload.filePath}"`);
}
};
};The framework's flexibility with service adapters can also lead to command injection when integrating with external systems. For example, a custom database adapter might use shell commands for data operations:
class ShellDBAdapter {
constructor(config) {
this.config = config;
}
async find(params) {
// VULNERABLE: Query parameters interpolated into shell command
const query = params.query || {};
const command = `sqlite3 ${this.config.dbPath} "SELECT * FROM ${query.table || 'default'}"`;
return new Promise((resolve, reject) => {
exec(command, (error, stdout) => {
if (error) return reject(error);
resolve(JSON.parse(stdout));
});
});
}
}These patterns are particularly dangerous in Feathersjs because the framework's convention-over-configuration approach means developers often write custom code without built-in security protections. The framework provides no automatic escaping or validation for shell commands, leaving this responsibility entirely to the developer.
Feathersjs-Specific Detection
Detecting command injection vulnerabilities in Feathersjs applications requires a combination of static code analysis and dynamic testing. For static analysis, look for these patterns in your codebase:
// Patterns to search for:
// 1. Direct use of exec/execSync with string interpolation
const { exec } = require('child_process');
exec(`command ${userInput}`);
// 2. execSync with concatenated strings
execSync('command ' + userInput);
// 3. exec with template literals containing variables
exec(`command ${variable}`);
// 4. exec with path traversal vulnerabilities
exec(`ls ${__dirname}/${userInput}`);
Using middleBrick's CLI tool, you can scan your Feathersjs API endpoints for command injection vulnerabilities:
npm install -g middlebrick
middlebrick scan https://your-feathers-api.com
middleBrick performs black-box scanning that tests for command injection by sending payloads designed to trigger shell command execution. The scanner tests for common injection patterns including:
- Command chaining with semicolons, ampersands, and pipes
- Background execution with ampersands
- File redirection with greater/less than symbols
- Command substitution with backticks and $()
For Feathersjs applications specifically, middleBrick's scanner examines:
# Scan specific Feathersjs endpoints
middlebrick scan https://api.example.com/messages
middlebrick scan https://api.example.com/users
# Scan with detailed output
middlebrick scan https://api.example.com --output json --verbose
The scanner's findings include severity ratings based on the potential impact and likelihood of exploitation. For command injection, middleBrick tests the unauthenticated attack surface, which is particularly relevant for Feathersjs APIs that might expose administrative endpoints without proper authentication.
middleBrick's LLM/AI security module also checks for AI-specific command injection scenarios, such as when user prompts are passed to shell commands in AI-powered Feathersjs services. This includes testing for:
- System prompt leakage that could reveal command injection vulnerabilities
- Prompt injection that modifies shell commands
- AI agent tool execution that might invoke shell commands
For continuous monitoring, the middleBrick GitHub Action can be configured to scan your Feathersjs API in CI/CD pipelines:
name: API Security Scan
on: [push, pull_request]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run middleBrick Scan
run: |
npx middlebrick scan https://staging.your-feathers-app.com
continue-on-error: true
- name: Fail on High Risk
if: failure()
run: exit 1
This ensures that any command injection vulnerabilities introduced in new code are caught before deployment to production.
Feathersjs-Specific Remediation
Remediating command injection vulnerabilities in Feathersjs requires both immediate fixes and architectural changes to prevent future issues. The primary defense is eliminating shell command execution where possible and using safer alternatives.
For file operations that might currently use shell commands, use Node.js's built-in fs module:
const fs = require('fs').promises;
class SafeFileService {
async getFileMetadata(context) {
const { filename } = context.params.query;
// Validate filename: allow only alphanumeric, hyphen, underscore, dot
if (!/^[a-zA-Z0-9_\.\-]+$/.test(filename)) {
throw new Error('Invalid filename');
}
const filePath = path.join(__dirname, '../uploads', filename);
// Use safe fs operations instead of shell commands
const stats = await fs.stat(filePath);
const fileBuffer = await fs.readFile(filePath);
return {
size: stats.size,
created: stats.birthtime,
modified: stats.mtime,
contentLength: fileBuffer.length
};
}
}When shell commands are unavoidable, use the child_process.execFile method instead of exec, as it doesn't use a shell and prevents command injection:
const { execFile } = require('child_process');
class SafeExecService {
async getFileMetadata(context) {
const { filename } = context.params.query;
// Validate and sanitize input
if (!/^[a-zA-Z0-9_\.\-]+$/.test(filename)) {
throw new Error('Invalid filename');
}
return new Promise((resolve, reject) => {
// execFile uses arguments array, no shell interpretation
execFile('file', ['-b', filename], (error, stdout) => {
if (error) return reject(error);
resolve({ metadata: stdout.trim() });
});
});
}
}For database operations that might use shell commands, use proper database libraries:
const sqlite3 = require('sqlite3').verbose();
class SafeDBService {
constructor() {
this.db = new sqlite3.Database('./data.db');
}
async find(params) {
const query = params.query || {};
// Validate table name against whitelist
const allowedTables = ['users', 'messages', 'files'];
const tableName = allowedTables.includes(query.table) ? query.table : 'users';
// Use parameterized queries
return new Promise((resolve, reject) => {
this.db.all(
`SELECT * FROM ${tableName} WHERE id = ?`,
[query.id],
(err, rows) => {
if (err) return reject(err);
resolve(rows);
}
);
});
}
}Implement comprehensive input validation using a whitelist approach:
const validateInput = (input, type) => {
const validators = {
filename: /^[a-zA-Z0-9_\.\-]+$/,
userId: /^[0-9]+$/,
email: /^[^@\s]+@[^@\s]+\.[^@\s]+$/,
path: /^([a-zA-Z0-9_\-\/]+\.)?[a-zA-Z0-9_\-]+$/,
};
if (!validators[type]) {
throw new Error(`No validator for type: ${type}`);
}
return validators[type].test(input);
};
// Usage in a Feathersjs hook
module.exports = function() {
return async (context) => {
const { filename } = context.params.query;
if (!validateInput(filename, 'filename')) {
throw new Error('Invalid filename format');
}
return context;
};
};Create a security middleware for your Feathersjs app to centralize validation:
const securityMiddleware = async (context, next) => {
const { method, type, params, data } = context;
// Block dangerous characters in query parameters
if (params.query) {
for (const [key, value] of Object.entries(params.query)) {
if (typeof value === 'string' && /[;|&$`\n\r\t]/.test(value)) {
throw new Error(`Potential injection in parameter: ${key}`);
}
}
}
// Validate data payload for create/update operations
if (data && method === 'create') {
// Add your validation logic here
if (data.command) {
throw new Error('Command field not allowed');
}
}
await next();
};
// Apply globally
app.hooks({
before: {
all: [securityMiddleware]
}
});
For Feathersjs services that must interact with external systems, use the framework's built-in validation and sanitization features:
const { BadRequest } = require('@feathersjs/errors');
class SecureService {
async create(data, params) {
// Validate data structure
if (!data || typeof data !== 'object') {
throw new BadRequest('Invalid data format');
}
// Check for dangerous fields
const dangerousFields = ['exec', 'execSync', 'spawn', 'execFile'];
for (const field of dangerousFields) {
if (field in data) {
throw new BadRequest(`Field ${field} is not allowed`);
}
}
// Proceed with safe operations
return this._safeCreate(data, params);
}
}Finally, implement comprehensive logging and monitoring to detect attempted command injection attacks:
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'security.log' })
]
});
// Security monitoring hook
module.exports = function() {
return async (context) => {
const { method, params, data } = context;
// Log suspicious patterns
if (params.query) {
for (const [key, value] of Object.entries(params.query)) {
if (typeof value === 'string' && /[;|&$`\n\r\t]/.test(value)) {
logger.warn('Suspicious query parameter detected', {
service: context.path,
method,
key,
value,
timestamp: new Date().toISOString(),
ip: params.provider === 'rest' ? params.headers['x-forwarded-for'] : 'unknown'
});
}
}
}
return context;
};
};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 |
Frequently Asked Questions
How can I test my Feathersjs API for command injection vulnerabilities?
npx middlebrick scan https://your-feathers-api.com. The scanner tests for command injection by sending payloads with shell metacharacters like semicolons, pipes, and backticks. It also checks for LLM-specific injection scenarios if your Feathersjs service includes AI features. For continuous testing, add the middleBrick GitHub Action to your CI/CD pipeline to automatically scan before deployment.What's the difference between exec and execFile in Node.js, and why does it matter for Feathersjs security?
execFile('file', ['-b', filename]) instead of exec(`file -b "${filename}"`). This simple change eliminates the command injection vulnerability while maintaining the same functionality.