Process.Start with string argument
| Vulnerability potential | High |
| DDoS potential | Low |
Passing user-controlled text to Process.Start is a command-injection vector. Validate the file name or use the ProcessStartInfo overload with UseShellExecute=false
Impact
Process.Start launches an external program. When the file name or arguments are built from user-controlled text, the call becomes a command-injection sink. Exactly what an attacker can do depends on the overload:
Process.Start(string fileName)and theProcessStartInfo.FileNamewithUseShellExecute = trueroute the string through the OS shell/ShellExecute. The shell interprets metacharacters, environment variables, and (on Windows) lets a bare document name launch its registered handler. Tainted input here can run arbitrary commands.- Even with
UseShellExecute = false, a singleArgumentsstring is parsed by the target program (and on Windows split withCommandLineToArgvWrules), so unescaped quotes/spaces let an attacker smuggle extra arguments and change the program’s behaviour.
The consequence ranges from running an unintended program, to argument injection that flips a flag (e.g. turning a read into a write), to full remote code execution.
Vulnerability potential
This is a primary security defect (CWE-78 OS Command Injection, CWE-88 Argument Injection).
- Shell metacharacter injection. With
UseShellExecute = true, input likefile.txt & calc.exeor$(rm -rf ~)is interpreted by the shell, running attacker-chosen commands with the privileges of the host process. - Argument injection. Even without a shell, concatenating user input into the
Argumentsstring lets an attacker inject extra flags. A classic example is passing a value beginning with-to a tool, turningapp fileintoapp --dangerous-option file. - PATH / search-order hijacking. Starting a program by bare name (
"git") resolves throughPATHand, on Windows, the current directory; an attacker who can drop a binary in a searched directory gets it executed. - Privilege escalation. If the host runs elevated (service, scheduled task), injected commands inherit that privilege.
To remediate: never pass untrusted text as a command line. Use UseShellExecute = false, set FileName to a fixed, validated absolute path, and supply each argument separately via ProcessStartInfo.ArgumentList (which escapes per-argument) instead of building one Arguments string.
Technical details
UseShellExecute
With UseShellExecute = true (the default for the simple Process.Start(string) overload on .NET Framework), the runtime calls the platform shell-execute API. The string is subject to shell parsing, file-association lookup, and verb handling — a much larger attack surface than a direct CreateProcess. On .NET Core/5+ the default is false, but code that explicitly enables it reopens the hole.
Argument string vs ArgumentList
A Windows process receives one raw command line; the runtime builds it from ProcessStartInfo.Arguments verbatim, so the caller is responsible for quoting. ArgumentList (added in .NET Core 2.1+) takes a list and applies the correct Windows escaping rules per element, eliminating manual quoting bugs. On Unix, arguments are passed as a real argv array, so there is no shell involved at all when UseShellExecute = false.
Resolution order
A non-rooted FileName is resolved against PATH (and the working directory on Windows). Always use an absolute, validated path to avoid binary-planting attacks.
Catching the issue
Static analysis
Security-focused analyzers flag tainted data flowing into Process.Start: the .NET SecurityCodeScan rules, SonarQube S4036 (PATH search) / command-injection rules, and CodeQL’s cs/command-line-injection query all detect this. Roslyn’s CA3006 (process command injection) is part of the FxCop/security ruleset.
Banned API
For libraries that should never spawn shells, add System.Diagnostics.Process.Start overloads taking a single string to a BannedSymbols.txt (BannedApiAnalyzers) and force callers onto a vetted wrapper.
Code review
Require that every Process.Start uses UseShellExecute = false, a constant/whitelisted FileName, and ArgumentList for any dynamic values. Reject any string concatenation or interpolation that reaches FileName or Arguments.
How to reproduce
Run with input x & calc (Windows) and observe that a second program launches. The safe version treats the whole value as one argument.
using System.Diagnostics;
class Program
{
// VULNERABLE: user text reaches the shell
static void Unsafe(string userInput)
{
Process.Start(new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c type " + userInput, // "x & calc" injects a command
UseShellExecute = true
});
}
// SAFE: fixed program, per-argument escaping, no shell
static void Safe(string userInput)
{
var psi = new ProcessStartInfo
{
FileName = @"C:\Windows\System32\find.exe",
UseShellExecute = false
};
psi.ArgumentList.Add("/c");
psi.ArgumentList.Add(userInput); // passed as a single opaque argument
Process.Start(psi);
}
static void Main() => Unsafe("x & calc");
}