Skip to content

CRITICAL SECURITY FIXES - Buat FileUploadService #1433

@vickyrolanda

Description

@vickyrolanda

lihat contoh penerapan dibawah ini perlu di terapkan di OpenDK untuk memperbaiki keamaan :

Step 1: Buat Service Class

php artisan make:class Services/FileUploadService

File: app/Services/FileUploadService.php

<?php

namespace App\Services;

use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

class FileUploadService
{
    /**
     * Upload file dengan validasi keamanan
     */
    public function uploadSecure(
        UploadedFile $file,
        string $directory,
        array $allowedMimes = [],
        int $maxSize = 2048
    ): string {
        // 1. Validate MIME type
        $this->validateMimeType($file, $allowedMimes);
        
        // 2. Validate file size (KB)
        $this->validateFileSize($file, $maxSize);
        
        // 3. Generate safe filename
        $safeFileName = $this->generateSafeFileName($file);
        
        // 4. Store file securely
        $path = $file->storeAs($directory, $safeFileName, 'public');
        
        return $path;
    }

    /**
     * Upload multiple files
     */
    public function uploadMultiple(
        array $files,
        string $directory,
        array $allowedMimes = [],
        int $maxSize = 2048
    ): array {
        $uploadedPaths = [];
        
        foreach ($files as $file) {
            if ($file instanceof UploadedFile) {
                $uploadedPaths[] = $this->uploadSecure($file, $directory, $allowedMimes, $maxSize);
            }
        }
        
        return $uploadedPaths;
    }

    /**
     * Delete file
     */
    public function delete(string $path): bool
    {
        if (Storage::disk('public')->exists($path)) {
            return Storage::disk('public')->delete($path);
        }
        
        return false;
    }

    /**
     * Validate MIME type
     */
    protected function validateMimeType(UploadedFile $file, array $allowedMimes): void
    {
        if (empty($allowedMimes)) {
            return;
        }
        
        $fileMime = $file->getMimeType();
        
        if (!in_array($fileMime, $allowedMimes)) {
            throw new \InvalidArgumentException(
                "File type not allowed. Allowed types: " . implode(', ', $allowedMimes)
            );
        }
    }

    /**
     * Validate file size
     */
    protected function validateFileSize(UploadedFile $file, int $maxSize): void
    {
        $fileSizeInKB = $file->getSize() / 1024;
        
        if ($fileSizeInKB > $maxSize) {
            throw new \InvalidArgumentException(
                "File size exceeds maximum allowed size of {$maxSize}KB"
            );
        }
    }

    /**
     * Generate safe filename
     */
    protected function generateSafeFileName(UploadedFile $file): string
    {
        // Generate unique hash-based filename
        $extension = $file->getClientOriginalExtension();
        $timestamp = time();
        $random = Str::random(16);
        
        return "{$timestamp}_{$random}.{$extension}";
    }

    /**
     * Get allowed MIME types by category
     */
    public static function getAllowedMimes(string $category): array
    {
        return match($category) {
            'image' => ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
            'document' => ['application/pdf', 'application/msword', 
                          'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
            'spreadsheet' => ['application/vnd.ms-excel', 
                             'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                             'text/csv'],
            'archive' => ['application/zip', 'application/x-zip-compressed'],
            default => [],
        };
    }
}

Step 2: Buat Custom Validation Rule

php artisan make:rule SecureFileUpload

File: app/Rules/SecureFileUpload.php

<?php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Http\UploadedFile;

class SecureFileUpload implements ValidationRule
{
    protected array $allowedMimes;
    protected int $maxSize;

    public function __construct(array $allowedMimes = [], int $maxSize = 2048)
    {
        $this->allowedMimes = $allowedMimes;
        $this->maxSize = $maxSize;
    }

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (!$value instanceof UploadedFile) {
            $fail("The {$attribute} must be a valid file.");
            return;
        }

        // Check MIME type
        if (!empty($this->allowedMimes)) {
            $mime = $value->getMimeType();
            if (!in_array($mime, $this->allowedMimes)) {
                $fail("The {$attribute} must be one of: " . implode(', ', $this->allowedMimes));
                return;
            }
        }

        // Check file size
        $sizeInKB = $value->getSize() / 1024;
        if ($sizeInKB > $this->maxSize) {
            $fail("The {$attribute} may not be greater than {$this->maxSize} kilobytes.");
        }

        // Check for dangerous extensions
        $dangerousExtensions = ['php', 'phtml', 'php3', 'php4', 'php5', 'exe', 'bat', 'sh'];
        $extension = strtolower($value->getClientOriginalExtension());
        
        if (in_array($extension, $dangerousExtensions)) {
            $fail("The {$attribute} has a forbidden file extension.");
        }
    }
}

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions