SN-2026-024:GetObjectAttributes 与只读 ZIP 归档扩展(x-minio-extract)标签策略及读取权限绕过
发布状态: 主干已修复(f746d0707、296c9710b),将随 20261109 之后的下一版 Server 发布
安全编号: SN-2026-024(安全公告台账,#342)
严重程度: 高危(High)
影响范围: SILO Server(RELEASE.2026-11-09T00-00-00Z 及更早版本)与自 2021-06-10(ba5fb2365)和 2024-01-05(9b8ba97f9)以来的上游原版 MinIO
不受影响组件: silo-console、mcli、silo-pkg 与 kes
在 SILO 的标准 GetObject 与 HeadObject 处理链路中,服务端先完成调用方身份认证,读取目标对象版本的 ObjectInfo,再将 objInfo.UserTags 传入策略引擎校验 s3:GetObject(或 s3:GetObjectVersion),从而确保基于对象已有标签条件(s3:ExistingObjectTag/<key>)的 IAM 规则准确生效。继承自上游 MinIO 的两类读取接口绕过了这一步“读取元数据后的标签复核”:GetObjectAttributes(GET /<bucket>/<key>?attributes)以及 .zip 归档在线读取扩展(携带 x-minio-extract: true 的 GET、HEAD 与 ListObjectsV2)。此外,归档内部成员列举仅凭桶级 s3:ListBucket 权限即可解析 .zip 中央目录而无需 s3:GetObject,且读取未预建索引的 .zip 对象时会在只读动词中调用 PutObjectMetadata 改写磁盘元数据。
摘要
- 问题本质:
GetObjectAttributes绕过s3:ExistingObjectTag条件策略:getObjectAttributesHandler(cmd/object-handlers.go)在调用objectAPI.GetObjectInfo之前执行checkRequestAuthType(内部传入空标签""),读取到objInfo.UserTags后从未重新评估s3:GetObjectAttributes与s3:GetObject(或带版本号的对应动作)。受s3:ExistingObjectTag/*显式Deny限制的调用方仍可获得200 OK并读取对象校验和、分片布局与大小,而依赖标签匹配才授予Allow的调用方则被误拒为403 AccessDenied。- ZIP 成员
GET/HEAD绕过标签Deny读取明文(x-minio-extract: true):getObjectInArchiveFileHandler与headObjectInArchiveFileHandler(cmd/s3-zip-handlers.go)同样在读取.zip对象标签前按空标签鉴权。被策略明确禁止访问带敏感标签(如classification=secret)对象的调用方,虽然直接请求GET /bucket/archive.zip会收到403,但只需附带x-minio-extract: true请求GET /bucket/archive.zip/secret.txt,即可绕过标签拦截直接提取归档内的任意明文文件。 - ZIP 成员
ListObjectsV2绕过s3:GetObject泄露归档内部结构:listObjectsV2InArchive(携带x-minio-extract: true请求GET /bucket?list-type=2&prefix=archive.zip/)直接复用了外层桶级s3:ListBucket鉴权结果,未检查调用方是否对archive.zip拥有s3:GetObject权限。即使策略显式配置了Deny s3:GetObject,仅持有列桶权限的账号仍可读取.zip数据流并枚举包内全部文件名、未压缩字节大小与时间戳。 - 只读请求附带隐式元数据写入: 当被读取的
.zip对象在上传时未预建归档索引时,readObjectZIPInfo会在GET、HEAD与ListObjectsV2请求中后台调用PutObjectMetadata将生成的索引写回xl.meta,违反了只读 HTTP 动词不修改持久化元数据的边界。
- 上游溯源:
.zip在线提取扩展由上游 MinIO 于2021-06-10在提交ba5fb2365(minio/minio#12267)中引入;GetObjectAttributes由上游于2024-01-05在提交9b8ba97f9(minio/minio#18732)中引入。 - 修复方式: 提交
f746d0707与296c9710b将两类路径统一拆分为前置身份认证与读取元数据后的真实标签鉴权:对GetObjectAttributes的双动作及.zip归档的GET、HEAD、ListObjectsV2全量绑定目标版本的真实UserTags,强制要求归档列举具备针对.zip对象的s3:GetObject权限,并通过readObjectZIPInfo将读取路径上的归档索引构建改为纯内存无副作用操作。
一、上游溯源与根因分析
为什么前置调用 checkRequestAuthType 会漏掉对象标签条件
在 cmd/auth-handler.go 中,checkRequestAuthType 将身份认证(authenticateRequest)与基于空标签的策略求值(authorizeRequestWithExistingTags(..., ""))打包为一步,适用于桶级操作或尚未存在目标对象的新建写入操作。而所有读取存量对象且需支持 s3:ExistingObjectTag/<key> 的接口(如标准 GetObject 与 HeadObject)都必须拆为两步:
- 访问存储前先调用
authenticateRequest完成验签; - 读取
ObjectInfo(或注入opts.CheckPrecondFn回调)后,将磁盘持久化的objInfo.UserTags传入authorizeRequestWithExistingTags。
上游在实现 ba5fb2365(cmd/s3-zip-handlers.go)与 9b8ba97f9(getObjectAttributesHandler)时,均只保留了前置的 checkRequestAuthType 而遗漏了读取 ObjectInfo 后的标签鉴权:
- 在
GetObjectAttributes中,按 AWS S3 规范调用方须同时具备s3:GetObjectAttributes与s3:GetObject(带?versionId=时为s3:GetObjectVersionAttributes与s3:GetObjectVersion)。前置按空标签求值导致针对任一动作的标签Deny失效、标签Allow被误拒。 - 在
.zip成员GET/HEAD(x-minio-extract: true) 中,只要桶内敏感数据以.zip归档存放并依靠对象标签做多租户或密级隔离,攻击者无需读取整包,直接请求归档内成员路径即可绕过标签Deny下载明文内容。
桶列举权限与对象内容读取权限的越界混淆
当客户端发送 GET /bucket?list-type=2&prefix=archive.zip/ 并携带 x-minio-extract: true 时,listObjectsV2Handler(cmd/bucket-listobjects-handlers.go)在校验完桶级 s3:ListBucket 后即转入 listObjectsV2InArchive。与普通命名空间前缀列举不同,listObjectsV2InArchive 需要调用 getFilesListFromZIPObject(底层执行 GetObjectNInfo)打开并解析 archive.zip 的对象数据字节。仅凭桶级 s3:ListBucket 即允许解析 .zip 内部目录,突破了“列举桶内对象键”与“读取对象内部载荷”之间的权限边界。
二、影响范围
| 受影响接口 / 能力 | 安全与行为影响 |
|---|---|
GetObjectAttributes(?attributes) |
绕过基于 s3:ExistingObjectTag/* 的 Deny 规则(泄露校验和、ETag、分片数与字节大小);基于标签匹配的 Allow 规则被误拒(403)。 |
ZIP 成员 GET / HEAD(x-minio-extract) |
完全绕过基于 s3:ExistingObjectTag/* 的 Deny 规则读取 .zip 包内任意文件明文;基于标签匹配的 Allow 规则被误拒。 |
ZIP 成员 ListObjectsV2(x-minio-extract) |
仅持有 s3:ListBucket 的主体即使被显式 Deny s3:GetObject,仍可读取任意 .zip 包内的完整文件清单与未压缩大小。 |
读取未预建索引的 .zip 对象 |
GET、HEAD、LIST 在读取过程中调用 PutObjectMetadata 改写对象持久化元数据。 |
三、修复方案:设计思考与充分必要性
提交 f746d0707 与 296c9710b 将上述全部读取路径对齐至存储后标签鉴权与只读无副作用不变量(并与 89028732a / #294 在 GetObject/HeadObject 上保留标签条件读取的 5xx 存储错误保持一致):
GetObjectAttributes双动作真实标签鉴权(cmd/object-handlers.go):- 根据是否指定
opts.VersionID选定[s3:GetObjectAttributes, s3:GetObject]或对应*Version*双动作,前置执行authenticateRequest; - 读取
objInfo后,对两个动作逐一执行authorizeRequestWithExistingTags(..., objInfo.UserTags)(读取报错时通过authorizeRequestWithUnreadTags判断权限),同时保留缺失对象的s3:ListBucket掩码语义,并确保已授权调用方在遇到底层磁盘读仲裁不足时收到真实的503 SlowDownRead而非误报403。
- 根据是否指定
- ZIP
GET、HEAD与ListObjectsV2闭环鉴权(cmd/s3-zip-handlers.go、cmd/bucket-listobjects-handlers.go):- 将鉴权目标
ReqInfo.ObjectName精确绑定为外层归档键名zipPath(标签与对象级策略均挂载在.zip对象自身上); - 在初次
GetObjectInfo与后续GetObjectNInfo范围读取的opts.CheckPrecondFn回调中均执行authorizeRequestWithExistingTags(ctx, r, policy.GetObjectAction, oi.UserTags),消除元数据读取与分块解压之间的并发标签变更窗口; - 在
listObjectsV2InArchive中强制校验调用方对zipPath的s3:GetObject权限(含真实标签条件),未获授权时返回PrefixAccessDenied(403 AccessDenied)。
- 将鉴权目标
- 剥离只读路径上的元数据写回(
cmd/s3-zip-handlers.go):readObjectZIPInfo改为纯内存构建并返回序列化索引,绝不调用PutObjectMetadata;持久化写回函数updateObjectMetadataWithZipInfo仅保留给显式携带x-minio-extract: true的上传写路径调用。
为什么该设计既必要又充分
- 必要性: 在
.zip提取路径上,服务端先读一次GetObjectInfo定位成员偏移量,随后再发起一次GetObjectNInfo读取压缩数据段;若不在opts.CheckPrecondFn内二次校验所打开版本的UserTags,并发打标仍可能产生竞态窗口。同时,只有彻底移除读取时的PutObjectMetadata,才能保证只读请求不污染xl.meta、不触发意外的元数据复制,也不会在只读或对象锁场景下产生隐式写冲突。 - 充分性: 所有涉及对象属性与
.zip归档读取的入口(getObjectAttributesHandler、getObjectInArchiveFileHandler、headObjectInArchiveFileHandler、listObjectsV2InArchive)均已纳入同一套基于真实标签的鉴权模型,并由cmd/object-attributes-archive-tags_test.go与cmd/object-attributes-read-error_test.go完整覆盖正反向条件策略与存储异常场景。
四、利弊权衡、潜在代价与边界
| 权衡维度 | 收益与代价说明 |
|---|---|
未预建索引 .zip 对象的重复读取开销 |
修复前,普通上传的 .zip 在首次被 x-minio-extract: true 读取时会把目录索引写回 xl.meta 供后续复用;修复后只读请求不再改写磁盘元数据,未预建索引的 .zip 每次按归档方式读取时会从末尾中央目录在内存中构建索引。对超大 .zip 有高频在线提取需求的业务,可在上传(PutObject / CompleteMultipartUpload)时显式附带 x-minio-extract: true 持久化索引。 |
归档内部列举必须具备 s3:GetObject 权限 |
此前仅被授予 s3:ListBucket(而无权读取 .zip 对象内容)的账号将无法再通过 x-minio-extract: true 枚举 .zip 内部成员列表(返回 403 AccessDenied)。确需在线浏览包内目录的角色需补充对相应 .zip 前缀的 s3:GetObject 授权。 |
升级指引与缓解建议
- 升级 SILO Server 至包含提交
f746d0707与296c9710b的20261109后续发布版本。 - 升级前网关层临时缓解: 若未升级集群依赖
s3:ExistingObjectTag/*的Deny规则保护敏感对象(尤其是.zip归档包),可在升级完成前于反向代理层拦截携带x-minio-extract请求头或?attributes查询参数的外部请求。 - 核对归档浏览角色的 IAM 策略: 若业务应用使用了
x-minio-extract: true配合ListObjectsV2在线展示.zip内部目录,升级前请确认该角色除s3:ListBucket外已同时具备目标.zip对象的s3:GetObject权限。
更多信息请参阅安全公告台账、Server 候选兼容性说明以及关联公告 SN-2026-022(双活复制标签代理鉴权)。