东营码良软件开发
  • 首页
  • 产品与服务
    抖音软件小程序开发网站建设小程序开发价格网站建设费用微信商城小程序源码交付APP定制开发建站公司选择
  • 开发案例
  • 新闻资讯
    二级菜单A
  • 关于我们
  • GEO 优化
  • Ai超市
咨询客服咨询客服
企业咨询
东营码良软件开发
首页›
产品与服务›
开发案例›
新闻资讯›
二级菜单A›
关于我们›GEO 优化›Ai超市›
会员中心
首页产品与服务开发案例新闻资讯我的
Dongying Maliang Software

码良软件开发

企业软件开发·微信小程序·网站建设·GEO 优化·AI 应用落地

咨询客服

导航

  • 首页
  • 产品与服务
  • 开发案例
  • 新闻资讯
  • 关于我们
  • GEO 优化
  • Ai超市

服务

  • 软件开发定制
  • GEO 搜索优化
  • 软件资源商城
  • 会员服务
  • 在线客服

行业方案

  • 零售门店
  • 教育培训
  • 医疗健康
  • 商务服务
  • 本地生活
  • 生产制造

联系电话

1886546092715154670927

地址

山东省东营市垦利区兴隆路8-5号

扫码

码良软件开发 · 品牌入驻
品牌入驻手机端查看商城、案例与在线咨询

Copyright © 2019-2026 东营码良软件开发 Inc. 版权所有鲁ICP备2025202136号-2
开发资讯 > 资讯详情

阿里云 OSS 权限收敛实战:从「公共读写」桶到最小权限子账号

2026-11-11 21:39:41阅读 1
广告
码良软件/小程序开发

码良软件/小程序开发

全栈技术团队 + 资深工程师开发 · 按需定制 · 源码交付 · 长期维护 · 东营本地可对接

小程序定制API接口设计bug修复升级改版软件开发企业管理系统
立即咨询 18865460927备用电话 15154670927

一次「有没有防盗链」的例行检查,最后挖出了三个更严重的问题:存储桶是公共读写、应用在用一把能建桶的万能钥匙、删了素材没法回滚。这篇记录完整的收敛过程,重点在如何给应用建一个真正最小权限的 RAM 子账号,以及怎么验证它「刚好够用、多一点都不给」。

文中的桶名、域名、账号 ID 已做脱敏,代码和 JSON 可直接复用。

一、起因:防盗链检查挖出的真问题

原本只是想确认「素材有没有配防盗链」,结果用 curl 直接探测存储桶,四个结论一个比一个糟:

探测方式返回含义
无签名直连对象206 Partial Content桶是公有读,任何人都能下
带外站 Referer 直连206没配 Referer 白名单
匿名 PUT200,内容能读回公有写
匿名 DELETE204,对象真被删了可以被匿名删除
匿名列举桶403 AccessDenied这一项反而是对的

公有读还能理解为「配置图省事」,公有写就是事故级别的:任何人可以覆盖你的模板素材、往你的域名下塞任意文件、或者直接 DELETE 掉线上资源。

更麻烦的是叠加上层设计:素材清单接口是公开的,返回里带了每个图层的对象键(assets/tpl-xxx/v3/avatar/<sha256>.png)。桶公有读 + 键外泄 = 付费素材可被直接批量下载,绕过会员和单购。

二、先止血:桶层面的三件事

动手改权限之前,先把桶本身的风险关掉,顺序是先止血再收敛,避免中途出现「改了一半,两边都不安全」。

1)ACL 改为私有读

const OSS = require("ali-oss");
const client = new OSS({ region: "oss-cn-beijing", accessKeyId, accessKeySecret, bucket });

// 改之前先留档,便于回滚
const before = await client.getBucketACL(bucket);
console.log("before:", before.acl);   // public-read-write

await client.putBucketACL(bucket, "private");
// 回滚:await client.putBucketACL(bucket, "public-read-write");

改私有不影响业务的前提是:读路径本来就不依赖匿名访问。我们的素材要么走服务端代理(带签名),要么由服务端现场签 OSS URL,客户端从不持密钥。如果你的项目里存在硬编码的公网直链,先把它改掉再动 ACL。

2)开版本控制

await client.putBucketVersioning(bucket, "Enabled");

私有化解决「谁能读」,解决不了「删了怎么恢复」。开了版本控制后,覆盖和删除都会留历史版本:删除只产生 delete marker,业务侧 get 照旧 404、list 照旧看不到,但旧内容还能按 versionId 捞回来。

3)开访问日志

日志要写到一个独立桶(同地域、私有读),别写回源桶,否则日志自己也会被记录、越滚越多:

const logBucket = "your-bucket-logs";
await client.putBucket(logBucket);
await client.putBucketLogging(bucket, logBucket, "access-logs/"); // 视 SDK 版本而定
注意:ali-oss 的 putBucketLogging(name, prefix) 把 name 同时当作源桶和目标桶,只能写同一个桶。要写独立桶得自己构造 ?logging 的请求体,这一步用一次性脚本做,不要放进业务代码。

三、为什么必须换成 RAM 子账号

桶锁好了,剩下的风险全在钥匙上。排查时发现:应用用的 AccessKey 能成功调用 CreateBucket。这意味着它不是最小权限凭证,一旦泄露,攻击面是整个账号的 OSS 资源,而不只是自己的几个桶。

更隐蔽的是信任链:桌面端自动更新的校验依据(版本清单 + 安装包)都在同一台服务器上,服务器被拿下就等于对所有用户下发任意程序。这时候一把「能改配置」的云凭证就是放大器。

正确做法:给应用单独建一个 RAM 用户,只授数据面权限,不授任何管理面权限。

四、创建子账号的完整流程

4.1 别用「快速开始」

RAM 控制台的「快速开始」提供了几个场景模板,第一个就是「用于程序访问的超级用户」,绑定 PowerUserAccess。

这是个陷阱:PowerUserAccess 除了账号管理、计费外,几乎什么都能干——建桶、删桶、改 ACL、开子账号。用它等于没收敛,只是把「主账号 AK」换成了「长得像子账号的主账号 AK」。

手动创建,不选任何场景模板。

4.2 创建用户

「身份管理 → 用户 → 创建用户」:

字段填什么原因
登录名称app-oss创建后不可修改
显示名称业务名-OSS便于识别
访问方式只勾「使用永久 AccessKey 访问」程序调用够用
控制台访问密码不勾少一个可爆破的入口
MFA不勾程序访问用不上

页面底部有个「我确认必须创建 AccessKey」的勾选框,必须勾上,否则只会创建出用户、不会生成密钥,还得再进详情页补一步。

创建结果页会显示 AccessKey ID 和 AccessKey Secret。Secret 只显示这一次,关掉页面后没有任何方式再查回来,只能禁用重建。

4.3 创建自定义策略(关键一步)

不要用系统策略(AliyunOSSFullAccess 也是全权限)。走「权限管理 → 权限策略 → 创建权限策略 → 脚本编辑」。

策略的内容不能拍脑袋写,要先摸清应用实际调用了哪些 OSS 操作。我的做法是直接统计 SDK 调用面:

grep -o "client\.\(get\|put\|delete\|list\|head\|multipartUpload\|signatureUrl\)[A-Za-z]*" \
  server/src/ossMaterials.ts | sed 's/.*client\.//' | sort | uniq -c

得到的结果是:put、get、head、delete、deleteMulti、list、multipartUpload、signatureUrl。对应到 RAM action 就是:

应用调用需要的 action
get / headoss:GetObject
putoss:PutObject
delete / deleteMultioss:DeleteObject
listoss:ListObjects(桶级资源)
multipartUploadInitiateMultipartUpload / UploadPart / CompleteMultipartUpload / AbortMultipartUpload / ListParts
signatureUrl无需额外权限(签名在本地算,实际访问仍走 GetObject)

最终策略:

{
  "Version": "1",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "oss:GetObject",
        "oss:PutObject",
        "oss:DeleteObject",
        "oss:InitiateMultipartUpload",
        "oss:UploadPart",
        "oss:CompleteMultipartUpload",
        "oss:AbortMultipartUpload",
        "oss:ListParts"
      ],
      "Resource": [
        "acs:oss:*:*:your-bucket/*",
        "acs:oss:*:*:your-font-bucket/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": ["oss:ListObjects"],
      "Resource": [
        "acs:oss:*:*:your-bucket",
        "acs:oss:*:*:your-font-bucket"
      ]
    }
  ]
}

三个最容易漏的点:

  1. oss:ListObjects 是桶级资源,不是对象级。上面的 Resource 写 bucket/* 只覆盖对象操作,列举必须单独一条写 acs:oss:*:*:bucket。漏了的表现不是报错,而是「素材列表莫名其妙是空的」。
  2. 分片上传要五个 action。大于分片阈值的文件会走 InitiateMultipartUpload → UploadPart → CompleteMultipartUpload,只给 PutObject 会在大文件上失败。
  3. 如果项目里短信和 OSS 复用同一组凭证,策略里必须加短信权限,否则验证码会在主流程之外静默失败:
{
  "Effect": "Allow",
  "Action": ["dysms:SendSms", "dysms:SendBatchSms"],
  "Resource": ["*"]
}

这一条是我实际踩到的:代码里短信客户端直接复用了 OSS_ACCESS_KEY_ID,换 Key 时如果只想着 OSS,注册和找回密码会全线挂掉,而且日志里只有一行「发送失败」。

4.4 绑定授权

两条路径,效果等价:

  • 用户页正向:用户详情 → 「权限管理」→ 新增授权 → 选自定义策略;
  • 策略页反向:策略详情 → 「授权管理」→ 新增授权 → 选用户。

这一步有个高频错误:策略列表默认展示「所有策略类型」,第一行就是标着高危的 AdministratorAccess。手快点错,前面全白做。把筛选切成「自定义策略」再搜名字,最终只应看到一条授权。

4.5 创建 AccessKey

用户详情 → 「认证管理」→ 创建 AccessKey。Secret 只显示一次,直接写进服务器 .env,不要经过聊天工具、截图或文档。

五、上线前先验证:正向要通,反向必须拒

顺序很重要:先用新凭证做权限探测,确认无误再改线上配置。 直接改 .env 的风险是「策略没配好 → 全站 403 → 你还得怀疑是不是代码问题」。

探测脚本同时包含正向和反向用例:

const OSS = require("ali-oss");
const bucket = "your-bucket";
const client = new OSS({ region, accessKeyId: process.env.PROBE_AK, accessKeySecret: process.env.PROBE_SK, bucket });

async function probe(label, run) {
  try { console.log(label, "= ok", await run()); }
  catch (e) { console.log(label, "= FAIL", e.code, e.message.slice(0, 60)); }
}

await probe("list", () => client.list({ prefix: "_probe/", "max-keys": 5 }));
await probe("get", () => client.get("字体文件/known.ttf"));
await probe("put", () => client.put("_probe/a.txt", Buffer.from("ok")));
await probe("delete", () => client.delete("_probe/a.txt"));

// 反向:这些必须被拒
await probe("get_bucket_acl", () => client.getBucketACL(bucket));
await probe("put_bucket_acl", () => client.putBucketACL(bucket, "private"));
await probe("create_bucket", () => client.putBucket("should-fail-probe"));

期望输出(前四项 ok,后三项 FAIL):

list = ok
get = ok
put = ok
delete = ok
get_bucket_acl = FAIL AccessDenied
put_bucket_acl = FAIL AccessDenied
create_bucket = FAIL AccessDenied

如果全部 FAIL,不是策略写错,而是策略根本没绑上——第一次探测时我就遇到这个,报错是 The bucket you access does not belong to you.,典型的「RAM 用户零策略」。策略绑了但缺 action 时,通常是一部分 ok 一部分 FAIL,不会全军覆没。

短信权限怎么验证又不真发短信:用一个非法号码调用发送接口,看返回的错误码。

const result = await client.sendSms(new SendSmsRequest({ phoneNumbers: "10000000000", ... }));
// 有权限:code = isv.MOBILE_NUMBER_ILLEGAL(手机号码格式错误)
// 没权限:抛 Forbidden.RAM / Forbidden.AccessKey

isv.* 说明鉴权通过、只是参数不合法;Forbidden.* 才是权限问题。这样既验证了权限,又不会真的下发短信。

六、切换与回滚

cd /path/to/project
cp .env .env.bak-$(date +%Y%m%d-%H%M%S)

# 用脚本替换,避免密钥出现在命令历史里
NEWID=xxx NEWSEC=yyy python3 - <<'PY'
import os, re
p = ".env"
s = open(p).read()
s = re.sub(r"^OSS_ACCESS_KEY_ID=.*$", "OSS_ACCESS_KEY_ID=" + os.environ["NEWID"], s, flags=re.M)
s = re.sub(r"^OSS_ACCESS_KEY_SECRET=.*$", "OSS_ACCESS_KEY_SECRET=" + os.environ["NEWSEC"], s, flags=re.M)
open(p, "w").write(s)
PY
chmod 600 .env
pm2 restart your-api --update-env

怎么证明「应用真的在用新 Key」

这一步最容易被糊弄:接口返回 200 不代表换了 Key,因为旧 Key 也通。

正确做法是找一个能反查出凭证身份的出口。如果应用会给客户端签发 OSS 签名 URL,那么 URL 里必然带 OSSAccessKeyId 参数:

curl -s https://your-domain/api/materials/catalog | python -c "
import sys, json, urllib.parse
d = json.load(sys.stdin)
def walk(n):
    for a in n.get('assets', []):
        if a.get('url'): return a
    for c in n.get('children', []):
        r = walk(c)
        if r: return r
url = walk(d['root'])['url']
print(urllib.parse.parse_qs(urllib.parse.urlparse(url).query)['OSSAccessKeyId'][0])
"

输出应该是新的 AccessKey ID。这一招比看日志可靠得多,也不需要打开密钥本身。

回滚:.env.bak-<时间戳> 拷回去,重启即可。所以旧 Key 不要立刻禁用,至少留 24 小时。

七、踩坑清单

现象原因处理
换 Key 后全站 403策略没绑,或绑了错策略回滚 .env,先补授权
素材列表空白但不报错缺 oss:ListObjects单独加桶级资源授权
大文件上传失败、小图正常缺分片上传 action补齐 5 个 multipart action
验证码发不出去策略漏了 dysms:SendSms短信复用 OSS 凭证时必查
401/403 改了不生效忘了 --update-env不能只 pm2 restart
Secret 找不回来关掉了创建结果页只能禁用旧 Key 重建
授权后出现 AdministratorAccess在「所有策略类型」里误勾解绑,只留自定义策略

八、收尾:旧 Key 的退役节奏

  1. 新 Key 验证通过、线上稳定运行 24 小时;
  2. 控制台把旧 AccessKey 设为禁用(先别删除,留作最后回滚);
  3. 再观察一周,确认没有遗漏的调用方(定时脚本、监控、第三方集成最容易漏),才删除。

换 Key 时还有一个容易忽略的盲区:同一个账号下可能有多处使用这把 Key。包括 CI、备份脚本、日志采集、以及短信这类「看起来跟 OSS 无关」的服务。换之前用 grep -r "OSS_ACCESS_KEY_ID" 全盘扫一遍,比事后排障便宜得多。

九、最后:为什么应用凭证不该有管理权限

这次收敛的边界是:

  • 允许:对象的读、写、删、列举、分片上传;
  • 拒绝:桶 ACL、桶创建删除、日志、版本控制、生命周期。

也就是说,应用凭证即使泄露,攻击者也只能在已有的桶里读写数据,改不了安全配置、关不掉日志、建不了新桶扩散。

代价是:以后做桶级运维(开版本控制、调生命周期、改 ACL)必须用主账号走控制台,不能用应用凭证顺手做。这个「不方便」正是隔离的意义——让两套权限无法互相提权。

一个可复用的判断标准:如果你的应用凭证能修改「谁能访问」这个问题的答案,那它的权限就过大了。


附:策略文件(可直接复制)

完整版见文中的 JSON。使用时替换:

  • your-bucket / your-font-bucket → 你的实际桶名;
  • 如果不需要短信,删掉 dysms 那一段;
  • 如果项目跨地域或用了多个账号,acs:oss:*:*: 里的 * 可以收窄成具体 region。

附:验收清单

[ ] RAM 用户只开 API 访问,无控制台登录
[ ] 只绑定自定义策略,无 PowerUserAccess / AdministratorAccess
[ ] 正向:list / get / put / delete 全部 ok
[ ] 反向:GetBucketAcl / PutBucketAcl / CreateBucket 全部 AccessDenied
[ ] 短信权限用非法号码探测,返回 isv.* 而非 Forbidden.*
[ ] .env 已备份,权限 600
[ ] 重启后签名 URL 里的 OSSAccessKeyId 是新 ID
[ ] 旧 Key 保持可用 24 小时后再禁用
DISCUSSION

评论

登录后参与
请使用网站前台用户账号登录后发表评论。

正在加载评论...


返回列表返回资讯列表下一篇公众号排版助手上线|Markdown一键排版,直推草稿箱

作者信息

超级管理
超级管理
等级作者
156文章
0点赞
0收藏
查看作者其他文章
广告🧩

软件定制 · 企业级项目 8 折起

10 年经验团队,按需定制 CRM、ERP、小程序,合同保障。

DDevStudio查看案例 →

文章展示

公众号排版助手上线|Markdown一键排版,直推草稿箱 - 东营码良软件开发资讯公众号排版助手上线|Markdown一键排版,直推草稿箱小程序UI设计有哪些风格?8种主流界面风格与5类导航图标设计指南 - 东营码良软件开发资讯小程序UI设计有哪些风格?8种主流界面风格与5类导航图标设计指南项目逆向研发|科研 - 东营码良软件开发资讯项目逆向研发|科研破甲软件下载 - 东营码良软件开发资讯破甲软件下载一个小项目养活一家人|纯干货无偿分享 - 东营码良软件开发资讯一个小项目养活一家人|纯干货无偿分享

热门排行

如何开通微信虚拟支付功能? - 东营码良软件开发资讯1如何开通微信虚拟支付功能?300 阅读东营网站开发哪家专业?选东营码良软件开发定制建站 - 东营码良软件开发资讯2东营网站开发哪家专业?选东营码良软件开发定制建站199 阅读如何在微信小程序中接入虚拟支付功能? - 东营码良软件开发资讯3如何在微信小程序中接入虚拟支付功能?174 阅读虚拟商品类小程序全类型汇总(2026微信合规版) - 东营码良软件开发资讯4虚拟商品类小程序全类型汇总(2026微信合规版)134 阅读一个小项目养活一家人|纯干货无偿分享 - 东营码良软件开发资讯5一个小项目养活一家人|纯干货无偿分享131 阅读团播动态灯牌MOV特效贴片定制 - 东营码良软件开发资讯6团播动态灯牌MOV特效贴片定制73 阅读
CONTENT GUIDE

文章目录

17 项
  1. 01一、起因:防盗链检查挖出的真问题
  2. 02二、先止血:桶层面的三件事
  3. 03三、为什么必须换成 RAM 子账号
  4. 04四、创建子账号的完整流程
  5. 054.1 别用「快速开始」
  6. 064.2 创建用户
  7. 074.3 创建自定义策略(关键一步)
  8. 084.4 绑定授权
  9. 094.5 创建 AccessKey
  10. 10五、上线前先验证:正向要通,反向必须拒
  11. 11六、切换与回滚
  12. 12怎么证明「应用真的在用新 Key」
  13. 13七、踩坑清单
  14. 14八、收尾:旧 Key 的退役节奏
  15. 15九、最后:为什么应用凭证不该有管理权限
  16. 16附:策略文件(可直接复制)
  17. 17附:验收清单