Mở đầu
Trong các ứng dụng web hiện đại, khả năng cho phép người dùng upload file là một tính năng phổ biến. Tuy nhiên, nếu không được xử lý đúng cách, nó có thể trở thành lỗ hổng bảo mật nghiêm trọng. Bài viết này sẽ hướng dẫn cách xây dựng một hệ thống upload file an toàn bằng Node.js và thư viện Multer, đồng thời đề cập đến các thực hành bảo mật quan trọng.
Vì sao cần quan tâm đến bảo mật khi upload file?
File upload là một trong những cửa ngõ dễ bị khai thác nhất. Kẻ tấn công có thể upload file chứa mã độc, script ẩn, hoặc file giả mạo định dạng nhằm chiếm quyền kiểm soát server. Nếu không kiểm tra kỹ lưỡng, hệ thống có thể bị xâm nhập, dữ liệu bị đánh cắp, hoặc server bị treo do upload file quá lớn. Do đó, việc thiết kế hệ thống upload an toàn là điều bắt buộc, không phải là tùy chọn.
Multer là gì và tại sao nên dùng nó?
Multer là middleware phổ biến nhất cho Node.js, được thiết kế riêng để xử lý multipart/form-data - định dạng dữ liệu thường dùng khi upload file. Multer giúp dễ dàng lưu file vào disk hoặc memory, đồng thời cung cấp các tùy chỉnh về giới hạn kích thước, filter file type, và đặt tên file. Tuy nhiên, Multer chỉ xử lý việc lưu file, còn việc kiểm tra bảo mật phải do developer tự thực hiện.
Các bước xây dựng hệ thống upload an toàn
1. Thiết lập dự án và cài đặt dependencies
Trước tiên, khởi tạo một project Node.js và cài đặt các thư viện cần thiết:
npm init -y
npm install express multer fs-extra
2. Cấu hình Multer với các tùy chọn bảo mật
Tạo thư mục uploads để lưu file và cấu hình Multer:
const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const app = express();
// Tạo thư mục uploads nếu chưa có
const uploadDir = 'uploads';
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir);
}
// Cấu hình Multer
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, uploadDir);
},
filename: (req, file, cb) => {
// Đặt tên file an toàn, tránh overwrite
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, uniqueSuffix + path.extname(file.originalname));
}
});
// Filter file type
function fileFilter(req, file, cb) {
const allowedTypes = /jpeg|jpg|png|gif|pdf/;
const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
const mimetype = allowedTypes.test(file.mimetype);
if (extname && mimetype) {
return cb(null, true);
} else {
cb(new Error('Chỉ cho phép upload file ảnh và PDF'));
}
}
// Giới hạn kích thước file
const limits = {
fileSize: 1024 1024 5 // 5MB
};
const upload = multer({
storage: storage,
fileFilter: fileFilter,
limits: limits
});
Quảng cáo
300x250 In-Content Advertisement
3. Tạo route upload và xử lý lỗi
app.post('/upload', upload.single('file'), (req, res) => {
if (!req.file) {
return res.status(400).json({ error: 'Không tìm thấy file' });
}
res.json({
message: 'Upload thành công',
file: {
originalname: req.file.originalname,
filename: req.file.filename,
size: req.file.size,
mimetype: req.file.mimetype
}
});
});
// Xử lý lỗi Multer
app.use((err, req, res, next) => {
if (err instanceof multer.MulterError) {
return res.status(400).json({ error: 'Lỗi upload file' });
}
if (err) {
return res.status(400).json({ error: err.message });
}
next();
});
4. Kiểm tra file type và nội dung
Không nên chỉ dựa vào phần mở rộng file. Sử dụng package file-type để kiểm tra magic number (dữ liệu nhận dạng định dạng file):
npm install file-type
const fileType = require('file-type');
async function checkFileType(file) {
const buffer = await file.stream.slice(0, 4100).buffer();
const type = await fileType.fromBuffer(buffer);
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
return allowedTypes.includes(type?.mime);
}
5. Đặt file ngoài thư mục public và đổi tên file
Lưu file trong thư mục không thể truy cập trực tiếp qua URL. Nếu cần phục vụ file, tạo route riêng và kiểm tra quyền truy cập:
app.get('/files/:filename', (req, res) => {
const filePath = path.join(uploadDir, req.params.filename);
if (fs.existsSync(filePath)) {
res.sendFile(filePath);
} else {
res.status(404).json({ error: 'File không tồn tại' });
}
});
6. Giới hạn số lượng file và kích thước upload
Với multiple upload:
upload.array('files', 5) // tối đa 5 file
Hoặc dùng upload.fields() cho form phức tạp.
Các thực hành bảo mật bổ sung
- Quét virus: Tích hợp công cụ quét virus như ClamAV. - Dọn dẹp định kỳ: Xóa file cũ hoặc không sử dụng. - Ghi log: Lưu lại mọi hoạt động upload để audit. - Dùng HTTPS: Mã hóa dữ liệu truyền tải. - Validate trên client: Giúp trải nghiệm tốt hơn, nhưng không thay thế validate server.
Kết luận
Xây dựng hệ thống upload file an toàn không chỉ đơn thuần là lưu file vào server. Nó đòi hỏi sự cẩn trọng trong từng bước: kiểm soát định dạng, giới hạn kích thước, đặt tên file an toàn, lưu trữ ở vị trí thích hợp, và luôn validate cả client lẫn server. Multer là công cụ mạnh mẽ, nhưng developer phải chủ động bổ sung các lớp bảo mật. Với những thực hành này, bạn có thể tự tin cung cấp tính năng upload file mà vẫn bảo vệ hệ thống trước các mối đe dọa.