Day05-入口管制(四)

前言

前面几天谈的都是纯文字的资料验证,像是信箱、电话等等,但很多 API server 除了文字资料外也会提供上传档案、照片的功能,尤其现在那麽多电商一定会需要使用者上传大头贴、商品照之类的,有时甚至还会上传影片,所以今天要来说说对於使用者上传的档案有哪些应该做的检查

档案类型

前端能做的事

虽然这个系列是以後端的 API Server 为主,不过这边也顺便讲一下在前端怎麽限制档案类型:平常在前端实作上传档案时一定会有一个 <input type="file"> 的 HTML 元素,这样使用者才能按下按钮选择档案。

而从 HTML5 开始,input 元件多了一个 accept 属性可以设定,所以你可以透过 <input type="file" accept=".png,.jpg"> 来设定副档名,或是直接用 <input type="file" accept="image/*,video/*"> 这种 MIME-Type 的格式来接受所有的图片、影片档,那使用者在选择时就会像下图这样不会选错档案

但因为这只是前端的限制,只要 API 在那边,攻击者一定有办法直接从浏览器以外的地方上传档案,所以马上来说说後端对於使用者传上来的档案该做哪些检查

後端

先举个 Node.js 的例子,如果你的 API server 是用 multer 在处理档案上传的话,那可以透过 fileFilter 来设定要接受哪些类型的档案,譬如说当 file.mimetype 是 jpg 或 jpeg 才接受,其他的档案类型则拒绝上传

var upload = multer({
    storage: storage,
    fileFilter: (req, file, cb) => {
        if (file.mimetype == "image/jpg" || file.mimetype == "image/jpeg") {
            cb(null, true)  // 接受这个档案
        } else {
            cb(null, false) // 拒绝这个档案
            return cb(new Error('Only .jpg/.jpeg format allowed!'))
        }
    }
})

app.post('/avatar', upload.single('avatar'), function (req, res) {
    // save image here
});

而在 Gin(Go 的框架)里面做起来也满简单的,只要先从 *gin.Context 里面拿到档案,再判断 MIME Type 是不是允许的类型就可以了~如果不是的话就直接回一个 400 给他

import (
    "github.com/gabriel-vasile/mimetype"
    "github.com/gin-gonic/gin"
)

func handler(ctx *gin.Context) error {
    fileHeader, err := ctx.FormFile("file")
    file, err := fFile.Open()
        
    mimeType, err := mimetype.DetectReader(io.Reader) // 侦测 MIME type
        
    // 把不要的格式拒绝掉
    if mimeType != "image/png" {
        c.JSON(400, gin.H{"error": "Only .png format allowed!"})
    }
}

档案大小

除了档案类型之外,限制档案大小也非常重要,如果完全不做限制,那攻击者只要同时上传一堆很大的档案,譬如说一次塞给你 10GB,那整个服务器就会变得非常慢,记忆体也有可能因此被塞爆

因为这个功能非常常见,所以 Node.js 的 multer 也实作了一个 limits 属性让你做设定,如果你想限制上传的大头贴最大 10MB,那就设定 10 << 20(也就是 10 * 1024 * 1024 bytes)就可以了~

var upload = multer({
    storage: storage,
    fileFilter: (req, file, cb) => { /* ... */ },
    limits: { fileSize: 10 << 20 } // 在这里设定 max size = 10MB
});

而 Go 的话也很简单,只要在进到 handler 之前先过一个 size limiter 就可以了,这样就能确保上传的档案都不会太大

import "github.com/gin-contrib/size"

func handler(ctx *gin.Context) {
    // 先检查刚刚有没有发生错误
    if len(ctx.Errors) > 0 {
        return
    }

    // 都没问题再把档案存起来
}

func main() {
    router := gin.Default()

    // 先检查 request size 再进到 handler
    router.POST("/avatar", limits.RequestSizeLimiter(10 << 20), handler)

    router.Run(":8888")
}

小结

今天介绍了为什麽 API Server 在收到档案时要检查 MIME Type 跟档案大小,也示范了在 Node.js 跟 Go 里面怎麽用现有的 library 来实作

关於「入口管制」的部分就到今天结束,如果对於内容有什麽问题的话欢迎在下方留言,没有的话明天就要开始「流量限制」的部分罗~

延伸阅读


<<:  Day 08 : 资料视觉化 Seaborn

>>:  [FGL] 程序开发(2) - 4类6个交谈指令-功能选单部分

乔叔教 Elastic - 30天文章总整理 + 完赛心得

这次参加铁人赛 30 天分享的文章,透过这篇做个总整理,并且建立一个索引目录,让大家能透过这系列文章...

心血管疾病notebook使用MLFlow做记录

上一篇我们已经完成心血管疾病资料的训练并且产生model档. 本篇我们再来加入MLflow的功能, ...

Day 29-ASP.NET & SQL资料库制作留言板(中)

-前集提要- 要如何把留言的资料(ASP.NET)存到资料库(MSSQL)的留言板。会使用到的工具有...

[Day20] 与问题成员对话-案例二:水涨船高的 Scrum Master 接班人

今天来聊聊有关对话的第二个案例: 案例二:水涨船高的 Scrum Master 接班人 推动改革,很...