0

PHP và MySQL Bài 14: Upload file lên server với PHP - Kiểm tra định dạng, kích thước và lưu trữ an toàn

Gửi văn bản qua Form là chuyện đơn giản, nhưng gửi một tệp tin (hình ảnh, tài liệu PDF, file log) lại là một câu chuyện hoàn toàn khác. Khi làm việc với file, rủi ro bảo mật tăng lên gấp bội, bởi hacker có thể lợi dụng lỗ hổng để tải lên các tệp mã độc (Webshell) và chiếm quyền điều khiển toàn bộ máy chủ.

Bài học này sẽ hướng dẫn bạn quy trình chuẩn xác để xử lý và bảo mật tính năng Upload File trong PHP.


1. Yêu cầu bắt buộc từ phía HTML Form

Để trình duyệt hiểu rằng nó cần phải đóng gói một tệp tin và gửi lên máy chủ, Form HTML của bạn bắt buộc phải thỏa mãn 2 điều kiện:

  • Sử dụng phương thức method="POST".
  • Phải có thuộc tính enctype="multipart/form-data". (Nếu thiếu dòng này, PHP sẽ chỉ nhận được tên của file chứ không nhận được nội dung file).
<!-- Form báo cáo lỗi thiết bị (Defect Notification Form) -->
<form action="upload.php" method="POST" enctype="multipart/form-data">
    <label>Tải lên ảnh chụp sự cố:</label>
    <!-- Thuộc tính name="evidence_image" rất quan trọng để PHP nhận diện -->
    <input type="file" name="evidence_image" required>
    
    <button type="submit">Gửi báo cáo</button>
</form>

2. Biến siêu toàn cục $_FILES

Khi Form được submit, dữ liệu văn bản bình thường sẽ vào $_POST, nhưng dữ liệu tệp tin sẽ đi vào một mảng siêu toàn cục riêng biệt tên là $_FILES.

Giả sử input file của bạn có tên là evidence_image, mảng $_FILES['evidence_image'] sẽ chứa 5 thông tin quan trọng sau:

  • name: Tên gốc của file trên máy tính người dùng (Ví dụ: loi_tvm_01.jpg).
  • type: Kiểu định dạng (MIME type) của file (Ví dụ: image/jpeg).
  • tmp_name: Đường dẫn tạm thời trên máy chủ. Khi file vừa được tải lên, nó nằm trong một thư mục rác (temp). Nếu bạn không dời nó đi, file này sẽ tự động bị xóa sau khi mã PHP chạy xong.
  • error: Mã lỗi tải lên (bằng 0 nghĩa là thành công).
  • size: Kích thước file tính bằng Byte.

3. Quy trình 4 bước Upload an toàn (Kèm code mẫu)

Để xây dựng một tính năng upload an toàn cho hệ thống nghiệp vụ, chúng ta phải tuân thủ nghiêm ngặt 4 bước xác thực sau:

  • Bước 1: Kiểm tra xem có lỗi trong quá trình truyền tải không ($_FILES['evidence_image']['error']).
  • Bước 2: Kiểm tra kích thước file (Size Validation), giới hạn dung lượng hợp lý (ví dụ: tối đa 5MB).
  • Bước 3: Kiểm tra định dạng (Extension & Type Validation) kết hợp kiểm tra sâu đuôi file và MIME type.
  • Bước 4: Đổi tên file ngẫu nhiên và Lưu trữ để chống ghi đè dữ liệu.

Dưới đây là đoạn code PHP hoàn chỉnh xử lý quy trình trên:

<?php
// Kiểm tra xem người dùng đã bấm nút Submit chưa
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['evidence_image'])) {
    
    $file = $_FILES['evidence_image'];
    $errors = [];

    // Cấu hình giới hạn
    $maxFileSize = 5 * 1024 * 1024; // 5MB (tính bằng byte: 5 * 1024 KB * 1024 B)
    $allowedExtensions = ['jpg', 'jpeg', 'png', 'pdf'];
    $uploadDirectory = 'uploads/defects/'; // Đảm bảo thư mục này đã được tạo trên Server

    // 1. Kiểm tra lỗi truyền tải
    if ($file['error'] !== UPLOAD_ERR_OK) {
        die("Có lỗi xảy ra trong quá trình tải lên. Mã lỗi: " . $file['error']);
    }

    // 2. Kiểm tra kích thước
    if ($file['size'] > $maxFileSize) {
        $errors[] = "File quá lớn. Vui lòng tải file dưới 5MB.";
    }

    // 3. Kiểm tra định dạng file (Lấy đuôi file chuyển về chữ thường)
    $fileExtension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
    
    if (!in_array($fileExtension, $allowedExtensions)) {
        $errors[] = "Định dạng không hợp lệ. Chỉ chấp nhận JPG, PNG, PDF.";
    }

    // Nếu không có lỗi, tiến hành lưu trữ
    if (empty($errors)) {
        // 4. Tạo tên file duy nhất để chống ghi đè (Ví dụ: defect_1722824498_abc123.jpg)
        $newFileName = 'defect_' . time() . '_' . uniqid() . '.' . $fileExtension;
        
        // Đường dẫn đích cuối cùng
        $destinationPath = $uploadDirectory . $newFileName;

        // Di chuyển file từ thư mục tạm ($tmp_name) sang thư mục đích
        if (move_uploaded_file($file['tmp_name'], $destinationPath)) {
            echo "<h3>Tải file thành công!</h3>";
            echo "Đường dẫn file: " . $destinationPath;
            
            // Tại đây, bạn có thể chạy lệnh SQL để lưu $destinationPath vào Database 
            // liên kết với ID của báo cáo lỗi (DNF).
        } else {
            echo "<h3>Lỗi hệ thống: Không thể lưu file vào thư mục đích.</h3>";
        }
    } else {
        // In ra các lỗi Validation
        foreach ($errors as $error) {
            echo "<p style='color:red;'>- $error</p>";
        }
    }
}
?>

4. Những nguyên tắc bảo mật khi triển khai thực tế

Khi đẩy mã nguồn lên môi trường Production, Backend Developer cần lưu ý thêm các thiết lập sau trên Web Server (Nginx/Apache):

  • Chặn thực thi mã ngầm: Cấu hình thư mục uploads/defects/ (nơi lưu file) KHÔNG được phép thực thi bất kỳ file kịch bản nào (vô hiệu hóa công cụ dịch PHP tại thư mục đó). Nếu hacker lọt qua được cửa bảo vệ và đẩy được file .php lên, file đó cũng sẽ chỉ là một đoạn văn bản vô hại.
  • Tùy chỉnh php.ini: Mặc định, PHP chỉ cho phép tải lên file tối đa 2MB. Bạn cần vào file cấu hình php.ini của máy chủ, tìm và tăng các thông số upload_max_filesizepost_max_size nếu hệ thống yêu cầu tải các file log nặng hơn.

Tổng kết Bài 14 và Khép lại Phần 3

Đến đây, bạn đã làm chủ hoàn toàn các kỹ thuật giao tiếp giữa người dùng và máy chủ thông qua biểu mẫu: lấy chuỗi ký tự bằng $_GET, nhận thông tin bảo mật qua $_POST, và tiếp nhận an toàn các tệp tin với $_FILES.

Tuy nhiên, giao thức HTTP là một giao thức "không trạng thái" (stateless). Máy chủ sẽ quên ngay người dùng là ai sau khi họ chuyển sang trang khác. Để duy trì trạng thái đăng nhập trong suốt quá trình sử dụng hệ thống, chúng ta cần bước sang Phần 4: Quản Lý Trạng Thái & Bảo Mật Cơ Bản.

Bài tiếp theo sẽ là Bài 15: Cookie trong PHP - Lưu trữ dữ liệu phía trình duyệt.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí