最小权限原则:安全的第一道闸
三个一眼就该报警的现象:
- 容器默认以 root 跑,挂载着宿主机目录,还带着
CAP_SYS_ADMIN; - 一个"只负责查订单"的服务,它的数据库账号有
ALL PRIVILEGES ON *.*; - 运维的
sudoers里写着ops ALL=(ALL) ALL——看起来是配置了权限系统,实际上等于没有。
它们背后是安全领域里少数"不用花钱、不用加机器、不用改架构"就能大幅降低损失的原则:最小权限(Principle of Least Privilege)。它最值钱的地方在于——它防的不只是攻击者,更是任何单一环节的错误。
一、它从哪来
1975 年,Jerome Saltzer 与 Michael Schroeder 在 Proceedings of the IEEE 上发表 《The Protection of Information in Computer Systems》——安全工程史上被引用最多的论文之一。论文提出了 8 条保护机制设计原则,第四条就是 Least privilege,大意是:
每个程序和每个用户,都应当使用完成工作所必需的最小权限集合来操作。
这句 50 年前的话,今天的形态只是换了皮肤:
- POSIX capabilities(Linux 2.2 起):把 root 的"全能"切成一项项能力(
CAP_NET_BIND_SERVICE、CAP_SYS_TIME…),可以让进程只获准绑定低位端口。 - SELinux / AppArmor:从"按用户授权"升级到"按程序和标签授权"(强制访问控制 MAC)。
- RBAC / ABAC / ReBAC:企业级权限模型(AWS IAM、Kubernetes RBAC、各类关系式授权)。
- OAuth scope:把令牌的权限收窄到"只能读某人的公开资料"这个级别。
- 零信任(Zero Trust):2010 年 Forrester 的 John Kindervag 提出 "never trust, always verify"——授权从"一次性静态配置"变成"每次请求都验证、尽量短期"。
- Kubernetes 时代:ServiceAccount + RBAC +
automountServiceAccountToken: false,容器侧--cap-drop=ALL、readOnlyRootFilesystem: true。
一句话:它是最早被写进论文的安全原则之一,之后几十年的工作,基本都是在让"最小"这件事更容易做到。
二、为什么需要它
因为它建立在一个不那么舒服的假设上:每一层都会失守。
代码有 bug、依赖有 CVE、配置会写错、人会手滑、内部也有威胁。既然"永不被攻破"无法承诺,那就退一步:让"被攻破一次的后果"被权限边界钳住。 这就是影响半径(blast radius)。
三个具体机制:
- 限制爆炸半径。同一个漏洞,在"只读单表"的账号下是数据泄露;在
ALL PRIVILEGES的账号下是整库被删、结构被改、顺手读走其他业务的库。漏洞是一样的,损失差三个数量级。 - 权限是横向移动的燃料。攻击者拿到一台低权机器后,第一件事是找配置文件里的凭证、找宽权限的邻接服务。权限越集中,一条攻击链就能吃下整个环境;权限分散,每一步都要重新找钥匙。
- 它让审计成为可能。权限收紧之后,"谁有权做什么"才是一份可枚举的清单;否则所有操作都"合法",日志里根本挑不出异常——没有边界的系统,审计等于零。
最容易被忽略的一点:权限边界防的主要不是攻击者,而是错误。运维手滑删错库、脚本里把测试环境变量连上了生产、一个迁移命令少写了 WHERE——这些都不是攻击,但后果和攻击一样。最小权限是唯一能替这些"非恶意错误"兜底的设计。
本质一句话:最小权限 = 假设每一层都会失守,于是用权限边界把"失守的代价"钳死;它不信任的是"单点",不是人。
三、两张图看懂
先看同一次漏洞,在不同权限下的分叉:
flowchart LR
R["一次漏洞被利用"] --> P1["权限:ALL PRIVILEGES<br/>(服务直连 DBA 账号)"]
R --> P2["权限:只读 orders 表"]
P1 --> D1["影响半径:整个实例<br/>可改结构 / 可删库 / 可读其他业务库"]
P2 --> D2["影响半径:一张表的部分行<br/>只读 · 越权即异常 · 可快速回滚"]
再看最小权限的闭环——授权只是第一步,不回收的权限迟早会"爬"回来:
flowchart LR
A["① 按需提出权限需求<br/>说清动作 + 资源"] --> B["② 授予最小集合<br/>尽量带时效"]
B --> C["③ 使用并留审计日志"]
C --> D["④ 定期复审 recertify"]
D --> E["⑤ 回收 / 降权<br/>人走了、项目结束了"]
E -->|"权限会爬,要主动清"| A
四、它有什么用
1. 应用层的权限边界:一个真实可跑的越界实验
下面的输出是本机实跑(脚本 .workbuddy/least_privilege_demo.py):一个服务只被允许读 public/ 目录,同级放着它本不该碰的文件,试三种输入。
1 | 服务根目录 = public/ 同级文件 = internal.txt |
怎么读:
../internal.txt是最经典的路径穿越,一步跨出服务目录。- 更狠的是绝对路径:在很多语言里
path.join(base, "C:/Windows/win.ini")会被"绝对路径覆盖"直接返回绝对路径,于是服务读到了系统文件——输出里那句; for 16-bit app support就是真实win.ini的开头。 - 白名单版本的做法是:先规范化真实路径(解掉
..、软链接),再断言"结果必须落在允许的根目录之内",越界一律拒绝。
这就是最小权限在代码里的样子:服务的权限边界由代码强制,而不是指望输入自觉。 落地时还有个细节要记住——白名单比较必须用规范化之后的路径,否则 realpath 之前判前缀就是自欺欺人。
2. 操作系统与进程:把 root 的权力切片
1 | # 只给"绑定 80 端口"这一项能力,而不是整个 root |
四个参数各管一件事:--user 1000:1000 避免容器内是 root;--read-only 让根文件系统不可写(写入只走挂载的卷);--cap-drop=ALL 把 root 的"全能"收回成"按需几项";no-new-privileges 阻止进程通过 setuid 之类的手段重新提权。容器隔离 ≠ 权限最小化,两者必须一起用。
3. 数据库:应用账号不该是 DBA
1 | -- 反面:一个账号吃下整个实例,还能把权限再发出去 |
三个容易被漏掉的收紧点:列级授权(只允许改 status,改不了金额)、来源 IP 收窄(10.0.3.% 而不是 %)、禁止 WITH GRANT OPTION(否则拿到账号的人可以自己发权限,权限体系当场失效)。再加一条硬规矩:应用账号永远不要有 DDL 权限——建表改表走迁移工具,用另一套凭证。
4. 云与凭证:短期、窄权、可撤销
- 用
AssumeRole拿临时凭证(几十分钟过期),而不是把长期密钥写进配置文件; - policy 里
Resource精确到具体资源的 ARN,而不是"Resource": "*"; - 代码平台的细粒度令牌只授单个仓库的读写,而不是"账号全权";
- 数据库、缓存、消息中间件尽量一服务一账号、一环境一套凭证,而不是全公司共用一套。
5. Kubernetes:默认权限比你以为的大
1 | spec: |
配套的 RBAC 原则是:只授具体资源名的 get/list,而不是拿 ClusterRole 一把梭;命名空间之间不要共享 ServiceAccount。
五、反例与边界
- 过紧的权限会把人逼出"影子权限"。拿不到正式权限,就会出现复制凭证、共享 root、私搭跳板机——结果比给权限更危险,因为没人在审计它。所以必须提供合法的快通道:临时提权(JIT access)、到期自动回收、事后复盘留痕。
- 权限会"爬"。项目结束、职责变更,权限却留在原地(privilege creep)。没有定期复审,最小权限一年之后就不最小了——这条对个人同样成立:翻翻你的 API key 列表,多半有一半已经没用了。
- 它需要治理成本。一服务一账号、一环境一密钥,意味着密钥管理、轮换、注入都得有工程支撑;否则大家会用"共用一套"来省钱,安全性反而倒退。
- 权限 ≠ 隔离。容器共享内核,权限收缩挡不住内核漏洞逃逸;最小权限必须与命名空间、沙箱、虚拟机或硬件隔离叠加使用。
- 只有权限、没有检测,等于只挡了一半。还需要审计日志(谁在什么时候用了什么权限)+ 快速吊销(必要时一分钟内让凭证彻底失效)。
- 它治的是"影响半径",不是"漏洞本身"。注入漏洞、依赖 CVE 的第一下它拦不住;它保证的是第一下之后不至于全盘皆输。
- 别把它做成"永远拒绝"。安全的目标是"业务能跑、且损失可控",不是"谁都别动"。一个让所有人都绕不过去的权限系统,一定会被绕过。
六、对比表与小结
| 维度 | 一个账号走天下 | 最小权限 |
|---|---|---|
| 一次失守的后果 | 整个实例 / 整个云账号 | 单表部分行、单目录、单资源 |
| 横向移动 | 一条链吃下全环境 | 每一步都要重新找凭证 |
| 审计能力 | 所有操作都"合法",看不出异常 | 越权即异常,日志可用 |
| 运维成本 | 低(便宜、快、不用想) | 高(账号、密钥、复审都要工程化) |
| 典型失败模式 | 手滑删库、测试连生产、CVE 被放大 | 权限过紧 → 影子权限(需要快通道兜底) |
| 层次 | 抓手 | 一句话要点 |
|---|---|---|
| 应用代码 | 目录白名单 + 真实路径校验 | 边界由代码强制,不靠输入自觉 |
| OS / 进程 | 非 root 用户、capabilities、umask | 把 root 切成按需能力 |
| 容器 | --user、--read-only、--cap-drop=ALL |
隔离 ≠ 权限最小,要一起用 |
| 数据库 | 表/列级授权 + 来源 IP + 禁 GRANT OPTION | 应用账号不给 DDL |
| 云 / 凭证 | 临时凭证、精确 ARN、细粒度令牌 | 短期、窄权、可撤销 |
| Kubernetes | RBAC 精确到资源名、SA token 不自动挂载 | 默认权限往往比你以为的大 |
| 治理 | JIT 提权 + 定期 recertify | 权限会爬,要主动回收 |
🐾 小结:最小权限最像安全带——它不阻止撞车,但它决定撞车之后你还剩多少。 落地时只需要反复问自己一个问题:"这个账号 / 进程 / 令牌,如果今天被完全攻破,最坏能坏到什么程度?" 如果答案是"全公司",那就说明这份权限还只是"方便",不是"设计"。
相关阅读
- 幂等性:让重试变得安全(越权之外的另一种"安全地重复做同一件事"):/posts/princ-idempotency/
- 背压:慢消费者的保护机制("宁可断你,也不吃光自己"是同一种自我保护思路):/posts/princ-backpressure/
- 康威定律:架构复制沟通结构("谁拥有哪份数据",本质是权限边界与组织边界的对齐):/posts/princ-conway/

