如何限制亚马逊S3预签名URL仅对当前用户开放上传并防范存储滥用
S3预签名URL滥用防护方案
滥用场景复现代码
// 黑客滥用服务的复现代码 const { default: axios } = require("axios"); async function uploadFromMaliciousSource() { // 黑客在站点注册账号后获取自己的jwt token // 此处申请的预签名URL原本用于用户帖子的图片上传 const { data: signedUrl } = await axios.get("http://khelkhelo.in", { headers: { "x-auth-token": "用户的jwt token", }, }); // 黑客拿到预签名URL后就可以上传任意内容,比如盗版电影等违规资源 const formdata = new FormData() formdata.append() // ... } uploadFromMaliciousSource();
具体防护措施
- 绑定预签名URL的核心参数限制
生成预签名URL时不要生成通用上传地址,必须强制绑定文件属性,从S3侧拦截不符合要求的上传请求:- 前端申请URL时必须先上传文件的大小、MIME类型、SHA256哈希值到Nodejs服务,服务校验参数在合理范围(比如单张图片不超过10MB、类型仅允许image/jpeg、image/png等)后再生成签名
- 生成签名时通过S3的policy规则绑定存储路径、文件大小、类型、哈希值,拿到URL的用户只能上传完全匹配参数的文件,即使URL泄露也无法上传大文件、非图片类违规内容
- 存储路径强制拼接用户唯一ID作为前缀,后续发现违规内容可以直接定位到对应账号溯源
生成绑定限制的预签名URL示例代码:
const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3"); const { getSignedUrl } = require("@aws-sdk/s3-request-presigner"); const s3Client = new S3Client({ region: "你的S3区域" }); async function generatePresignedUrl(userId, fileSize, fileType, fileHash) { // 存储路径强制绑定用户ID前缀,禁止自定义路径 const fileExt = fileType.split('/')[1]; const storageKey = `upload/${userId}/${Date.now()}_${Math.random().toString(36).slice(2)}.${fileExt}`; const command = new PutObjectCommand({ Bucket: "你的存储桶名称", Key: storageKey, ContentType: fileType, // 绑定文件哈希,上传内容不匹配S3直接拒绝 xAmzContentSha256: fileHash, ContentLength: fileSize }); // 有效期设置为3分钟,足够正常上传使用 return getSignedUrl(s3Client, command, { expiresIn: 180 }); }
设置合理的有效期与接口限流
- 单张图片上传的预签名URL有效期不要超过5分钟,大幅缩短URL泄露后的可用窗口
- 对申请预签名URL的接口做账号维度、IP维度的频率限流,短时间内多次申请直接拦截,防止批量刷取URL的行为
配置账号上传配额
服务层做账号维度的配额管控:单用户单日上传文件数量上限、总上传容量上限,超过阈值直接拒绝生成预签名URL的申请,从源头避免被批量滥用消耗存储资源上传后异步校验内容
用户上传完成后不要直接对外暴露资源,先触发异步校验:- 校验文件魔数和实际类型,防止通过伪造后缀上传非图片内容
- 校验图片内容是否合规,违规内容直接删除,同时对对应账号做限制上传、封号等处罚
S3存储桶侧配置兜底规则
在S3侧配置额外的防护规则作为兜底:- 开启存储桶的上传大小限制,禁止超过单张图片合理大小的文件写入
- 开启存储桶访问日志,定期审计异常上传行为,发现短时间大量大文件写入、非图片类型上传等情况及时处理
内容的提问来源于stack exchange,提问作者Juno
相关产品推荐
相关产品推荐

