Hàm kiểm tra file ảnh thật hay giả trong PHP: Chặn upload shell đội lốt JPG

Upload ảnh là một trong những điểm dễ toang nhất của website PHP. Hacker không cần cao siêu: chỉ cần đổi tên shell.php thành cute-girl.jpg là đủ phá server nếu backend kiểm tra hời hợt. Bài này đi thẳng vào vấn đề: cách kiểm tra file ảnh có phải ảnh thật hay không, dùng PHP thuần, không màu mè, không niềm tin mù quáng vào $_FILES.

Hàm kiểm tra file ảnh thật hay giả trong PHP: Chặn upload shell đội lốt JPG

Vì sao kiểm tra đuôi file là tự sát

Nhiều code vẫn còn kiểu:

<?php
$ext = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
if (in_array($ext, ['jpg', 'png', 'gif'])) {
    // cho upload
}

Vấn đề: đuôi file là thứ dễ giả nhất hành tinh. Hacker chỉ cần đổi tên, còn nội dung bên trong là PHP, JS, hoặc binary độc hại thì server vẫn ăn đủ.

Mime type từ client không đáng tin

$_FILES['type'] hay header Content-Type là do trình duyệt gửi lên. Trình duyệt nói gì tin nấy thì khác gì hỏi kẻ trộm xem nó có phải trộm không.

Nguyên tắc kiểm tra ảnh đúng nghĩa

  • Không tin tên file
  • Không tin mime type từ client
  • Kiểm tra nội dung thật của file
  • Chỉ cho phép định dạng ảnh cần thiết

Hàm kiểm tra file ảnh thật hay giả

Đây là hàm thực chiến, dùng được cho hầu hết project PHP:

<?php
function is_real_image(string $tmpFilePath): bool {
    if (!is_file($tmpFilePath)) {
        return false;
    }

    // 1. kiểm tra bằng getimagesize (đọc header ảnh thật)
    $imageInfo = @getimagesize($tmpFilePath);
    if ($imageInfo === false) {
        return false;
    }

    // 2. whitelist mime type cho phép
    $allowedMimeTypes = [
        'image/jpeg',
        'image/png',
        'image/gif',
        'image/webp'
    ];

    if (!in_array($imageInfo['mime'], $allowedMimeTypes, true)) {
        return false;
    }

    // 3. kiểm tra file không chứa code php ở đầu
    $fh = fopen($tmpFilePath, 'rb');
    if ($fh !== false) {
        $firstBytes = fread($fh, 512);
        fclose($fh);

        if (preg_match('/<\?php|<script|<\/script>/i', $firstBytes)) {
            return false;
        }
    }

    return true;
}

Vì sao getimagesize hiệu quả

getimagesize() không đọc theo đuôi file, mà phân tích header nhị phân của ảnh. File giả dạng thường không có header đúng chuẩn JPEG/PNG, nên sẽ bị loại ngay.

Trường hợp ảnh có chèn mã độc phía sau

Một số shell cao tay có thể:

  • Ảnh thật ở đầu file
  • Mã độc PHP chèn phía sau

Đoạn kiểm tra đọc vài trăm byte đầu file giúp loại bớt loại này. Không tuyệt đối 100%, nhưng đủ an toàn cho đa số website.

Cách dùng hàm khi upload

<?php
if (!isset($_FILES['image'])) {
    die('Không có file');
}

$tmpPath = $_FILES['image']['tmp_name'];

if (!is_real_image($tmpPath)) {
    die('File upload không phải ảnh hợp lệ');
}

// tiếp tục xử lý: rename, move_uploaded_file...

Đừng quên bước rename file

Dù ảnh có hợp lệ, vẫn nên:

  • Đổi tên file sang chuỗi random
  • Không dùng lại tên do người dùng gửi
  • Không cho upload vào thư mục có quyền execute

Kết luận

Upload ảnh tưởng đơn giản nhưng là cửa tử của bảo mật PHP. Chỉ kiểm tra đuôi file là chưa đủ, tin mime type client là tự bắn vào chân. Với một hàm kiểm tra ảnh đúng nghĩa như trên, bạn đã chặn được phần lớn trò bẩn ngoài kia mà không cần thư viện nặng nề.

Viết backend mà lơ là upload file thì sớm muộn cũng có ngày lên server nhìn thấy file lạ tên index1.php!

Bình luận


  • Không có bình luận.

Init Toolbox

Nhấn Ctrl + \ trên máy tính, hoặc vuốt sang trái ở bất kỳ đâu trên mobile.

Đăng nhập





Đang tải...