當我們想要限制上傳檔案的格式時,最簡單的方式是檢查副檔名或Mime types,不過如果該檔案被更改過副檔名的話,這個檢查就沒意義了。好在我們可以透過各種檔案格式的「魔術數字」來檢查該檔案的內容是否真的符合副檔名所對應的格式。
所謂的「魔術數字」指的是各種不同的檔案開頭的幾個固定字節。比如說 .jpg 或 .jpeg 的檔案,開頭都固定是“ffd8ffe0”,.png 的檔案,開頭都固定是 “89504e47”。我們可以在檔案上傳後存檔前,檢查檔案是否有符合這個規則,若符合再將檔案儲存。
以下是 node.js 檢查上傳圖片格式的程式碼範例:
import express from 'express' // express middleware for multipart/form parsing import multer from 'multer' import fs from 'fs' import path from 'path' const app = express() const port = 5438 // 因為這個範例中,想先檢查副檔名是否符合檔案格式再儲存 // 因此我們使用 multer 的 memoryStorage,這樣我們就可以在 file 物件中使用 buffer 來檢查 // 不過使用 memoryStorage 時,最好限制一下檔案大小,否則上傳大檔時,會很消耗主機記憶體 const storage = multer.memoryStorage() const upload = multer({ storage, fileFilter (req, file, cb) { const ext = path.extname(file.originalname) const acceptFormats = ['.webp', '.jpg', '.jpeg', '.png', '.gif', '.svg'] if (!acceptFormats.includes(ext)) { return cb(new Error('Only images are allowed')) } cb(null, true) }, limits: { // 限制上傳檔案大小 (bytes) fileSize: 1000000 } }) app.post('/upload', upload.array('files'), (req, res, next) => { if (req.files && req.files.length) { req.files.forEach(file => { const filename = new Date().getTime() + '_' + Math.floor(Math.random() * 1E+12) file.newfilename = filename const ext = path.extname(file.originalname) const newpath = 'uploads/' + filename + ext // 在這邊檢查圖片格式符不符合副檔名 const needsCheckFormats = ['.webp', '.jpg', '.jpeg', '.png', '.gif'] const magicMatch = { '.jpg': 'ffd8ffe0', '.jpeg': 'ffd8ffe0', '.png': '89504e47', '.gif': '47494638', '.webp': '52494646' } // 檔案是否需要檢查 if (needsCheckFormats.includes(ext)) { const magic = file.buffer.toString('hex', 0, 4) if (magic && magic === magicMatch[ext]) { // 檢查完畢,符合格式,存起來 fs.appendFileSync(newpath, file.buffer) } else { // 檢查完畢,不符合格式,在 file 物件中做紀錄 file.err = 'File format does not match file ext' } } else { // 不需檢查的svg直接存 fs.appendFileSync(newpath, file.buffer) } }) } console.log(req.files) res.send('OK') }) app.listen(port, () => { console.log(`Server for testing image upload is listening on port ${port}`) })
以上是最常用的圖片上傳的判斷,其他還有很多魔術數字,下面列出一些常用的。其他可以參考 https://gist.github.com/leommoore/f9e57ba2aa4bf197ebc5 或 https://asecuritysite.com/forensics/magic
副檔名 | 魔術數字 |
.gif | 47 49 46 38 |
.jpg / .jpeg | ff d8 ff e0 |
.png | 89 50 4e 47 |
.tif / .tiff | 4d 4d 00 2a / 49 49 2a 00 |
.zip | 50 4b 03 04 |
25 50 44 46 | |
.mp4 | 00 00 00 18 66 74 79 70 6D 70 34 32 |
.mp3 | 49 44 33 |
.doc | D0 CF 11 E0 A1 B1 1A E1 |
.xls | D0 CF 11 E0 A1 B1 1A E1 |
.psd | 38 42 50 53 |
完整範例可以參考我的 GitHub 專案,這個專案更新了比較完善的檢查用代碼,有興趣的話可以看看。