bigywater.tech
Docker · 46

no-new-privileges:setuid 程序还能提权吗

容器里一个 setuid 程序能把普通用户变成 root 吗?no-new-privileges 到底挡住了什么、又没挡住什么?这篇用一份 setuid root 的 Python 在两种设置下对比,并验证 docker exec 进去的进程。

euid=0 → 1000
setuid root 程序由 uid 1000 运行,默认 euid 是 0;加 no-new-privileges 后仍是 1000
NoNewPrivs=1
/proc/self/status 里表示进程及其后代不能通过 execve 获得新权限
root 不受影响
已经是 root 的进程有同样的 capability,只多了 NoNewPrivs=1

setuid 失效

实测 · 本机执行输出
  /pysuid 的权限:-rwsr-xr-x    1 root     root         67168 Oct 10 13:05 /py
  默认:
    setuid 的 /pysuid → uid=1000 euid=0 gid=0 ['CapEff:\t00000000a80425fb', 'NoNewPrivs:\t0']
    普通的 /pynormal → uid=1000 euid=1000 gid=0 ['CapEff:\t0000000000000000', 'NoNewPrivs:\t0']
  --security-opt no-new-privileges:
    setuid 的 /pysuid → uid=1000 euid=1000 gid=0 ['CapEff:\t0000000000000000', 'NoNewPrivs:\t1']
    普通的 /pynormal → uid=1000 euid=1000 gid=0 ['CapEff:\t0000000000000000', 'NoNewPrivs:\t1']
  --security-opt no-new-privileges=false:
    setuid 的 /pysuid → uid=1000 euid=0 gid=0 ['CapEff:\t00000000a80425fb', 'NoNewPrivs:\t0']
    普通的 /pynormal → uid=1000 euid=1000 gid=0 ['CapEff:\t0000000000000000', 'NoNewPrivs:\t0']

默认情况下,普通用户运行镜像里 setuid root 的程序,得到 euid=0 和全部 14 个 capability。加了 --security-opt no-new-privileges 后,setuid 位被忽略,euid 仍是 1000。我是自己制造了一份 setuid 的 Python;镜像里本来就有的 setuid 程序(如 su、ping)没有逐个测。

范围

实测 · 本机执行输出
  root 用户 + no-new-privileges:uid=0 euid=0 gid=0 ['CapEff:\t00000000a80425fb', 'NoNewPrivs:\t1']
  docker exec 进入 no-new-privileges 的容器,运行 /pysuid:uid=1000 euid=1000 gid=0 ['CapEff:\t0000000000000000', 'NoNewPrivs:\t1']
  docker exec --user 0 进去:uid=0 euid=0 gid=0 ['CapEff:\t00000000a80425fb', 'NoNewPrivs:\t1']
  HostConfig.SecurityOpt = [no-new-privileges]

这个标志不降低已经是 root 的进程的权限;docker exec 进入这个容器的进程同样带着 NoNewPrivs=1。它防止的是「通过 execve 获得比父进程更多的权限」,所以常和 --cap-drop ALL 一起用(见 c40)。

分步演示

no-new-privileges
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Docker Desktop(Engine 29.8.2,容器跑在 Docker Desktop 自带的 Linux 虚拟机里,内核与宿主机的 macOS 不同);镜像为 linux/arm64;需要看虚拟机内部时用特权容器 + nsenter;计时用 time / date +%s%N,数字每次运行会有小幅抖动。setuid 程序是构建时把 python 解释器复制成 /pysuid 并 chmod 4755;以 uid 1000 运行并打印 os.getuid / os.geteuid 与 /proc/self/status 的 NoNewPrivs、CapEff;每种配置运行一次;实验后已删除容器和镜像
    • 没有验证:真实镜像里 su / sudo / ping 的行为、文件 capability(setcap)是否同样被忽略、与 AppArmor / SELinux 的交互、Kubernetes 的 allowPrivilegeEscalation 对应关系
    京ICP备2026067502号-1京公网安备11010502063487号