Command Injection in Nestjs
How Command Injection Manifests in Nestjs
Command injection vulnerabilities in Nestjs applications typically occur when user-controlled data flows into child process execution without proper sanitization. Nestjs developers often use Node.js's child_process module to execute system commands, and when these commands incorporate unvalidated user input, attackers can inject malicious payloads.
The most common pattern appears in controller methods that accept file paths, search terms, or other parameters that get passed directly to exec() or execSync(). For example, a file upload endpoint might use user-provided filenames to execute cleanup commands:
@Controller('files')
export class FileController {
@Delete(':filename')
async deleteFile(@Param('filename') filename: string) {
// Vulnerable: user input flows directly to shell
const command = `rm -rf ${filename}`;
await exec(command);
return { success: true };
}
}
An attacker could request DELETE /files/important.txt; rm -rf / to delete arbitrary files on the server. The semicolon allows command chaining, executing multiple commands in sequence.
Another Nestjs-specific manifestation occurs when using @Injectable() services that wrap child_process calls. Developers might create utility services for system operations:
@Injectable()
export class SystemService {
async runCommand(input: string) {
// Vulnerable: no validation of input
return exec(`python3 process.py ${input}`);
}
}
Third-party Nestjs modules can also introduce command injection risks. Database migration tools, file processing libraries, or image manipulation services that execute shell commands with user data are common culprits. The danger increases when developers use dynamic imports or eval() with user-controlled module names.
Command injection in Nestjs often combines with other vulnerabilities. An IDOR (Insecure Direct Object Reference) flaw might allow an attacker to specify arbitrary filenames, while the command injection vulnerability provides the mechanism to execute system commands. This combination can lead to complete server compromise.
Nestjs-Specific Detection
Detecting command injection in Nestjs applications requires both static analysis and runtime scanning. Static analysis tools can identify dangerous patterns like exec(), execSync(), spawn(), and spawnSync() calls with string concatenation. However, runtime scanning provides more accurate results by testing actual API endpoints.
middleBrick's black-box scanning approach is particularly effective for Nestjs applications. The scanner sends crafted payloads to API endpoints and observes responses for command execution indicators. For command injection detection, middleBrick tests common injection patterns:
# middleBrick CLI scan
middlebrick scan https://api.example.com --output json
The scanner automatically identifies Nestjs-specific patterns by examining HTTP responses. Nestjs applications often return JSON with specific error formats, and successful command injection typically produces different response patterns than normal application behavior.
middleBrick tests multiple injection vectors simultaneously:
- Basic command chaining with semicolons (;)
- Background execution with ampersands (&)
- Redirection operators (>, <, |)
- Environment variable manipulation ($VAR)
- Process substitution ($(command))
For Nestjs applications using TypeScript decorators, middleBrick analyzes parameter binding patterns. The scanner identifies which parameters are user-controlled and tests them systematically. For example, if a @Param('id') parameter is used in a command execution context, middleBrick will test that specific parameter with injection payloads.
The LLM/AI security features in middleBrick also detect command injection in AI-powered Nestjs applications. If your Nestjs app uses OpenAI or other LLM APIs and incorporates user input into system commands, middleBrick's AI-specific checks will identify these dangerous patterns.
middleBrick's OpenAPI spec analysis is particularly valuable for Nestjs projects, as many Nestjs applications use OpenAPI decorators (@ApiProperty, @ApiOperation) to generate API documentation. The scanner cross-references spec definitions with runtime findings to ensure comprehensive coverage.
Nestjs-Specific Remediation
Remediating command injection in Nestjs requires eliminating shell command execution with user input. The most effective approach is using child_process functions that accept argument arrays instead of shell strings:
@Controller('files')
export class FileController {
@Delete(':filename')
async deleteFile(@Param('filename') filename: string) {
// Secure: no shell interpretation, arguments are escaped
const { execFile } = require('child_process');
return new Promise((resolve, reject) => {
execFile('rm', ['-rf', filename], (error, stdout, stderr) => {
if (error) return reject(error);
resolve({ success: true });
});
});
}
}
The execFile() function executes the 'rm' binary directly with arguments as an array. Node.js handles argument escaping, preventing shell metacharacter interpretation.
For more complex scenarios, Nestjs developers should implement strict input validation and whitelisting:
@Injectable()
export class SafeSystemService {
private readonly ALLOWED_OPERATIONS = ['list', 'process', 'analyze'];
async runOperation(operation: string, data: string) {
if (!this.ALLOWED_OPERATIONS.includes(operation)) {
throw new BadRequestException('Invalid operation');
}
// Validate data against expected format
const sanitizedData = this.sanitizeInput(data);
// Use argument array, not shell string
const { execFile } = require('child_process');
return new Promise((resolve, reject) => {
execFile('python3', ['process.py', operation, sanitizedData], (error, stdout, stderr) => {
if (error) return reject(error);
resolve(stdout);
});
});
}
private sanitizeInput(input: string): string {
// Remove any characters that could be used for injection
return input.replace(/[^a-zA-Z0-9._-]/g, '_');
}
}
Nestjs's built-in validation pipes provide another layer of defense. By combining class-validator decorators with custom validation logic, you can ensure only expected input reaches your service layer:
import { IsString, IsIn, Validate } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class CommandDto {
@ApiProperty({ description: 'Operation type' })
@IsIn(['list', 'process', 'analyze'])
operation: string;
@ApiProperty({ description: 'Data to process' })
@Validate(SafeInputValidator)
data: string;
}
@Injectable()
export class SafeInputValidator extends ValidatorConstraint implements ValidatorConstraintInterface {
validate(value: string): boolean {
// Allow only alphanumeric, dots, hyphens, and underscores
return /^[a-zA-Z0-9._-]+$/.test(value);
}
}
For Nestjs applications that must execute shell commands, consider using the built-in Nestjs validation pipes with custom sanitization:
@Controller('files')
export class FileController {
constructor(private readonly systemService: SystemService) {}
@Delete(':filename')
async deleteFile(
@Param('filename', new ValidationPipe({
transform: true,
whitelist: true,
forbidNonWhitelisted: true
})) filename: string
) {
// Validation pipe ensures only expected characters
return this.systemService.safeDelete(filename);
}
}
The most secure approach is architectural: eliminate the need for shell command execution entirely. Use native Node.js libraries for file operations, database access, and other system tasks. When external tools are absolutely necessary, run them in isolated containers or sandboxed environments where they cannot affect the main application.
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 |