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:
- Input Validation: Tüm user input’ları validate edin
- escapeshellarg(): Command injection’ı önleyin
- Whitelist Approach: İzin verilen commands/paths tanımlayın
- Least Privilege: Minimum gerekli yetkilerle çalıştırın
⚡ Best Practices:
- Error handling implement edin
- Timeout değerleri belirleyin
- Process monitoring kullanın
- Logging ekleyin
- Resource limits koyun
🔧 Use Cases:
- System administration scripts
- File processing automation
- Background job execution
- Image/video processing
- Data export/import operations
🚨 Güvenlik Uyarıları:
- Production’da exec() kullanımını minimize edin
- Web uygulamalarında dikkatli olun
- User input’unu asla direkt command’a vermeyin
- Sandboxed environment kullanın
PHP exec() ile güvenli ve efficient process management yapabilirsiniz!