1. 从一次“意外发现”说起:为什么OSS存储桶总上榜
在甲方做安全的时候,我最怕的不是哪个系统被打穿,而是某天收到一条告警:“某存储桶存在公共读权限,疑似存在敏感数据泄露风险。”点进去一看,好家伙,整个业务网站的备份数据库、用户上传的身份证照片、甚至内网运维脚本全躺在里面,任何人只要拼接出URL就能下载。
这类问题之所以高频,核心原因其实很朴素:对象存储(Object Storage Service,简称OSS)本身是为了“易用”而生的。云厂商为了让开发者能快速接入,默认权限往往倾向宽松,而很多团队在完成功能开发后,就再也没回去看过存储桶的权限配置。再加上对象存储的访问模型和传统文件系统完全不同,不少从LNMP时代过来的开发者会把一些惯性思维带进来,于是埋下隐患。
所谓“每天看一种漏洞类型”,我觉得本质上不是要背漏洞编号,而是建立一种**“威胁建模”的敏感度**:拿到一个系统、一个组件,能快速想到它在真实攻防里会被怎么利用,又在哪些环节最容易出问题。今天是OSS存储桶,明天可能是Redis、是Kubernetes的匿名接口、是某个开源组件的反序列化点。有了这种敏感度,做安全测试、合规排查、应急响应时,都比死记硬背工具命令高效得多。
这篇文章就把我在实际项目里对OSS存储桶这类问题的理解和踩坑经验完整梳理一遍。不光是概念,还包括怎么自查、怎么修复、怎么在合规排查里落地,希望能给正在做安全测试、运维或开发的同学一点点参考。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 先把基础模型捋清楚:OSS存储桶的访问控制到底卡在哪几环
2.1 对象存储的“三层结构”:账号、Bucket、Object
很多新手第一个绕不清的点,是“OSS存储桶”和传统服务器目录的区别。传统目录是树状的,权限挂在目录节点上,子目录继承;而对象存储是扁平式命名空间,所谓“目录”只是一种Key前缀的模拟,比如/images/2024/photo.jpg里的images/2024/并不是真实目录,它只是对象Key的一部分。权限控制也是围绕三个层级展开的:
- 账号层(Account):云账号的AccessKey/SecretKey,拥有最高控制权。
- Bucket层:存储桶,相当于一个全局唯一的命名空间。每个Bucket有自己的Region、访问域名、ACL和Policy。
- Object层:单个对象,也有独立的ACL,可以覆盖Bucket级别的设置。
漏洞高发区,往往就在Bucket层和Object层的权限交叉处。因为很多人以为“只要Bucket不对外开放就安全”,完全没注意到自己上传某个Object时,单独设置了public-read的ACL,或者使用了带签名参数的URL,结果链接被分享出去后一直有效,甚至永久有效。
2.2 四类关键访问入口:控制台、API、SDK、公网域名
从攻击面角度看,一个OSS存储桶暴露在外的入口主要有四类:
- 控制台:登录云控制台直接操作,攻击者通常拿不到。
- API/SDK:需要AccessKey认证,漏洞点在于AccessKey泄露或被硬编码在代码里。
- 公网Endpoint域名:形如
<bucket>.oss-cn-shanghai.aliyuncs.com的访问地址。只要权限配置不当,任何人都能通过这个域名读写对象。这也是大多数“未授权访问”类漏洞的入口。 - 自定义域名绑定:很多企业会绑定自己的域名到OSS,如果域名解析和Bucket权限同时出问题,影响范围会更隐蔽。
值得单独拎出来说的是:CDN回源也是常见入口。为了加速,很多企业把OSS挂到CDN后面,但CDN回源时需要访问源站OSS。如果回源鉴权Header配置不严,攻击者可能绕过CDN直接访问源站Bucket。
2.3 权限模型的两把钥匙:ACL与Policy
阿里云OSS的权限体系,可以简化理解为两套并行的机制:
- ACL(访问控制列表):粗粒度的预设策略。Bucket和Object都有,取值一般是
private(私有)、public-read(公共读)、public-read-write(公共读写)。它在控制台里就是一个下拉框,但就是这最简单的三选一,对应漏洞量级却是天壤之别。 - Policy:细粒度的授权策略。可以精确到“某个特定用户/某个IP段/某个Referer对哪些对象有什么操作权限”,写入方式通常是JSON。Policy写好了很灵活,写坏了就是事故现场——比如常见的
"Action": "*"、"Resource": "*"、"Principal": "*"三连,等于把大门的钥匙直接放到了脚垫下面。
我再补充一个很细节但很要命的知识点:Bucket ACL和Object ACL之间有一个“取小”的生效逻辑。并不是说Bucket是private,里面所有Object都绝对安全。如果某个Object单独被设置了公共读,那这个对象依然可以被匿名访问。我之前遇到过一起数据泄露,排查了很久,最后发现是某个上传接口把对象ACL写死了,跟Bucket权限完全无关。所以自查时千万不要只看Bucket一层。
3. 存储桶最典型的几类漏洞:不止“公共读写”那么简单
3.1 未授权访问:列表可读 + 对象可读/可写
这是OSS存储桶最经典的漏洞形态,也是我在实战里遇到频率最高的。用大白话说就是:不需要任何账号密码,通过浏览器直接访问Bucket的Endpoint,就能看到对象列表,或者直接下载对象。
测试方法非常简单:浏览器打开https://<bucket>.<region>.oss.aliyuncs.com/,如果返回一堆XML格式的<ListBucketResult>,包含了对象Key列表,那基本可以判断Bucket存在列表可读问题。有的Bucket虽然不允许列表,但如果你能猜到对象Key(比如backup/db.sql、uploads/avatar.jpg),依然可以逐个尝试下载。所以“列表不可读”不等于“对象不可读”,这点要分开验证。
触发原因通常就两个:一是建Bucket时图省事选了“公共读”;二是通过控制台“授权”时误操作,把整个Bucket授权给了*(所有用户)。云平台现在一般会在控制台给出风险提示,但提示只出现在创建或修改的瞬间,一旦被确认,很多团队就没再管过了。
3.2 存储桶枚举:规则之外的“猜谜游戏”
在实际攻防中,很多存储桶漏洞其实不是靠“直接公开”,而是靠“猜”出来的。比如某目标站点的图片链接是https://img.example.com/upload/2024/01/photo.jpg,把域名换回OSS默认Endpoint或者直接遍历路径,就可能发现附近还躺着backup.zip、logs.tar.gz这样的对象。
这种方式不算高深,但命中率却意外地高。因为很多运维的习惯是“备份文件往存储桶里扔”,扔完还不设置生命周期规则,结果逐年累积的对象就成了一个面向公网的文件仓库。
我习惯把它叫“存储桶枚举”,更准确的说法是对象Key的枚举与遍历。与之相关的还有一种情况是Bucket名称枚举:由于Bucket名称在云厂商全局唯一,攻击者可以系统地探测一些命名规律,比如company-backup、company-logs2,一旦命中了没有开启“禁止公共访问”的Bucket,数据就暴露了。
3.3 任意文件上传与覆盖:Web侧的“连锁反应”
第二类高频漏洞是结合Web应用出现的。比如很多团队用FastAdmin等后台框架做文件管理,然后把上传目录直接指向阿里云OSS。如果上传接口只校验了Content-Type,或者根本没做扩展名过滤、前端JS校验,那攻击者就能上传一个evil.html或者evil.js到自己的Bucket里。如果这个Bucket策略又允许公共读写,这个文件就等于托管在了一个稳定的云服务器上,可用于钓鱼页面的托管或者脚本分发。
还有更隐蔽的对象覆盖:某些接口允许用户在指定Key下覆盖对象,例如头像上传用/avatar/<uid>.jpg,而uid是用户输入且未校验的。攻击者把uid改成别人,就能覆盖别人的头像,更严重的甚至能覆盖配置类文件。别觉得对象存储里不会存配置,很多IoT固件、App热更新包、前端静态资源配置都直接放在存储桶里。
3.4 加密与版本控制缺失:数据安全的“兜底防线”失守
如果前面说的权限问题是“门没锁”,那加密和版本控制就是“保险柜”和“监控摄像头”没装。
- 服务端加密:如果Bucket没有开启默认加密,上传到里面的对象,无论是静态数据还是备份文件,都是以明文形式存储在云端的物理磁盘上。云厂商内部人员或底层硬件漏洞一旦出现,数据就是裸奔状态。合规检查里,这也是等保2.0中“数据保密性”一项的重要考察点。
- 版本控制:如果Bucket没有开启版本控制,一旦被恶意覆盖或删除,对象就永久丢失了,连回滚的机会都没有。由于存储桶的删除是覆盖式的,很多企业在遭受勒索攻击后才发现:想恢复,根本没有历史版本可用。
所以,就算你的权限配得再完美,我也建议把“默认加密开启+版本控制开启”作为新建Bucket的必选操作,这不是给攻击者看的,是给“事故善后”留后路的。
3.5 特殊场景:跨账户授权与STS临时凭证误用
大一点的公司会涉及多账户资源隔离。A账户的Bucket授权给B账户的某个RAM用户,本来是很正常的架构,但很多人图省事,直接把整个Bucket的Write权限授权给外部账户。这样一来,B账户如果被攻破,攻击者就能反向往A账户的Bucket里写文件,进而形成跨账户的跳跃与持久化。
还有一种场景是STS临时凭证被用在了不该用的地方。比如前端直传OSS时生成了临时上传凭证,如果Policy里限定的Prefix范围太宽,攻击者可以把文件传到任意前缀下,实现对存储桶的污染。我之前审计过一个小程序的上传功能,开发为了省事,直接把整个Bucket的oss:PutObject授权给了匿名前端,任何人都能上传任意文件。当时就意识到,这种“开发一时爽,安全火葬场”的坑,在对象存储场景里远比想象中普遍。
4. 自查与合规排查:Black Duck之外,别忘了“人肉逻辑校验”
4.1 开源组件合规与OSS存储桶的“梦幻联动”
说到热词里的“Black Duck”,很多人的第一反应是开源许可证扫描。确实,Black Duck(以及同类工具比如FOSSA、Snyk)主要干的事情是:扫描代码库里引用的开源组件,对比许可证类型与已知CVE漏洞,输出一份合规报告。重点其实在于识别和管控你依赖的那些组件。
但这里有个很有意思的联动场景:很多项目都会引入阿里云OSS SDK、腾讯云COS SDK,或者MinIO客户端库。这些SDK本身是开源组件,Black Duck等工具能够扫描出来并给出对应的许可证信息(比如Apache 2.0),这属于代码依赖层面的合规。但如果你的实际使用方式出了问题,比如把AccessKey硬编码进代码里,或者用明文方式存储了SecretKey,那即使SDK许可证合规,也照样会出现存储桶被未授权访问的线上事故。
所以我的建议是:把“开源组件合规扫描”和“云资源安全配置基线”当成两条并行的排查线。Black Duck负责告诉你有哪个组件、哪个版本、哪个许可证;而云平台自有的配置检查工具(比如阿里云的云安全中心、等保自查)负责告诉你当前Bucket权限、加密、日志等配置是否存在风险。两条线合流之后,才能算一个比较完整的排查闭环。
4.2 手工检查存储桶的“四个必测项”
就算没有自动化工具,凭一条URL也能快速判断存储桶是否健康。我给自己定的最少检查清单是四件事:
- 访问默认Endpoint根路径:看是否返回
ListBucketResult或AccessDenied。前者代表列表可读,后者代表列表被拒绝。 - 拼接几个常见对象Key:比如
/backup.sql、/config.json、/.env、/logs/app.log。如果可以访问,说明对象层存在公共读风险。 - 尝试一个PUT请求:用
curl -X PUT上传一个测试文件。如果能成功,说明公共写权限已经大开,这是最严重的情况,需要立刻处理。 - 检查HTTP与HTTPS:有些Bucket只配了HTTP的公共读,HTTPS下是拒绝访问的,这种不一致往往意味着配置被改过一半,或者有历史遗留策略。
之所以把“手工”和“工具”并列,是因为自动化扫描虽然快,但很多自定义策略是扫描器猜不透的,尤其是Referer白名单这种。
4.3 用Black Duck做依赖侧检查时要注意什么
如果你所在团队已经引入了Black Duck,那在合规排查这块有几点经验可以分享:
- 扫描范围要覆盖开发、测试、生产三个环境。只扫主仓库的话,测试环境里的OSS SDK引入、上线脚本里的依赖,都容易漏。
- 要对扫描结果设置“处置时限”。Black Duck会报出High/Critical的CVE,但真正的问题往往不是“要不要修”,而是“多久内修完”。没有时限的合规报告,最后就是一份没人看的PDF。
- 把Black Duck的SBOM(软件物料清单)导出出来,与云账号下的OSS资源做交叉比对。比如SBOM里出现了某个OSS SDK版本,而这个版本恰好有已知漏洞,那么这个SDK所连接的Bucket就应当被纳入重点人工复核名单。
简单来说,Black Duck是“帮你看清供应链有什么”,安全配置基线是“帮你看清资源本身是否裸奔”,两者不能互相替代。
5. 实操复盘:FastAdmin上传到阿里云OSS的一次完整排查
为了把前面讲的原理落到一个具体的场景里,我挑一个比较有代表性的案例:FastAdmin上传图片到阿里云OSS的配置与安全排查。FastAdmin是一款基于ThinkPHP 5/6的开源后台快速开发框架,很多中小型项目在用,它的文件上传模块支持对接阿里云OSS等云存储。
第一步,打开application/extra/upload.php,找到driver => 'oss'这类配置项。通常需要填写的字段包括:bucket(存储桶名)、accessKeyId、accessKeySecret、endpoint、domain等。这当中有两个最容易踩坑的点:
- AccessKey直接以明文写在配置文件里。虽然PHP文件在服务器上一般不会被直接下载,但如果项目泄露、代码仓库权限失控,或者服务器上有任意文件读取漏洞,这对Key就等于白送。
domain字段用了不带签名参数的资源URL。如果选择了公共读,上传后的文件URL直接公开;如果选了私有读,则还需要处理URL签名,但很多所谓的“OSS上传插件”并没有实现这个功能,导致开发只能在公共读和“上传失败”之间二选一。
第二步,模拟一次完整的上传流程,然后在OSS控制台观察对象列表。我一般会做以下动作:
- 上传一个正常的jpg文件,观察对象Key和时间戳规则。
- 尝试上传一个
avatar.html,看看是否被拦截。 - 修改Content-Type为
image/png但文件内容为HTML,再上传一次,看看服务端是否做了二次校验。 - 检查上传后的文件URL,是否可以通过
?response-content-disposition=attachment强制下载,还是允许浏览器直接渲染。
这套流程走下来,大部分FastAdmin+OSS的上传安全缺陷就会浮出水面。最常见的结果是:文件名可控、路径可控、扩展名校验不严。也就是说,攻击者可以上传HTML并链接到精确路径,形成一个存储型XSS的载体,或者直接把webshell文件上传到OSS但无法解析(因为OSS不解析脚本),转而用于钓鱼或恶意文件托管。
修复方案也不复杂,核心思路就四条:
- 在服务端对扩展名做白名单校验,而不是黑名单。
- 在上传时,强制改写对象Key,不要直接用用户输入的文件名和路径。
- 把Bucket ACL和Object ACL全部设为私有,通过CDN或云函数生成签名URL来提供访问。
- 开启OSS的防盗链(Referer黑白名单)和自定义域名HTTPS,降低被直接攻击的概率。
这套整改做完之后,再回到Black Duck侧重新扫描一次项目依赖,确认OSS SDK版本没有问题,整个闭环才算完整。
6. 常见问题与排查技巧实录
6.1 我在真实项目里遇到的“奇怪”情况
情况一:Bucket列表显示私有,但对象还能被匿名下载。
排查后发现,不是对象ACL的问题,而是网站中有一个公开分享接口,会给任意对象生成带签名参数的URL,签名有效期为10年。这种“永久签名URL”在代码里被硬编码在页面中,搜索引擎还能抓到。处理方式不是只删页面,而是要到OSS控制台把对应对象的ACL改掉,同时检查代码里是否还有类似逻辑。
情况二:日志里发现大量来自境外的下载流量,但Bucket权限显示私有。
后来发现是CDN回源配置的鉴权头泄露了。用户在夜间访问了一个过期的CDN节点,该节点拿着源站Bucket的鉴权头,反向同步了未授权的数据。问题本质是CDN回源鉴权设置不当,而不是Bucket本身放了公共读。
情况三:FastAdmin的上传功能在本地测试正常,上线后OSS始终报签名错误。
原因是服务器系统时间和真实时间偏差过大了(NTP没有同步),导致签名中的时间戳超出了允许的范围。修复NTP后就好了。这不是安全漏洞,但如果不搞清楚,很容易误判为“权限配置被篡改”而去折腾ACL。
6.2 问题排查速查表
| 现象 | 可能原因 | 处置思路 |
|---|---|---|
| 根路径能列出对象列表 | Bucket ACL为public-read或Policy允许List | 改为私有并生成新Policy,删除“允许列表”授权 |
| 特定对象能被匿名下载 | Object ACL单独设置了公共读 | 批量重置Object ACL,并检查上传代码是否写死ACL |
| 上传接口能传任意文件 | 扩展名/Content-Type校验不严 | 服务端白名单校验,强制改写对象Key |
| 访问时多个URL都能读到同一对象 | 自定义域名、默认Endpoint、CDN域名均可访问 | 关闭默认Endpoint访问,只保留CDN域名 |
| HTTPS能访问,HTTP也能访问且返回内容不同 | 存在多套策略或历史遗留配置 | 控制台检查Bucket Policy、授权策略、回源配置 |
| 删除对象后旧URL立即404,但几个月后又可以访问 | 开启了版本控制,删除只是新增删除标记 | 补一条生命周期规则,彻底清理历史版本 |
6.3 我自己一直在坚持的“存储桶卫生习惯”
关于OSS存储桶的安全运维,虽然各家云厂商都提供了安全中心之类的扫描工具,但工具始终是“事后提醒”,我更建议把检查嵌入到日常开发流程里。有四个习惯,这几年帮我在多个项目里避免了不少事故:
- 新建Bucket时强制开启版本控制和默认加密。这不是为了性能,纯粹是为了出事之后还能还原。
- 上传文件的代码里,永远不要依赖Bucket自身权限,而是显式指定Object ACL。每一条上传请求,都应该有一个明确的权限声明。
- 给OSS对象设置生命周期规则。比如日志类文件保留90天、备份类文件保留180天后转归档或删除。避免对象数量无限膨胀,也是缩小攻击面。
- 每季度做一次“匿名访问自查”。用一条没有任何凭证的裸curl命令,把Bucket的Endpoint和几个常见路径逐个打一遍。如果哪天返回的不再是
AccessDenied,就说明某个权限配置被改动过,需要马上追查。
7. 一点额外的经验:把“每天的漏洞学习”变成“每天的威胁建模”
回到最开始说的“每天看一种漏洞类型”。我自己的体会是,如果只是每天刷一个CVE编号、背一下漏洞描述,很难形成长期能力。真正有用的做法是:当天看的漏洞,一定想一个“它跟我手上的系统有哪些可能的交点”。比如今天看了OSS存储桶,你脑子里的下一步就不是“哦,原来有公共读权限这种漏洞”,而是“我们项目里有哪些上传接口?上传后对象被存到哪里?权限是公共读还是私有?如果我把上传路径改成用户可控,会发生什么?”
这种把漏洞转化成问题的思考方式,才是安全从业者真正能带给团队的价值。很多漏洞不是云厂商的锅,也不是安全团队的锅,而是“开发的时候没想过后来会发生什么”。安全这行,本质上就是替所有人把“万一”提前想一遍。希望这篇关于OSS存储桶的拆解,能帮你把这类问题理得更清晰,少踩几个坑。
