容器里你是root,出了容器你啥都不是:User Namespace让你当个"假皇帝"

49 阅读7分钟

前言:一个让人后背发凉的真实场景

上周安全扫描,我们组的Docker容器报警了。

警告:容器以root身份运行,存在容器逃逸风险

新来的实习生小王一脸懵逼:"不可能啊,我在容器里执行whoami,明明显示是root,权限也是0,怎么会不安全呢?"

我笑了笑,问他:"你在容器里执行rm -rf /,猜猜会删掉什么?"

"当然是容器里的文件啊!"他自信满满。

"那如果我告诉你,在没有User Namespace的情况下,你这行命令可能会删掉宿主机的根目录呢?"

小王愣住了。你可能也愣住了。

今天咱们就来彻底搞懂:为什么容器里的root是"假皇帝",以及User Namespace是怎么给你穿上"皇帝的新装"的。


一、先说个让你不舒服的事实

你的容器root,本质上就是宿主机的root

咱们做个实验(别在生产环境试啊!):

# 启动一个普通容器
docker run -it --rm ubuntu bash

# 在容器里查看当前用户
root@abc123:/# whoami
root

root@abc123:/# id
uid=0(root) gid=0(root) groups=0(root)

看起来很完美对吧?你在容器里是root,UID=0,GID=0。

但问题来了:Linux内核识别用户,只看UID,不看你在不在容器里。

也就是说:

  • 容器里的UID=0,就是宿主机的UID=0
  • 容器里的GID=0,就是宿主机的GID=0
  • 容器里能读取/修改的文件权限,完全取决于宿主机上这个UID的权限

这就是为什么容器逃逸漏洞这么可怕:一旦攻击者突破容器边界,他们拿到的就是宿主机的真正root权限。

类比一下:

你在自家小区(容器)里确实是个"业主"(root)。

但如果小区门禁系统(Namespace)坏了,你可以直接走进隔壁小区(宿主机)。

更可怕的是,你的"业主卡"在隔壁小区也能用——因为两个小区用的是同一套门禁系统(Linux内核的UID管理)。


二、User Namespace:给你一个"平行宇宙"

2.1 User Namespace是啥?

一句话解释: User Namespace让容器内的UID/GID和宿主机的UID/GID建立映射关系,而不是直接使用宿主机的UID/GID。

更通俗的说法: 它给容器创造了一个"平行宇宙",你在宇宙A(容器)里是皇帝(UID=0),但到了宇宙B(宿主机),你可能只是个平民(UID=1000)。

2.2 没有User Namespace vs 有User Namespace

没有User Namespace(危险⚠️):

容器内                    宿主机
UID=0 (root)  ===========>  UID=0 (root)
UID=1000      ===========>  UID=1000

有User Namespace(安全✅):

容器内                    宿主机
UID=0 (root)  =====映射====>  UID=100000
UID=1000      =====映射====>  UID=101000

看到了吗?容器里的root(UID=0)在宿主机眼里,只是个普通用户(UID=100000)。

生活类比:

没有User Namespace: 你在中国的驾驶证(UID=0)在美国也能直接开车,因为两国承认同一套驾照系统。

有User Namespace: 你在中国的驾驶证(UID=0),到了美国会被翻译成一张"临时驾照"(UID=100000),这张临时驾照在美国只能开普通车,不能开警车或卡车。


三、实战:User Namespace怎么用?

3.1 Docker开启User Namespace

修改/etc/docker/daemon.json

{
  "userns-remap": "default"
}

重启Docker:

sudo systemctl restart docker

然后会发生什么?

  1. Docker会自动创建/etc/subuid/etc/subgid文件(如果不存在)
  2. 容器内的UID 0-65536会被映射到宿主机的UID 100000-165536
  3. 容器内以root身份运行的进程,在宿主机眼里只是UID=100000的普通用户

3.2 验证User Namespace是否生效

# 启动一个容器
docker run -it --rm ubuntu bash

# 在容器里查看进程
root@abc123:/# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   4080  3440 pts/0    Ss   10:00   0:00 bash

# 退出容器,到宿主机查看同一个进程
exit
ps aux | grep bash
100000      1234  0.0  0.0   4080  3440 pts/0    Ss   10:00   0:00 bash

看到了吗?容器里显示USER=root,但宿主机里显示USER=100000

这就是User Namespace的魔法!


四、底层原理:Linux怎么实现这个"平行宇宙"?

4.1 UID/GID映射表

每个User Namespace都维护一张映射表,格式如下:

容器内UID    容器内UID数量    宿主机UID起始值
0            65536           100000

翻译成人话:

  • 容器内的UID 0-65535,对应宿主机的UID 100000-165535
  • 容器内的root(UID=0),在宿主机看来是UID=100000

4.2 系统调用怎么看User Namespace?

Linux内核提供了一组系统调用管理Namespace:

系统调用作用类比
clone()创建新Namespace时传入CLONE_NEWUSER标志给孩子盖个新房子
unshare()当前进程脱离当前Namespace搬出去单过
setns()加入一个已存在的Namespace搬到别人家住

关键点: clone()创建新进程时,如果指定了CLONE_NEWUSER,这个进程就会在一个新的User Namespace里运行,拥有独立的UID/GID映射。


五、踩坑预警:User Namespace不是万能的

5.1 不是所有文件系统都支持

场景: 你想在容器里挂载NFS共享目录

docker run -v nfs-server:/data ubuntu

问题: 很多NFS服务器不支持UID/GID映射,会导致权限错乱。

解决: 要么NFS服务器端配置支持,要么放弃User Namespace。

5.2 有些操作需要特权模式

场景: 你想在容器里修改系统时间

docker run --privileged -it ubuntu

问题: --privileged会禁用所有安全限制,包括User Namespace。

解决: 不要用--privileged,改用--cap-add SYS_TIME(如果你真需要改时间的话)。

5.3 Docker Volume的权限问题

场景: 容器内以root身份创建文件,宿主机上普通用户却无法修改

# 容器内
root@abc123:/# echo "test" > /data/file.txt

# 宿主机上
$ ls -l /data/file.txt
-rw-r--r-- 1 100000 100000 5 Jan 4 10:00 /data/file.txt

问题: 文件所有者是UID=100000,你的宿主机用户是UID=1000,改不了。

解决: 在Docker Compose里配置:

version: '3'
services:
  app:
    user: "1000:1000"  # 强制使用宿主机用户UID
    volumes:
      - ./data:/data

六、最佳实践:什么时候用User Namespace?

✅ 建议使用的场景:

  1. 多租户环境:多个用户共享一台Docker服务器
  2. 高安全要求:金融、医疗等对数据隔离要求高的行业
  3. 不可信的第三方镜像:你不确定镜像里有没有恶意代码

❌ 不建议使用的场景:

  1. 开发环境:单机开发,启用User Namespace反而增加复杂度
  2. 需要访问宿主机资源:比如挂载NFS、需要特权操作
  3. 性能敏感场景:User Namespace会有少量性能损耗

七、总结:记住这三句话

  1. 没有User Namespace,容器root=宿主机root——这是最大的安全隐患
  2. User Namespace的本质是UID映射——给容器一个独立的用户空间
  3. User Namespace不是万能的——文件系统、特权操作要小心

最后一句骚话:

容器安全就像你家的门锁——User Namespace不是万能的,但没有它,你的门就是给小偷留的缝儿。


延伸阅读

  • 搞懂了User Namespace,你可能想:那其他Namespace(PID、Network、Mount)呢?
  • 容器逃逸有哪些常见手段?怎么防御?
  • Kubernetes 1.30的User Namespace Beta版有什么新特性?

这些坑,咱们下篇再填。


参考资料: