如何基于元数据删除AWS S3存储桶文件 防止用户越权删除他人文件
问题根因
你当前代码中传入DeleteObjectCommand的Metadata参数是完全无效的:S3的DeleteObject接口本身不支持携带元数据做删除前的匹配校验,接口只会识别Bucket和Key两个核心参数,只要请求发起方拥有对应桶的删除权限,传对目标对象的Key就会直接执行删除操作,你额外传的Metadata字段会被服务端直接忽略,因此才会出现无论元数据是否匹配都能删除成功的现象。
推荐落地方案
按可靠性从高到低排列,优先选择前两种S3原生层面的拦截方案,比纯业务层校验更稳妥:
- 按用户ID做对象Key路径隔离
这是成本最低、可靠性最高的方案。存储文件时就将用户ID作为Key的固定前缀,例如Key格式统一为user-files/{userId}/{实际文件名/文件唯一ID},删除时完全不要信任前端传入的完整Key,仅接收前端传递的文件唯一标识,由服务端拼接当前登录用户ID生成最终的S3 Key,从根源上避免用户能构造出指向其他用户文件的Key。
注意你当前代码中使用basename(req.body.key)的写法存在安全隐患:basename会剥离传入路径的所有前缀部分,攻击者完全可以构造携带其他用户路径的参数绕过简单校验。 - 配置S3权限策略做访问边界约束
如果你使用STS临时凭证给端侧分配S3操作权限,可以在生成临时凭证的权限策略中添加资源路径条件,强制限制当前凭证仅能操作对应用户ID前缀下的对象,即使业务层代码出现逻辑漏洞,S3层面也会直接拦截越权删除请求,不会执行非法操作。 - 删除前主动校验对象归属(兜底方案)
如果暂时无法调整现有文件的Key格式,可以在执行删除操作前,先调用HeadObjectCommand查询目标对象的元数据,比对对象上存储的userId元数据是否和当前登录用户ID一致,校验通过后再发起删除请求。该方案存在极短的竞态窗口(校验完成到发起删除的间隙对象可能被替换),仅适合作为兜底补充方案,不建议作为唯一拦截手段。
修正后的参考代码
static async deleteFile(req, res) { try { // 仅接收前端传的文件唯一标识,不接受完整Key const fileId = req.body.fileId const userId = req.user._id // 服务端强制拼接用户专属前缀,生成最终Key const key = `user-files/${userId}/${fileId}` // 兜底元数据校验(已做路径隔离+IAM权限约束时可省略) const headRes = await s3.send(new HeadObjectCommand({ Bucket: "my_bucket", Key: key })) // 注意S3返回的元数据键名默认全小写,不要用驼峰字段匹配 if (headRes.Metadata?.userid !== userId.toString()) { return res.status(403).send({ error: "无权限删除该文件" }) } const data = await s3.send(new DeleteObjectCommand({ Bucket: "my_bucket", Key: key })) return res.send(data) } catch (err) { // 对象不存在时统一返回403,避免攻击者探测文件存在性 if (err.$metadata?.httpStatusCode === 404) { return res.status(403).send({ error: "无权限删除该文件" }) } console.log(err) return res.status(500).send({ error: "删除失败" }) } }
内容的提问来源于stack exchange,提问作者golder3
相关产品推荐
相关产品推荐

