When dealing with file uploads in Node.js, one of the most used packages is Multer. Itās simple to use, integrates well with Express, and allows for straightforward file handling. But unfortunately, Multerās file-type validation is not secure enough š. By default, Multer does not validate the actual content of a file; it only depends on the file extension or the MIME type provided by the client, which is editable. The client can easily change the extension of the file in his operating system. This can lead to serious security vulnerabilities in your application.
The problemš
When a user uploads a file, Multer allows you to check the file's MIME type or extension to decide whether to accept it using the fileFilter
option passed to multer
. Hereās a typical way to use Multer's fileFilter
to check file types:
import multer from 'multer';
const fileFilter = (req, file, cb) => {
if (file.mimetype === 'image/png' || file.mimetype === 'image/jpeg') {
cb(null, true);
} else {
cb(new Error('Invalid file type, only PNG and JPEG is allowed!'), false);
}
};
const upload = multer({
dest: 'uploads',
fileFilter,
});
In the above example, Multer checks the MIME type of the file to ensure it's either a PNG or JPEG. This sounds pretty safe, right? Wrong š. The MIME type that Multer checks is provided by the client, which means it's extremely easy to manipulate.
For example, a malicious user can rename a .exe file to .jpg and modify its MIME type to image/jpeg. Multer will happily accept this file, assuming itās a valid image,š but in reality, the user has uploaded a potentially dangerous file.
The Inaccuracy of MIME Type Validation š
MIME types are just metadataāthey donāt represent the actual content of the file. A determined attacker can easily bypass this by changing a fileās extension or setting the wrong MIME type header in their request.
The following scenario can happen:
- A user renames a harmful file virus.exe to image.jpg.
- Then he sends the file to the server with the MIME type image/jpeg.
- Multer accepts this file based on the MIME type.
This is why depending only on MIME type validation becomes dangerous.
The Solution š¤: Validate Using Magic Numbers (The file signature)
To accurately verify the content of a file, you need to inspect its magic numberāthe unique binary signature that identifies the file type. Magic numbers are stored at the beginning of a file and are much harder to change than MIME types or extensions.
You can check for the magic number by manually writing some code on your own, but fortunately thereās a great package for this in Node.js: file-type. It helps determine the actual file type by reading the first few bytes of the file, ensuring the file is genuinely what it claims to be.
file-type package has a method fileTypeFromBuffer
that identifies the type of the file using the buffer format of the file. Since in the above code snippet, we store the file in the disk storage, we need a way to convert this file to buffer. This can be done easily using fs built-in package in Node.js.
You can implement express middleware to truly validate the file using the following code:
import { fileTypeFromBuffer } from "file-type";
import fs from "fs";
// Middleware to validate file type by magic number (file signatures)
export const fileValidation = async (req, res, next) => {
try {
// get the file path
const filePath = req.file.path;
// read the file and return buffer
const buffer = fs.readFileSync(filePath);
// get the file type
const type = await fileTypeFromBuffer(buffer);
// validate
const allowedTypes = ["image/jpeg", "image/png"];
if (!type || !allowedTypes.includes(type.mime))
return next(new Error("Invalid file type"));
return next();
} catch (error) {
return next(new Error("Internal server error"));
}
};
Hint
If you store your file in the memory, instead of disk storage, you can access the buffer data directly from req.file.buffer
instead of using readFileSync
Why it is important? š§
Security: Malicious users can easily bypass Multerās built-in validation by renaming files and changing their MIME types. Using magic number validation ensures that youāre accepting only legitimate file types. If youāre dealing with sensitive data, ensuring file integrity is critical.
Reliability: While MIME type headers can be altered by users, magic numbers are tied to the fileās content and cannot be easily faked.
So by inspecting the actual content of the file, you can prevent users from uploading dangerous files disguised as harmless ones, making your application safer and more secure.
Top comments (0)