
Content-Type Override 在云存储桶公有可读对象里的坑一、背景介绍很多上传点的防护都放在“上传那一刻”限制后缀、检查文件头、控制大小、强制写入Content-Type。这些检查当然有用但它们只覆盖了前半段。真正容易漏掉的是后半段文件已经进了对象存储之后用户访问的往往是一个公开 URL例如https://cdn.target.com/uploads/avatar123.png这个请求可能不再经过业务后端也就不会再触发上传接口里的校验逻辑。浏览器最终怎么处理这份文件主要看响应头。如果攻击者能让响应从image/png变成text/html同一份内容在浏览器里的意义就完全变了原本是图片现在可能变成页面。这个问题成立有一个关键前提上传文件需要能在目标自己的域名下被访问比如cdn.target.com或target.com/uploads/。如果只是落在一个无关的存储域名上影响通常会小很多。二、正文 / 案例分析1. 正常访问服务端说它是图片假设目标把头像文件放在公开 bucket 里。普通访问时响应头是干净的GET /uploads/avatar123.png HTTP/1.1 Host: cdn.target.com HTTP/1.1 200 OK Content-Type: image/png到这里看起来没问题。上传接口做了限制返回也确实是图片类型。2. 加一个参数响应类型被覆盖问题出现在读取对象时。某些对象存储或兼容 S3 的服务支持通过查询参数覆盖响应头例如GET /uploads/avatar123.png?response-content-typetext/html HTTP/1.1 Host: cdn.target.com HTTP/1.1 200 OK Content-Type: text/html文件内容没变路径也没变只是多了一个参数。但浏览器看到的是text/html于是它不再把这份内容当图片而是尝试按 HTML 页面处理。如果上传内容里藏了可执行的 HTML/JS 片段并且它能绕过上传阶段的文件检查就可能形成存储型 XSS。3. MinIO 场景参数直接生效MinIO 支持一组 response header override 参数其中就包括response-content-type。在公开对象可读的情况下这个参数可能直接把对象原本的Content-Type覆盖掉。参考如下腾讯云的云存储桶 get object的请求参数对漏洞测试来说最直接的验证方式就是看响应头curl -I https://cdn.target.com/uploads/avatar123.png?response-content-typetext/html如果返回里出现Content-Type: text/html就说明读取阶段的类型被你控制了。接下来再结合上传点、文件内容和目标域名判断是否能形成有效 XSS。4. S3 场景匿名请求会被拦但不代表没戏AWS S3 也支持类似的响应头覆盖参数但匿名 GET 请求不能直接使用。直接拼参数时通常会看到类似错误Request specific response headers cannot be used for anonymous GET requests.这句话的意思不是“没有这个能力”而是“匿名请求不能这么用”。如果对象本身是公开可读的授权测试时可以继续检查预签名 URL 场景在签名请求里加入ResponseContentTypetext/html再看浏览器最终拿到的响应头。如果对象本身公开可读任何有效 AWS 身份都可能读取它。授权测试环境里可以用自己的 AWS 凭证给这个公开对象生成一个 presigned URL并把ResponseContentTypetext/html放进签名参数里。打开签名链接后再看最终响应头是否变成 HTML。Params{ Bucket: bucket, Key: key, ResponseContentType: text/html }某些情况下还可以通过添加任意sign字段绕过形如GET /uploads/avatar123.png?response-content-typetext/htmlsign111 HTTP/1.1 Host: cdn.target.com5. 判断是否值得提交漏洞这个问题不是看到参数生效就一定高危关键看上下文。看域名是否在目标自己的主域、子域或反代路径下触发。看内容上传内容是否能被构造成浏览器可执行的 HTML。看链路用户是否会访问这个公开文件是否能被分享到业务页面里。看隔离是否有独立静态资源域、CSP、下载头、沙箱域等隔离措施。三、总结这个案例的价值在于提醒我们文件上传漏洞不要只盯上传接口。很多时候真正的问题出在“文件被取出来的那一刻”。上传时强制写入image/png不代表浏览器最终一定按图片处理如果读取阶段允许覆盖Content-Type前面的校验就可能被绕开。测试时可以记住三句话公开对象的读取 URL也要当成攻击面看。Content-Type是浏览器渲染决策的一部分不只是一个响应头。能不能报成有效漏洞最终取决于是否在目标可信 origin 下执行。修复建议用户上传内容尽量放到独立静态资源域不要和主站共享敏感 origin。禁止或过滤公开对象上的响应头覆盖参数尤其是response-content-type。对不可控文件使用Content-Disposition: attachment避免浏览器 inline 渲染。不要只在上传接口做校验也要检查 CDN、反代、对象存储的读取行为。