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