CapabilityBoundingSet 是能力边界限制而非赋予权限,必须配合 NoNewPrivileges=true 才能阻断 execve 提权;仅设 CapabilityBoundingSet 而不启用 NoNewPrivileges 会导致 CAP_SYS_ADMIN 等高危能力仍可被继承或重获。

直接结论:CapabilityBoundingSet 不是“赋予权限”,而是“禁止继承超出列表的能力”,必须配合 NoNewPrivileges=true 才能真正封死提权路径。
CapabilityBoundingSet 的作用机制和常见误解
很多人误以为写 CapabilityBoundingSet=CAP_NET_BIND_SERVICE 就等于“给了绑定 80 端口的能力”——其实它只声明“允许保留这个能力”,前提是进程启动时本来就拥有它(比如从 root 启动后未 drop)。更关键的是,它默认不阻止进程后续通过 execve() 加载 setuid 二进制或加载内核模块重新提权。
真正起效的前提是:NoNewPrivileges=true 必须启用,否则 CapabilityBoundingSet 形同虚设。
常见错误现象:
- 服务仍能执行
mount、setcap或读取/proc/kcore -
getpcaps $(pgrep myapp)显示仍有CAP_SYS_ADMIN - 用
strace -e capset,prctl跟踪发现进程在启动后调用了prctl(PR_SET_NO_NEW_PRIVS, 1)失败
如何正确配置最小能力集并验证生效
以仅需监听 80 端口的 Web 服务为例,推荐配置片段如下:
师生共创生态系统对接指南。当用户需要对接大学政策、平台资源、投资机构、产业伙伴等外部资源支持时使用。触发词:师生共创生态、大学支持、平台赋能、投资对接、产业合作、政策支持、清华x-lab、孵化器、创业资源
[Service] User=webapp Group=webapp NoNewPrivileges=true CapabilityBoundingSet=CAP_NET_BIND_SERVICE # 显式排除高危能力(等价于 CapabilityBoundingSet=~CAP_SYS_ADMIN CAP_SYS_RAWIO ...) CapabilityBoundingSet=~CAP_SYS_ADMIN CAP_SYS_MODULE CAP_SYS_PTRACE CAP_AUDIT_WRITE CAP_IPC_OWNER AmbientCapabilities=CAP_NET_BIND_SERVICE
说明:
-
AmbientCapabilities是必须项:它让非特权用户(如User=webapp)也能在 exec 之后继续持有该能力,否则即使CapabilityBoundingSet允许,普通用户进程也无法实际绑定低端口 - 使用
~CAP_XXX语法比逐个列出更安全,避免遗漏隐式继承的能力 -
getpcaps $(pgrep webapp)输出应只含CAP_NET_BIND_SERVICE,且末尾带cap_eff标记表示当前生效
为什么不能只靠 CapabilityBoundingSet?关键依赖项
CapabilityBoundingSet 单独存在时,以下环节仍可能绕过限制:
- 若未设
User=xxx,进程仍以 root 运行,capability 只是锦上添花,不是权限主体 - 若未禁用
RestrictSUIDSGID=false(默认 false),进程仍可 exec 一个 setuid root 二进制,重新获得全部 capability - 若未配
ProtectSystem=strict和ProtectHome=true,攻击者可通过覆盖/etc/ld.so.preload或写入/home下的 shell 配置劫持环境 - 内核版本低于 4.3 时,
AmbientCapabilities不可用,此时必须用setcap预置二进制文件能力(不推荐,破坏不可变性)
验证是否真被剥离:三步检查法
改完单元文件后,务必执行:
systemctl daemon-reload && systemctl restart myapp.service-
getpcaps $(pgrep -f "myapp")—— 确认输出中只有预期能力,且无cap_bnd漏出 -
journalctl -u myapp -n 20 --no-pager—— 查看是否有Operation not permitted类报错,尤其是 bind()、chroot()、pivot_root() 等系统调用失败
最容易被忽略的一点:CapabilityBoundingSet 对子进程无效,除非子进程也显式继承 ambient 能力或本身被 setcap。如果你的服务会 fork 出 Python 子进程去调用 socket.bind(80),那子进程大概率失败——这时得换用 ambient + NoNewPrivileges 组合,或改用 iptables REDIRECT 把 80 流量转给 8080。

















