Skip to content

PHP exec(): Process Yönetimi ve Güvenlik

Published: at 06:01 PMSuggest an edit

Merhaba! Bugün PHP’de sistem komutlarını çalıştırmak için kullanılan exec() fonksiyonu ve process yönetimini inceleyeceğiz. Güvenlik riskleri ve korunma yöntemleriyle birlikte pratik örnekler göreceğiz.

PHP Process Functions

PHP’de sistem komutlarını çalıştırmak için birkaç farklı fonksiyon bulunur:

exec() Fonksiyonu

<?php
// Basic exec usage
$output = [];
$return_code = 0;

exec('ls -la', $output, $return_code);

echo "Return code: $return_code\n";
foreach ($output as $line) {
    echo "$line\n";
}

// Single line output
$current_user = exec('whoami');
echo "Current user: $current_user\n";
?>

system() vs shell_exec() vs exec()

<?php
// Comparison of different execution functions

class ProcessExecutor {

    public function demonstrateExecFunctions() {
        $command = 'echo "Hello World" && date';

        echo "=== exec() ===\n";
        $output = [];
        $return_code = 0;
        exec($command, $output, $return_code);

        echo "Output (array):\n";
        print_r($output);
        echo "Return code: $return_code\n\n";

        echo "=== system() ===\n";
        echo "Output (direct to browser):\n";
        $return_code = system($command);
        echo "\nReturn code: $return_code\n\n";

        echo "=== shell_exec() ===\n";
        $output = shell_exec($command);
        echo "Output (string):\n$output\n";

        echo "=== passthru() ===\n";
        echo "Output (direct binary):\n";
        passthru($command, $return_code);
        echo "\nReturn code: $return_code\n";
    }

    public function getSystemInfo() {
        return [
            'php_version' => phpversion(),
            'operating_system' => php_uname(),
            'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? 'CLI',
            'current_user' => exec('whoami'),
            'current_directory' => getcwd(),
            'available_memory' => shell_exec('free -h') ?: 'N/A (Non-Linux)',
            'disk_usage' => shell_exec('df -h') ?: 'N/A'
        ];
    }
}

$executor = new ProcessExecutor();
$executor->demonstrateExecFunctions();

echo "\n=== System Information ===\n";
$system_info = $executor->getSystemInfo();
foreach ($system_info as $key => $value) {
    echo "$key: $value\n";
}
?>

Güvenlik ve Command Injection

Command Injection Vulnerability

<?php
// ❌ VULNERABLE CODE - Never do this!
class VulnerableExecutor {

    public function badFileList($directory) {
        // User input directly in command - DANGEROUS!
        $command = "ls -la $directory";
        $output = shell_exec($command);
        return $output;
    }

    public function badPing($host) {
        // Command injection vulnerability
        $command = "ping -c 3 $host";
        $output = shell_exec($command);
        return $output;
    }
}

// Attack examples:
// badFileList("/etc; cat /etc/passwd")
// badPing("google.com; rm -rf /")
?>

Secure Command Execution

<?php
class SecureExecutor {

    // Input validation patterns
    private const PATTERNS = [
        'filename' => '/^[a-zA-Z0-9._-]+$/',
        'path' => '/^[a-zA-Z0-9\/._-]+$/',
        'ip' => '/^(\d{1,3}\.){3}\d{1,3}$/',
        'domain' => '/^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/'
    ];

    public function validateInput($input, $type) {
        if (!isset(self::PATTERNS[$type])) {
            throw new InvalidArgumentException("Unknown validation type: $type");
        }

        return preg_match(self::PATTERNS[$type], $input);
    }

    public function secureFileList($directory) {
        // 1. Input validation
        if (!$this->validateInput($directory, 'path')) {
            throw new InvalidArgumentException("Invalid directory path");
        }

        // 2. Whitelist allowed directories
        $allowed_dirs = ['/tmp', '/var/log', '/home/user/documents'];
        $real_path = realpath($directory);

        $allowed = false;
        foreach ($allowed_dirs as $allowed_dir) {
            if (strpos($real_path, realpath($allowed_dir)) === 0) {
                $allowed = true;
                break;
            }
        }

        if (!$allowed) {
            throw new SecurityException("Directory not allowed");
        }

        // 3. Use escapeshellarg()
        $safe_directory = escapeshellarg($directory);
        $command = "ls -la $safe_directory";

        $output = [];
        $return_code = 0;
        exec($command, $output, $return_code);

        if ($return_code !== 0) {
            throw new RuntimeException("Command failed with code: $return_code");
        }

        return $output;
    }

    public function securePing($host) {
        // Validate IP or domain
        if (!$this->validateInput($host, 'ip') && !$this->validateInput($host, 'domain')) {
            throw new InvalidArgumentException("Invalid host format");
        }

        // Additional security: check against blacklist
        $blacklisted_hosts = ['localhost', '127.0.0.1', '0.0.0.0'];
        if (in_array($host, $blacklisted_hosts)) {
            throw new SecurityException("Host is blacklisted");
        }

        // Use escapeshellarg for safety
        $safe_host = escapeshellarg($host);
        $command = "ping -c 3 -W 5 $safe_host";

        $output = [];
        $return_code = 0;
        exec($command, $output, $return_code);

        return [
            'success' => $return_code === 0,
            'output' => $output,
            'return_code' => $return_code
        ];
    }

    public function executeWithTimeout($command, $timeout = 30) {
        // Use timeout command to prevent hanging
        $safe_command = "timeout $timeout " . escapeshellcmd($command);

        $descriptors = [
            0 => ['pipe', 'r'],  // stdin
            1 => ['pipe', 'w'],  // stdout
            2 => ['pipe', 'w']   // stderr
        ];

        $process = proc_open($safe_command, $descriptors, $pipes);

        if (!is_resource($process)) {
            throw new RuntimeException("Failed to create process");
        }

        // Close stdin
        fclose($pipes[0]);

        // Read stdout and stderr
        $stdout = stream_get_contents($pipes[1]);
        $stderr = stream_get_contents($pipes[2]);

        fclose($pipes[1]);
        fclose($pipes[2]);

        $return_code = proc_close($process);

        return [
            'stdout' => $stdout,
            'stderr' => $stderr,
            'return_code' => $return_code
        ];
    }
}

// Usage examples
try {
    $executor = new SecureExecutor();

    // Safe file listing
    $files = $executor->secureFileList('/tmp');
    echo "Files in /tmp:\n";
    foreach ($files as $file) {
        echo "$file\n";
    }

    // Safe ping
    $ping_result = $executor->securePing('google.com');
    if ($ping_result['success']) {
        echo "Ping successful:\n";
        foreach ($ping_result['output'] as $line) {
            echo "$line\n";
        }
    }

} catch (Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}
?>

Process Management Class

<?php
class ProcessManager {

    private $processes = [];
    private $max_processes = 10;
    private $log_file = '/tmp/process_manager.log';

    public function __construct($max_processes = 10) {
        $this->max_processes = $max_processes;
    }

    public function executeBackground($command, $process_id = null) {
        if (count($this->processes) >= $this->max_processes) {
            throw new RuntimeException("Maximum process limit reached");
        }

        $process_id = $process_id ?: uniqid('proc_');

        // Create background process
        $safe_command = escapeshellcmd($command);
        $full_command = "nohup $safe_command > /tmp/output_$process_id.log 2>&1 & echo $!";

        $pid = (int) shell_exec($full_command);

        if ($pid > 0) {
            $this->processes[$process_id] = [
                'pid' => $pid,
                'command' => $command,
                'started_at' => time(),
                'status' => 'running'
            ];

            $this->log("Started process $process_id with PID $pid");
            return $process_id;
        }

        throw new RuntimeException("Failed to start background process");
    }

    public function getProcessStatus($process_id) {
        if (!isset($this->processes[$process_id])) {
            return null;
        }

        $process = $this->processes[$process_id];
        $pid = $process['pid'];

        // Check if process is still running
        $result = shell_exec("ps -p $pid -o pid=");
        $is_running = !empty(trim($result));

        $process['status'] = $is_running ? 'running' : 'completed';
        $process['runtime'] = time() - $process['started_at'];

        // Get output if completed
        if (!$is_running) {
            $output_file = "/tmp/output_$process_id.log";
            if (file_exists($output_file)) {
                $process['output'] = file_get_contents($output_file);
            }
        }

        $this->processes[$process_id] = $process;
        return $process;
    }

    public function killProcess($process_id) {
        if (!isset($this->processes[$process_id])) {
            return false;
        }

        $pid = $this->processes[$process_id]['pid'];

        // Try graceful termination first
        $result = shell_exec("kill $pid 2>&1");
        sleep(2);

        // Check if still running
        $check = shell_exec("ps -p $pid -o pid=");
        if (!empty(trim($check))) {
            // Force kill
            shell_exec("kill -9 $pid 2>&1");
            $this->log("Force killed process $process_id (PID: $pid)");
        } else {
            $this->log("Gracefully terminated process $process_id (PID: $pid)");
        }

        $this->processes[$process_id]['status'] = 'killed';
        return true;
    }

    public function listProcesses() {
        $active_processes = [];

        foreach ($this->processes as $id => $process) {
            $status = $this->getProcessStatus($id);
            if ($status) {
                $active_processes[$id] = $status;
            }
        }

        return $active_processes;
    }

    public function waitForProcess($process_id, $timeout = 60) {
        $start_time = time();

        while (time() - $start_time < $timeout) {
            $status = $this->getProcessStatus($process_id);

            if (!$status || $status['status'] !== 'running') {
                return $status;
            }

            sleep(1);
        }

        throw new TimeoutException("Process $process_id did not complete within $timeout seconds");
    }

    public function cleanup() {
        // Clean up completed processes
        $cleanup_count = 0;

        foreach ($this->processes as $id => $process) {
            $status = $this->getProcessStatus($id);

            if ($status && $status['status'] !== 'running') {
                // Remove output file
                $output_file = "/tmp/output_$id.log";
                if (file_exists($output_file)) {
                    unlink($output_file);
                }

                unset($this->processes[$id]);
                $cleanup_count++;
            }
        }

        $this->log("Cleaned up $cleanup_count completed processes");
        return $cleanup_count;
    }

    public function getSystemLoad() {
        $load = sys_getloadavg();
        $cpu_count = (int) shell_exec("nproc 2>/dev/null") ?: 1;

        return [
            'load_1min' => $load[0],
            'load_5min' => $load[1],
            'load_15min' => $load[2],
            'cpu_count' => $cpu_count,
            'load_percentage' => round(($load[0] / $cpu_count) * 100, 2)
        ];
    }

    private function log($message) {
        $timestamp = date('Y-m-d H:i:s');
        $log_entry = "[$timestamp] $message\n";
        file_put_contents($this->log_file, $log_entry, FILE_APPEND | LOCK_EX);
    }
}

// Usage example
$pm = new ProcessManager(5);

try {
    // Start background processes
    $proc1 = $pm->executeBackground('sleep 10 && echo "Process 1 completed"');
    $proc2 = $pm->executeBackground('find /usr -name "*.log" -type f');
    $proc3 = $pm->executeBackground('ping -c 10 google.com');

    echo "Started processes: $proc1, $proc2, $proc3\n";

    // Monitor processes
    while (true) {
        $processes = $pm->listProcesses();

        echo "\n=== Process Status ===\n";
        foreach ($processes as $id => $process) {
            echo "Process $id: {$process['status']} (Runtime: {$process['runtime']}s)\n";
        }

        // Check if all completed
        $running_count = 0;
        foreach ($processes as $process) {
            if ($process['status'] === 'running') {
                $running_count++;
            }
        }

        if ($running_count === 0) {
            break;
        }

        sleep(2);
    }

    // Show system load
    $load = $pm->getSystemLoad();
    echo "\nSystem Load: {$load['load_1min']} ({$load['load_percentage']}%)\n";

    // Cleanup
    $pm->cleanup();

} catch (Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}
?>

File Operations

<?php
class SecureFileOperations {

    private $allowed_extensions = ['txt', 'log', 'csv', 'json'];
    private $max_file_size = 10 * 1024 * 1024; // 10MB

    public function safeFileRead($filename) {
        // Validate file path
        if (!$this->validateFilePath($filename)) {
            throw new SecurityException("Invalid file path");
        }

        // Check file exists and readable
        if (!file_exists($filename) || !is_readable($filename)) {
            throw new FileNotFoundException("File not found or not readable");
        }

        // Check file size
        if (filesize($filename) > $this->max_file_size) {
            throw new FileSizeException("File too large");
        }

        return file_get_contents($filename);
    }

    public function safeFileWrite($filename, $data) {
        if (!$this->validateFilePath($filename)) {
            throw new SecurityException("Invalid file path");
        }

        // Create directory if needed
        $directory = dirname($filename);
        if (!is_dir($directory)) {
            if (!mkdir($directory, 0755, true)) {
                throw new RuntimeException("Cannot create directory");
            }
        }

        // Write with exclusive lock
        $bytes_written = file_put_contents($filename, $data, LOCK_EX);

        if ($bytes_written === false) {
            throw new RuntimeException("Failed to write file");
        }

        return $bytes_written;
    }

    public function compressFile($source_file, $destination_file = null) {
        if (!$this->validateFilePath($source_file)) {
            throw new SecurityException("Invalid source file path");
        }

        if (!file_exists($source_file)) {
            throw new FileNotFoundException("Source file not found");
        }

        $destination_file = $destination_file ?: $source_file . '.gz';

        // Use shell command for compression
        $safe_source = escapeshellarg($source_file);
        $safe_dest = escapeshellarg($destination_file);

        $command = "gzip -c $safe_source > $safe_dest";

        $output = [];
        $return_code = 0;
        exec($command, $output, $return_code);

        if ($return_code !== 0) {
            throw new RuntimeException("Compression failed");
        }

        return $destination_file;
    }

    public function extractArchive($archive_file, $destination_dir) {
        if (!$this->validateFilePath($archive_file)) {
            throw new SecurityException("Invalid archive file path");
        }

        if (!file_exists($archive_file)) {
            throw new FileNotFoundException("Archive file not found");
        }

        // Create destination directory
        if (!is_dir($destination_dir)) {
            mkdir($destination_dir, 0755, true);
        }

        $file_extension = pathinfo($archive_file, PATHINFO_EXTENSION);

        $safe_archive = escapeshellarg($archive_file);
        $safe_dest = escapeshellarg($destination_dir);

        switch (strtolower($file_extension)) {
            case 'zip':
                $command = "unzip -q $safe_archive -d $safe_dest";
                break;
            case 'tar':
                $command = "tar -xf $safe_archive -C $safe_dest";
                break;
            case 'gz':
                if (strpos($archive_file, '.tar.gz') !== false) {
                    $command = "tar -xzf $safe_archive -C $safe_dest";
                } else {
                    $command = "gunzip -c $safe_archive > $safe_dest/" . basename($archive_file, '.gz');
                }
                break;
            default:
                throw new UnsupportedFormatException("Unsupported archive format");
        }

        $output = [];
        $return_code = 0;
        exec($command, $output, $return_code);

        if ($return_code !== 0) {
            throw new RuntimeException("Extraction failed: " . implode("\n", $output));
        }

        return $destination_dir;
    }

    public function getFileInfo($filename) {
        if (!$this->validateFilePath($filename)) {
            throw new SecurityException("Invalid file path");
        }

        if (!file_exists($filename)) {
            throw new FileNotFoundException("File not found");
        }

        $stat = stat($filename);

        return [
            'name' => basename($filename),
            'path' => realpath($filename),
            'size' => $stat['size'],
            'size_human' => $this->formatBytes($stat['size']),
            'permissions' => substr(sprintf('%o', fileperms($filename)), -4),
            'owner' => posix_getpwuid($stat['uid'])['name'] ?? $stat['uid'],
            'group' => posix_getgrgid($stat['gid'])['name'] ?? $stat['gid'],
            'created' => date('Y-m-d H:i:s', $stat['ctime']),
            'modified' => date('Y-m-d H:i:s', $stat['mtime']),
            'accessed' => date('Y-m-d H:i:s', $stat['atime']),
            'type' => filetype($filename),
            'mime_type' => mime_content_type($filename),
            'md5' => md5_file($filename),
            'sha256' => hash_file('sha256', $filename)
        ];
    }

    private function validateFilePath($path) {
        // Basic security checks
        $path = realpath($path) ?: $path;

        // Prevent directory traversal
        if (strpos($path, '..') !== false) {
            return false;
        }

        // Check extension if file exists
        if (file_exists($path)) {
            $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
            if (!empty($extension) && !in_array($extension, $this->allowed_extensions)) {
                return false;
            }
        }

        return true;
    }

    private function formatBytes($bytes, $precision = 2) {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];

        for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) {
            $bytes /= 1024;
        }

        return round($bytes, $precision) . ' ' . $units[$i];
    }
}

// Usage example
$fileOps = new SecureFileOperations();

try {
    // Read file info
    $info = $fileOps->getFileInfo('/var/log/syslog');
    echo "File: {$info['name']}\n";
    echo "Size: {$info['size_human']}\n";
    echo "Modified: {$info['modified']}\n";
    echo "MD5: {$info['md5']}\n";

} catch (Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}
?>

Sonuç

PHP’de exec() fonksiyonu güçlü ama tehlikeli bir araçtır:

🔒 Güvenlik Öncelikleri:

⚡ Best Practices:

🔧 Use Cases:

🚨 Güvenlik Uyarıları:

PHP exec() ile güvenli ve efficient process management yapabilirsiniz!

Kaynaklar



Previous Post
Server-Side Rendering (SSR): Ne Zaman Gerekli, Ne Zaman Gereksiz?
Next Post
Kill ve PID: Linux Process Yönetiminin Temelleri