answerdevanswer是基于go语言编写的一个问答平台(类似于知乎),前几周在审计该项目时,发现该系统的图片上传功能点存在一个有趣的漏洞。 该系统的图片上传功能点的工作原理大致如下: step1。用户上传图片文件,将文件存储在本地文件系统中 step2。当需要访问图片时,使用gin框架提供的静态资源服务器将用户上传的图片文件作为静态资源返回给用户 internalrouterstaticrouter。goRegisterStaticRouterregisterstaticapirouterfunc(aStaticRouter)RegisterStaticRouter(rgin。RouterGroup){r。Static(uploads,a。serviceConfig。UploadPath)} 为了防止用户上传恶意文件,该系统的文件上传功能设置了一个后缀白名单,用户只能上传白名单中后缀的文件 internalserviceuploaderupload。goFormatExtsmap〔string〕imaging。Format{。jpg:imaging。JPEG,。jpeg:imaging。JPEG,。png:imaging。PNG,。gif:imaging。GIF,。tif:imaging。TIFF,。tiff:imaging。TIFF,。bmp:imaging。BMP,} 起初,我在本地搭建了该项目,对文件上传功能和静态资源服务的进行了测试:我尝试上传文件内容为,文件后缀为。bmp。tif。tiff的文件,然后通过静态资源服务访问这些文件,我发现返回的响应报文中ContentType分别为imagebmpimagetiffimagetiff,这些MIME都是正常的图片类型,无法使得浏览器将响应报文中的内容作为html来解析,从而造成XSS。 但是,神奇的是当我使用answer官方提供的docker镜像来搭建answer时:dockerrundp9080:80vanswerdata:datanameansweranswerdevanswer:latest 再次上传同样的文件,然后访问文件,发现返回的http响应报文的ContentType竟然变为了texthtml! step1。上传图片文件 step2。访问文件 罪魁祸首mime标准库 之所以go实现的静态资源服务器会出现这种将bmptiftiff等后缀的图片文件的ContentType设置为texthtml的情况,是因为go语言的mime标准库的实现有问题。 go语言实现的静态资源服务器在返回文件时,大致会执行以下步骤: step1。调用mime。TypeByExtension()函数来根据文件后缀获取对应的ContentType step2。如果mime。TypeByExtension()函数返回的ContentType为空字符串,则会进行ServerSideMIMESniff,即根据文件内容来判断对应的ContentType 而mime。TypeByExtension()函数的实现实际上是依赖于外部的mime。types文件的,其本身所维护的文件后缀与ContentType的映射关系非常有限 usrlocalgosrcmimetype。goTypeByExtensionreturnstheMIMEtypeassociatedwiththefileextensionext。Theextensionextshouldbeginwithaleadingdot,asin。html。Whenexthasnoassociatedtype,TypeByExtensionreturns。Extensionsarelookedupfirstcasesensitively,thencaseinsensitively。ThebuiltintableissmallbutonunixitisaugmentedbythelocalsystemsMIMEinfodatabaseormime。typesfile(s)ifavailableunderoneormoreofthesenames:usrlocalsharemimeglobs2usrsharemimeglobs2etcmime。typesetcapache2mime。typesetcapachemime。typesOnWindows,MIMEtypesareextractedfromtheregistry。 在容器化过程中,为了追求最小化攻击面、更小的镜像体积,往往会使用alpine系列镜像,而该系列镜像中并没有以上所列的mime。types文件:usrlocalsharemimeglobs2usrsharemimeglobs2etcmime。typesetcapache2mime。typesetcapachemime。types 例如: UsersrickshangCodeSecurityResearchInTheLabcontenttypelabgolangfuzzermain。gopackagemainimport(fmtmime)funcmain(){exts:〔〕string{。bmp,。gif,。jpeg,。jpg,。png,。svg,ico,。tif,。tiff,。webp}for,ext:rangeexts{contenttype:mime。TypeByExtension(ext)fmt。Printf(ext:scontenttype:v,ext,contenttype)}} 本地运行:gorunmain。goext:。bmpcontenttype:imagebmpext:。gifcontenttype:imagegifext:。jpegcontenttype:imagejpegext:。jpgcontenttype:imagejpegext:。pngcontenttype:imagepngext:。svgcontenttype:imagesvgxmlext:。icocontenttype:imagexiconext:。tifcontenttype:imagetiffext:。tiffcontenttype:imagetiffext:。webpcontenttype:imagewebp 使用golang官方镜像:golang:1。19alpine容器化之后运行:dockerrunitrmvUsersrickshangCodeSecurityResearchInTheLabcontenttypelabgolangfuzzermain。go:codemain。gowcodegolang:1。19alpinegorunmain。goext:。bmpcontenttype:ext:。gifcontenttype:imagegifext:。jpegcontenttype:imagejpegext:。jpgcontenttype:imagejpegext:。pngcontenttype:imagepngext:。svgcontenttype:imagesvgxmlext:。icocontenttype:ext:。tifcontenttype:ext:。tiffcontenttype:ext:。webpcontenttype:imagewebp 话句话说,如果你的静态资源服务器是基于go语言的mime标准库来实现的,那么你的静态资源服务器在使用alpine镜像容器化之后很可能会出现这种将bmptiftiff等后缀的图片文件的ContentTypes识别为texthtml的情况,进而导致存储型XSS漏洞。总结 该漏洞有趣的点在于,它揭示了安全问题与环境的关系:测试环境下没有安全问题,不代表生产环境下也没有安全问题。 容器化在追求最小化攻击面的同时也引入了新的攻击面,go语言mime标准库的问题便是典型的例子。依赖外部文件的标准库实现容器化外部文件缺失标准库功能出现安全问题修复方案 go语言mime标准库维护的内置的mime类型映射关系非常有限:gosrcmimetype。govarbuiltinTypesLowermap〔string〕string{。avif:imageavif,。css:textcss;charsetutf8,。gif:imagegif,。htm:texthtml;charsetutf8,。html:texthtml;charsetutf8,。jpeg:imagejpeg,。jpg:imagejpeg,。js:textjavascript;charsetutf8,。json:applicationjson,。mjs:textjavascript;charsetutf8,。pdf:applicationpdf,。png:imagepng,。svg:imagesvgxml,。wasm:applicationwasm,。webp:imagewebp,。xml:textxml;charsetutf8,} 方案一:打包镜像时将如下mime。types文件拷贝到容器中:usrlocalsharemimeglobs2usrsharemimeglobs2etcmime。typesetcapache2mime。typesetcapachemime。types 方案二:在实现图片上传功能时,不要将mime标准库内置表之外的后缀类型添加到白名单中,例如:。bmp。ico。tif。tiff 方案三:使用nginx作为静态资源服务器进一步的研究其他语言的静态资源服务器容器化后是否存在同样的问题?容器化是否会引入其他新的安全问题? fromhttps:tttang。comarchive1880