跳到内容

第 8 卷 / 第 5 章 / 第 1 课

5.1 Linux 进程权限:UID、Capabilities 与权限分离 ​

操作系统安全从一个不舒服的假设开始:应用漏洞已经让攻击者在进程内执行代码。此时真正的问题是,这个进程能读哪些文件、调用哪些内核能力、访问哪些服务,以及能否继续扩大权限。

进程携带安全上下文 ​

Linux 访问控制常见输入包括:

text
real/effective/saved UID 与 GID
supplementary groups
filesystem mode bits 与 ACL
capability sets
namespace 视图
LSM label/profile
seccomp syscall policy
resource limits 与 cgroup

root 不是一个神秘开关,而是 UID 0 加上内核和策略赋予的广泛能力。让网络服务全程拥有不受约束的宿主 root 权限,会使解析漏洞足以取得主机控制;受 user namespace、能力集合和 MAC 限制的 UID 0 仍要按实际上下文判断。

DAC 以所有者和组为基础 ​

传统 mode bits:

text
rwx for owner | rwx for group | rwx for others

目录的 read 表示列出名称,write 表示修改目录项,execute 表示穿越目录。只看文件自身权限可能误判:攻击者若对父目录同时有 write 与 execute 权限,通常可以替换或删除不可写文件;sticky bit、ACL、只读挂载或 MAC 还可能限制操作。

服务账户应一服务一身份,避免共享 app 用户。配置、可执行文件和 secret 的所有者要与运行用户区分:运行进程通常只需读取配置,不应能改自己的二进制、systemd unit 或启动脚本。

umask 决定新对象默认去掉哪些权限,但不能修复已经存在的宽权限。临时文件使用原子安全创建 API,不要先检查名称不存在再创建;共享 /tmp 中的符号链接与竞态会绕过这类检查。

Setuid 缩小代码,不能扩大方便 ​

执行带 setuid 位的可执行文件通常会把 effective UID 设为文件所有者;nosuid 挂载、no_new_privs 等条件会阻止这类提权,Linux 也不按普通 setuid 二进制的方式处理解释器脚本。对真正生效的 helper,任何输入解析、环境变量、PATH、文件描述符和动态链接问题都可能成为提权路径。优先用更窄的机制:Unix socket 代理、polkit 规则、专用 daemon 或单项 capability。

确需提权 helper 时,应功能单一、固定绝对路径、清理环境、关闭多余 fd、拒绝不可信配置,并在完成特权操作后永久降权。

Capabilities 拆分 root 权限 ​

Linux capabilities 将部分超级用户能力拆开。例如绑定低端口可能需要 CAP_NET_BIND_SERVICE,修改网络配置可能需要 CAP_NET_ADMIN。

不要把 CAP_SYS_ADMIN 当作“缺什么就加它”的通用答案,它覆盖范围巨大,被称为新的 root。容器里也一样:namespace 中的 capability 边界取决于具体操作和宿主配置,不代表内核攻击面消失。

进程能力包含 permitted、effective、inheritable、bounding、ambient 等集合。安全的进程启动流程要同时收窄这些集合:移除 bounding set 中的位限制后续 exec 获得的能力,但不会单独清除当前已经持有的 permitted/effective 能力;仅在 shell 中执行一次 setcap 而不记录部署状态,会产生不可审计漂移。

下面是 systemd 系统服务的加固片段,省略 ExecStart 等完整 unit 配置。它假设专用用户已存在,且 /var/lib/atlas-api 已创建并授予所需 DAC 权限:

ini
[Service]
User=atlas-api
Group=atlas-api
NoNewPrivileges=yes
CapabilityBoundingSet=
AmbientCapabilities=
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/atlas-api

NoNewPrivileges 阻止后续 exec 通过 setuid/file capability 等获得新权限,不会收回进程已有权限。ReadWritePaths 只提供挂载视图中的可写例外,不会创建目录或替代普通文件权限。PrivateTmp 隔离临时目录视图,ProtectSystem 也不等于网络隔离。要在支持相应功能的 systemd、内核与真实 workload 上逐项验证;错误配置可能到升级后才暴露隐藏写依赖。

权限分离把高风险步骤拆开 ​

不要让同一进程同时负责:解析任意上传、持有主密钥、写系统目录和访问管理数据库。可以拆成:

text
Internet-facing parser(无 secret、只写隔离队列)
        ↓ validated job
worker(只读输入、限资源)
        ↓ narrow result
privileged signer(固定协议、HSM key、不可读私钥)

进程间协议也要认证、限长和版本化,否则攻击者只是从“调用危险函数”变成“给高权限 daemon 发危险消息”。

Secret 不应通过所有通道扩散 ​

环境变量方便,但可能进入进程转储、诊断页面、子进程和错误报告。命令行参数还可能被进程列表看到。优先使用受权限保护的内存注入、短期凭据、文件描述符或专用 secret agent,并避免传给不需要的子进程。

Core dump、ptrace、/proc 可见性和调试接口要按生产威胁模型限制。关闭所有调试会降低事故诊断能力,因此应通过受控临时提权而不是永久开放。

验证权限而不是只读配置 ​

  • 以真实服务用户启动,确认无法读取其他服务 secret;
  • 尝试修改自身二进制、unit、配置和父目录;
  • 列出运行时 capability,而不是相信部署 YAML;
  • 检查继承的 fd、环境变量和 supplementary groups;
  • 让解析进程处理恶意大输入,确认 resource limit 生效;
  • 验证降权后不能通过 saved UID 或 helper 恢复;
  • 记录特权操作的主体、目标与结果。

下一课加入强制访问控制、syscall 过滤和内核加固,使应用账户被攻破后仍有独立边界。

参考资料 ​

Built with VitePress | Software Systems Atlas