用户
搜索
  • TA的每日心情
    开心
    2018-12-25 11:26
  • 签到天数: 1 天

    连续签到: 1 天

    [LV.1]初来乍到

    官方账号

    Rank: 7Rank: 7Rank: 7

    87

    主题

    95

    帖子

    1087

    魔法币
    收听
    0
    粉丝
    3
    注册时间
    2018-6-12

    i春秋认证

    发表于 2021-8-18 15:50:32 02304
    本帖最后由 云鼎实验室 于 2021-8-18 18:40 编辑

    背景

    国外安全研究员champtar[1]在日常使用中发现Kubernetes tmpfs挂载存在逃逸现象,研究后发现runC存在条件竞争漏洞,可以导致挂载逃逸[2]

    关于条件竞争TOCTOU和一些linux文件基础知识可见这篇文章《初探文件路径条件竞争 - TOCTOU&CVE-2019-18276》[3]

    CVE-2021-30465在Redteam的研究者视角中比较鸡肋,因为需要K8S批量创建POD的权限。但在产品安全的视角恰恰相反,针对Caas(Container as a service)类产品,用户/租户拥有批量创建POD权限,利用挂载逃逸可打破租户间隔离,同时读取Host层面某些敏感数据,危害性极大。

    RunC简介
    为了让容器生态更加开放,Linux基金会发起OCI(Open Container Initiative),目标是标准化容器格式和运行时[4],其中一个重要产物就是CRI(Container Runtime Interface),抽象了容器运行时接口,使得上层调控容器更加便捷。containerd和runC都是其中代表产物,从dockerd中再剥离出containerd[5],向上提供rpc接口,再通过containerd去管理runC。containerd在初期也是直接对runC进行管理,但为了解决containerd进行升级等操作时会造成不可用的问题,containerd再拆出containerd-shim,独立对接runC。containerd从Runtime、Distribution、Bundle维度提供容器全生命周期的管理能力[6],runC专注于Runtime。
    漏洞分析.png

    容器设备挂载相关基础知识
    Namespace
    Namespace是linux控制系统资源的抽象层,将不同的进程放置入不同的Namespace将获得不同的资源视角,该项技术是容器实现的基础[7]
    linux提供8种不同的Namespace以提供不同维度的隔离能力,分别是:

    1. Cgroup
    2. IPC
    3. Network
    4. Mount
    5. PID
    6. Time
    7. User
    8. UTS

    其中,Cgroup和Mount Namespace是最常接触的,在容器挂载相关能力均通过Mount Namespace进行实现。Namespace的使用主要通过clone和unshare 两个方法实现,其中clone创建新进程时,标志位为CLONE_NEW*将会创建新的Namespace并将子进程放入该Namespace[8],unshare方法将调用进程放入不同的Namespace中[9]

    Mount Namespace
    Linux中有一个很核心的思想,那就是一切皆文件。在该思想下,Linux通过挂载对不同设备中的文件进行管理。在Linux中,每一个空目录/文件都可以成为挂载点并设置相应的属性。在Mount Namespace下,处在当前Namespace中的进程只对当前Namespace中的挂载点可见,通过
    /proc/[pid]/mounts 、/proc/[pid]/mountinfo和/proc/[pid]/mountstats
    提供不同维度的数据。每个task(在Linux中,不论是进程还是线程,在内核的视角都是一个task)都会指向一个Namesapce(存放在task→nsproxy中)[10]

    struct nsproxy {
      atomic_t count;
      struct uts_namespace *uts_ns;
      struct ipc_namespace *ipc_ns;
      struct mnt_namespace *mnt_ns;
      struct pid_namespace *pid_ns;
      struct net        *net_ns;
    };


    但Mount Namespace的引入也带来了新的问题,由于Mount Namespace中的隔离性,当用户需要挂载一个新的磁盘使所有Namespace可见时,就需要在所有的Namespace中都进行一次挂载,很麻烦,于是2.6.15中引入了共享子树(Shared Subrees)。通过在不同挂载点设置不同的属性,使挂载事件在不同的维度(peer group[11])进行传播。目前支持以下四种传播类型,其中MS_SHARED和MS_SLAVE比较常见。

    1. MS_SHARED
    2. MS_PRIVATE
    3. MS_SLAVE
    4. MS_UNBINDABLE

    在MS_SLAVE传播属性的挂载点下,父挂载点(Master)的传播事件可以接收,但子挂载点下(Slave)的挂载事件不再传播,容器的Rootfs挂载即为该种类型,也就是说在容器中挂载的挂载动作是不影响宿主机的,保证了容器隔离。
    Untitled 2.png

    漏洞分析

    RunC漏洞挂载逻辑分析


    checkout到修复commit(0ca91f44f1664da834bc61115a849b56d22f595f)[12]的上一个版本commitc(01a56034f5ab0c1aa314377a499fe60a9c26b36)。

    整体流程如下
    Untitled 3.png


    RunC通过命令runc create + runc start 或runc run启动一个容器,runc create主要分为两部分,一部分是准备容器进程的启动参数,与真正实施容器runc init进程进行交互,保证容器初始化顺利进行;另外一部分是执行克隆出的runc init进程,加入各种namespace并初始化容器进程的执行环境。本文以第二部分为切入点进行分析,从
    libcontainer/standard_init_linux.go的linuxStandardInit.Init()开始,在其中调用prepareRootfs,准备初始化rootfs并进行挂载。

    Untitled 4.png

    在prepareRootfs 中,调用 prepareRoot 设置初始挂载点,并设置挂载标志位为 unix.MS_SLAVE | unix.MS_REC ,其后使用 mountToRootfs 对container.json中配置的挂载进行操作。

    Untitled 5.png

    prepareRoot 中设置容器根目录挂载标志位为unix.MS_SLAVE | unix.MS_REC ,容器在初始的时候会通过镜像中的容器标准包(bundle)挂载根文件系统(BaseFS),在这里runC默认将挂载点(Propagation Type)设置为slave。由于当前已经处于容器的mount namespace中,所以当前\  为容器根路径。rootfsParentMountPrivate 函数确保上一层的挂载点是PRIVATE ,应该是出于防止逃逸的考虑。

    Untitled 6.png
    在mountToRootfs 中,针对不同的设备类型存在不同的处理逻辑。

    Untitled 7.png
    在tmpfs的处理逻辑中,configs.EXT_COPYUP默认为1。

    Untitled 8.png

    首先准备/tmp 目录,在prepareTmp 函数中将这个挂载点设置为 MS_PRIVATE ,再创建runctmpdir 路径,将目标路径复制到 tmpDir 中,最后将dest 路径挂载到tmpDir 中,且Propagation Type设置为MS_MOVE 。对于MS_MOVE ,官方说明[13]如下:

    If mountflags contains the flag MS_MOVE (available since Linux 2.4.18), then move a subtree: source specifies an existing mount point and target specifies the new location to which that mount point is to be relocated. The move is atomic: at no point is the subtree unmounted.

    当此时的dest 为一个symlink时,subtree将覆盖已存在挂载点。所以此处存在TOCTOU(Time-of-check to time-of-use),在SecureJoin 函数执行时,dest 为正常路径,当挂在发生时,dest 为symlink,导致逃逸发生。

    结论

    RunC为了防止在路径组合中的路径穿越漏洞,引入了filepath-securejoin[14]作为符号链接过滤函数,但r在挂载时并未校验挂载的实际目的路径,从而导致存在TOCTOU条件竞争漏洞。

    从securejoin的Readme中也可看出这一点。
    Untitled 9.png

    之所以能够成功逃逸的另一原因在于tmpfs[15]中为了实现copy-up功能[16]使用MS_MOVE[17]作为挂载标志,根据runC作者的描述只有在tmpfs情况才能够逃逸[18]

    补丁分析
    在补丁中,可以看出在tmpfs的挂载逻辑中,增加了doTmpfsCopyUp 函数。
    Untitled 10.png

    在其中使用WithProcfd 函数防止TOCTOU漏洞的发生,所有的 securejoin.SecureJoin 移入WithProcfd进行统一处理。

    Untitled 11.png
    WithProcfd 中使用/proc/self/fd/ ,确保打开的文件是securejoin.SecureJoin 后的文件。

    Untitled 12.png
    POC分析
    漏洞作者给出的POC中给出了一个很精妙的构造,利用了这个看似很难利用的条件竞争漏洞。

    首先,创建两个公共tmpfs的挂载,名称为test1、test2,在容器A中,将test1挂载到/test1路径,test2挂载到/test2路径,同时将/test2/test2指向/ 。在容器B中,将test1挂载到/test1路径,test2挂载到/test1/mntx路径和/test1/zzz路径。

    在容器A启动后,将/test1/mnt-tmpx指向rootfs路径,且交换mnt和mnt-tmpx,且rootfs/test2/test2指向/(K8S中,同一个pod下的rootfs在一个路径,形如/var/lib/kubelet/pods/$MY_POD_UID/volumes/kubernetes.io~empty-dir )。

    所以当条件竞争挂载的时候,即容器B启动时,挂载test2,
    mount('/','rootfs/test1/zzz')  ,同时MS_MOVE 标志位将原有该挂载点的subtree移至新挂载点下,造成逃逸发生。

    总结

    Linux在引入symlink的时候并不存在安全风险,但随着时代的变迁(容器的引入),symlink确实在一定程度上确实容易造成容器逃逸的发生。[19]Linux在尝试在不同的角度去解决这个问题,但目前还没有很完全的能够解决此风险。这里不禁让人想引用tk的一句话:

    安全意识要有时代背景。

    作者认为伴随容器场景愈发复杂,安全研究的逐渐深入,非Linux内核漏洞导致的容器逃逸长期来看还会有一个增长的趋势。

    参考资料

    [1]  champtar
    https://github.com/champtar
    [2]runc mount destinations can be swapped via symlink-exchange to cause mounts outside the rootfs (CVE-2021-30465) https://blog.champtar.fr/runc-symlink-CVE-2021-30465/
    [3] 《初探文件路径条件竞争 - TOCTOU&CVE-2019-18276》
    http://whip1ash.cn/2021/06/16/toctou/
    [4] About the Open Container Initiative
    https://opencontainers.org/about/overview/
    [5] What is the relationship between containerd, OCI and runc?
    http://www.caict.ac.cn/kxyj/qwfb/ztbg/202010/t20201021_360375.htm
    [6] Containerd Architecturehttps://github.com/docker-archiv ... ign/architecture.md
    [7] namespaces(7) — Linux manual pagehttps://man7.org/linux/man-pages/man7/namespaces.7.html
    [8] clone(2) — Linux manual pagehttps://man7.org/linux/man-pages/man2/clone.2.html
    [9] unshare(2) — Linux manual pagehttps://man7.org/linux/man-pages/man2/unshare.2.html
    [10] Linux Namespace分析——mnt namespace的实现与应用
    https://hustcat.github.io/namespace-implement-1/
    [11]Mount namespaces and shared subtrees
    https://lwn.net/Articles/689856/
    [12] 0ca91f44f1664da834bc61115a849b56d22f595f
    https://github.com/opencontainer ... 1115a849b56d22f595f
    [13] mount(2) — Linux manual page
    https://man7.org/linux/man-pages/man2/mount.2.html
    [14] filepath-securejoin
    https://github.com/cyphar/filepath-securejoin
    [15]tmpfs
    https://en.wikipedia.org/wiki/Tmpfs
    [16] Overlay Filesystem
    https://www.kernel.org/doc/html/ ... tml#non-directories
    [17] mount(2) — Linux manual page
    https://man7.org/linux/man-pages/man2/mount.2.html
    [18]  rootfs: add mount destinationvalidation
    https://github.com/opencontainer ... 1115a849b56d22f595f
    [19] Re: [PATCH 2/3] namei: implement AT_THIS_ROOT chroot-like path resolution
    https://lwn.net/ml/linux-kernel/CAG48ez30WJhbsro2HOc_DR7V91M+
    [email]hNFzBP5ogRMZaxbAORvqzg@mail.gmail.com[/email]/




    Untitled 1.png
    Untitled.png
    Untitled 5.png
    Untitled 10.png
    腾讯安全云鼎实验室,关注云主机与云内流量的安全研究和安全运营。利用机器学习与大数据技术实时监控并分析各类风险信息,帮助客户抵御高级可持续攻击;联合腾讯所有安全实验室进行安全漏洞的研究,确保云计算平台整体的安全性。相关能力通过腾讯云开放出来,为用户提供黑客入侵检测和漏洞风险预警等服务,帮助企业解决服务器安全问题。
    发新帖
    您需要登录后才可以回帖 登录 | 立即注册