diff --git a/app/Http/Controllers/UploadController.php b/app/Http/Controllers/UploadController.php index 3d8c1076f..4a531593f 100644 --- a/app/Http/Controllers/UploadController.php +++ b/app/Http/Controllers/UploadController.php @@ -6,6 +6,7 @@ use Illuminate\Http\Request; use Illuminate\Http\UploadedFile; use Illuminate\Routing\Controller as BaseController; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Str; use Pion\Laravel\ChunkUpload\Exceptions\UploadMissingFileException; use Pion\Laravel\ChunkUpload\Handler\HandlerFactory; use Pion\Laravel\ChunkUpload\Receiver\FileReceiver; @@ -114,13 +115,28 @@ class UploadController extends BaseController mkdir($finalPath, 0755, true); } - // Use original filename with timestamp to avoid conflicts - $filename = time().'_'.$file->getClientOriginalName(); - $file->move($finalPath, $filename); + // Security: Generate safe filename server-side to prevent path traversal + $originalName = $file->getClientOriginalName(); + $extension = $file->getClientOriginalExtension(); + + // Create a safe slug from original filename (without extension) + $nameWithoutExt = pathinfo($originalName, PATHINFO_FILENAME); + $safeSlug = Str::slug($nameWithoutExt); // Converts to lowercase, replaces special chars with dashes + $safeSlug = substr($safeSlug, 0, 50); // Limit length + + // Sanitize extension (only allow alphanumeric) + $safeExtension = preg_replace('/[^a-zA-Z0-9]/', '', $extension); + + // Generate safe filename: timestamp_slug_randomhash.ext + $randomHash = Str::random(16); + $safeFilename = time().'_'.$safeSlug.'_'.$randomHash.($safeExtension ? '.'.$safeExtension : ''); + + $file->move($finalPath, $safeFilename); return response()->json([ 'mime_type' => $mime, - 'filename' => $filename, + 'filename' => $safeFilename, + 'original_name' => $originalName, // Keep original name for reference ]); } } diff --git a/app/Jobs/CleanupExpiredTerminalFilesJob.php b/app/Jobs/CleanupExpiredTerminalFilesJob.php index b0b1558ca..bc392d212 100644 --- a/app/Jobs/CleanupExpiredTerminalFilesJob.php +++ b/app/Jobs/CleanupExpiredTerminalFilesJob.php @@ -41,9 +41,10 @@ class CleanupExpiredTerminalFilesJob implements ShouldQueue // Delete file from server $server = Server::find($this->serverId); if ($server) { - // Remove from server + // Remove from server - escape shell arguments to prevent injection + $escapedServerPath = escapeshellarg($this->serverPath); $result = instant_remote_process([ - "rm -f {$this->serverPath}" + "rm -f {$escapedServerPath}" ], $server, throwError: false); if ($result) { @@ -52,9 +53,12 @@ class CleanupExpiredTerminalFilesJob implements ShouldQueue // If container was specified, remove from container as well if ($this->containerUuid) { - $containerPath = "/tmp/{$this->filename}"; + $escapedContainerUuid = escapeshellarg($this->containerUuid); + $escapedFilename = escapeshellarg($this->filename); + $containerPath = "/tmp/{$this->filename}"; // For logging only + instant_remote_process([ - "docker exec {$this->containerUuid} rm -f {$containerPath} 2>/dev/null || true" + "docker exec {$escapedContainerUuid} rm -f /tmp/{$escapedFilename} 2>/dev/null || true" ], $server, throwError: false); Log::info("Cleaned up container terminal file: {$containerPath}"); diff --git a/app/Livewire/Terminal/FileImport.php b/app/Livewire/Terminal/FileImport.php index f8c0cd39f..fdef11541 100644 --- a/app/Livewire/Terminal/FileImport.php +++ b/app/Livewire/Terminal/FileImport.php @@ -130,14 +130,17 @@ class FileImport extends Component // Copy file to server's temporary directory $serverTmpPath = "/tmp/coolify_import_{$uploadId}_{$sanitizedFilename}"; - instant_scp($finalPath, $serverTmpPath, $server); + $safeServerTmpPath = escapeshellarg($serverTmpPath); + instant_scp($finalPath, $safeServerTmpPath, $server); // If it's a container, copy to container if ($isContainer) { $containerPath = "/tmp/{$sanitizedFilename}"; + $safeContainer = escapeshellarg($this->selectedUuid); + $safeContainerPath = escapeshellarg($containerPath); instant_remote_process([ - "docker cp {$serverTmpPath} {$this->selectedUuid}:{$containerPath}", + "docker cp {$safeServerTmpPath} {$safeContainer}:{$safeContainerPath}", ], $server); $this->filePath = $containerPath; diff --git a/resources/views/livewire/terminal/file-import.blade.php b/resources/views/livewire/terminal/file-import.blade.php index 78a4646b3..fc834e540 100644 --- a/resources/views/livewire/terminal/file-import.blade.php +++ b/resources/views/livewire/terminal/file-import.blade.php @@ -1,7 +1,6 @@