Command injection in Swift

Command injection in Swift

Description

When Process launches a shell with user input after -c, the shell interprets that input as command syntax, potentially allowing arbitrary command execution. Even without a shell, allowing a user to choose the executable path or control a program's options can cause unintended operations.

Potential impact

  • Arbitrary command execution.
  • Reading, deleting, or exfiltrating files.
  • Compromise of privileges on a server or developer workstation.

Remediation

  1. Avoid shell execution through -c and use a fixed executable path.
  2. Pass arguments separately through Process.arguments and validate each value against an allow-list.
  3. If constructing a shell string is unavoidable, use a proven escaping function and run with the least possible privileges.

Examples

Before

swift
let command = readLine()!
let task = Process()
task.launchPath = "/bin/sh"
task.arguments = ["-c", command]

After

swift
import Foundation

func runGitStatus(for path: String) throws {
    let task = Process()
    task.executableURL = URL(fileURLWithPath: "/usr/bin/git")
    task.arguments = ["status", "--", path]
    try task.run()
    task.waitUntilExit()
}

Explanation:

  • Before: Running the process with this configuration interprets the entire input as a shell command.
  • After: The executable and subcommand are fixed, and the user value follows --, preventing shell interpretation and option injection.

References