判斷上傳的圖片格式

當我們想要限制上傳檔案的格式時,最簡單的方式是檢查副檔名或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

副檔名魔術數字
.gif47 49 46 38
.jpg / .jpegff d8 ff e0
.png89 50 4e 47
.tif / .tiff4d 4d 00 2a / 49 49 2a 00
.zip50 4b 03 04
.pdf25 50 44 46
.mp400 00 00 18 66 74 79 70 6D 70 34 32
.mp349 44 33
.docD0 CF 11 E0 A1 B1 1A E1
.xlsD0 CF 11 E0 A1 B1 1A E1
.psd38 42 50 53

完整範例可以參考我的 GitHub 專案,這個專案更新了比較完善的檢查用代碼,有興趣的話可以看看。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料