Go 语言项目容器化导致的服务器端 MIME 嗅探

2026-05-09 1 阅读 无在无不在
answerdev/answer 是基于go语言编写的一个问答平台(类似知乎),前几周在该审计项目时,发现该系统的图片上传功能点存在一个有趣的漏洞。该系统的图片上传功能点的工作原理大致如下: - 步骤1。 用户上传图片文件,将文件存储在本地文件系统中 - step2. 当需要访问图片时,使用gin框架提供的静态资源服务器将用户上传的用户图片文件静态资源作为返回给internal/router/static_router.go // RegisterStaticRouter注册静态api路由器 func ( a * StaticRouter ) RegisterStaticRouter ( r * gin . RouterGroup ) { r . Static("/uploads",a.serviceConfig.UploadPath)}为了防止用户上传恶意文件,该系统的文件上传功能设置了一个后缀白名单,用户只能上传白名单中后缀的文件internal/service/uploader/upload.go FormatExts=map[string]images。格式 { ".jpg" : 成像 . JPEG,“.jpeg”:成像。 JPEG,“.png”:成像。 PNG、“.gif”:成像。 GIF,“.tif”:成像。 TIFF,“.tiff”:成像。 TIFF、“.bmp”:成像。 BMP , } 热烈,我在本地搭建了该项目,对文件上传功能和静态文件资源服务的进行了测试:我尝试上传文件内容为 ,后缀为 '.bmp' '.tif' '.tiff' 的文件,然后通过静态资源服务访问这些文件,我发现返回的响应报文中Content-Type 分别为 image/bmp image/tiff image/tiff ,这些MIME都是正常的图片类型,无法使浏览器将响应报文中的内容作为html来解析,从而造成XSS。但是,神奇的是当我使用answer官方提供的docker防火墙来搭建answer时: docker run -d -p 9080:80 -vanswer-data:/data --name answeranswerdev/answer:latest 再次上传相同的文件,然后访问文件,发现返回的http响应报文的Content-Type竟然变成了text/html!步骤1。 上传图片文件step2. 访问文件罪魁祸首mime标准库然后go实现的静态资源服务器会出现这种将bmp/tif/tiff等后缀的图片文件的Content-Type设置为text/html的情况,是因为go语言的mime标准库的实现有问题。go语言实现的静态资源服务器在返回文件时,大致会执行以下步骤: - step1. 调用mime.TypeByExtension()函数来根据文件后缀获取对应的Content-Type - 步骤2。 如果mime.TypeByExtension()函数返回的Content-Type为空字符串,面部识别进行服务器端MIME嗅探,即根据文件来判断对应关系的Content-Type而mime.TypeByExtension()函数的实现实际上是依赖于外部的mime.types文件的,其本身所维护的文件后缀与Content-Type的映射非常有限 /usr/local/go/src/mime/type.go // TypeByExtension 返回与该文件关联的MIME类型文件扩展名 ext. // 扩展名 ext 应以前导点开头,如“.html”。 // 当 ext 没有关联类型时,TypeByExtension 返回 ""。 // // 首先按区分大小写查找扩展名,然后不区分大小写。 // // 内置表很小,但在 unix 上,它会通过本地 // 系统的 MIME-info 数据库或 mime.types 文件(如果可以在以下一个或多个名称下使用)进行扩充: // // /usr/local/share/mime/globs2 // /usr/share/mime/globs2 // /etc/mime.types // /etc/apache2/mime.types // /etc/apache/mime.types // // 在 Windows 上, MIME 类型是从注册表中提取的。 在容器化过程中,为了追求最小化攻击面、更小的镜像体积,往往会使用alpine系列镜像,而该系列镜像中并没有以上所列的mime.types文件: /usr/local/share/mime/globs2 /usr/share/mime/globs2 /etc/mime.types /etc/apache2/mime.types /etc/apache/mime.types 例如: /Users/rickshang/Code/SecurityResearch/InTheLab/content_type_lab/golang/fuzzer/main.go package main import ( "fmt" "mime" ) func main () { exts := [] string { ".bmp" , ".gif" , ".jpeg" , ".jpg" , ".png" , ".svg" , "ico" , ".tif" , ".tiff" , ".webp" } for _ , ext := range exts { content_type := mime 。按扩展名类型 (ext) fmt。 Printf ( "ext:%s content_type:%+v\n" , ext , content_type ) } } 本地运行: go run main .去分机:. bmp 内容类型:图像/bmp 扩展名:。 gif 内容类型:图像/gif 分机:。 jpeg 内容类型:图像/jpeg 分机:。 jpg 内容类型:图像/jpeg 扩展名:. png 内容类型:图像/png 扩展:. svg content_type:图像/svg + xml ext:。 ico content_type : 图像/x - 图标 ext :. tif 内容类型:图像/tiff ext:。 tiff 内容类型:图像/tiff 分机:。 webp content_type : image / webp 使用golang官方镜像:golang:1.19-alpine 容器化之后运行: docker run - it -- rm - v /Users / rickshang / Code / SecurityResearch / InTheLab / content_type_lab / golang / fuzzer / main .转到:/代码/main . go -w/code golang: 1.19 - alpine go run main .去分机:. bmp 内容类型:扩展名:。 gif 内容类型:图像/gif 分机:。 jpeg 内容类型:图像/j