漏洞简介

CVE-2018-10000281是存在于Linux内核NFS子系统的访问控制错误漏洞,允许远程用户通过NFS读写权限范围外的文件,CVSS 3.x评分为7.4(高危)。漏洞利用需要确保目标NFS服务器对外暴露的文件系统启用了rootsquash选项2

Root squash is a special mapping that maps remote root user (uid 0) to local “nobody” user (uid 65534), which has minimal privileges.

漏洞分析

漏洞的补丁3非常简单,内容如下:

diff --git a/fs/nfsd/auth.c b/fs/nfsd/auth.c
index f650e475d8f0d8..fdf2aad7347090 100644
--- a/fs/nfsd/auth.c
+++ b/fs/nfsd/auth.c
@@ -60,10 +60,10 @@ int nfsd_setuser(struct svc_rqst *rqstp, struct svc_export *exp)
 				gi->gid[i] = exp->ex_anon_gid;
 			else
 				gi->gid[i] = rqgi->gid[i];
-
-			/* Each thread allocates its own gi, no race */
-			groups_sort(gi);
 		}
+
+		/* Each thread allocates its own gi, no race */
+		groups_sort(gi);
 	} else {
 		gi = get_group_info(rqgi);
 	}

结合rootsquash选项的含义来分析补丁,不难理解漏洞成因。开发者的本意是先通过一个for循环将客户端传过来的所有等于root的GID替换为一个低权限GID,然后执行一次groups_sort来对收到的所有GID排序。漏洞代码实际上在for循环的每一次迭代都执行了一次groups_sort,这可能导致传过来的root GID没有被全部替换为低权限GID。

例如,考虑这样一种情况:客户端的user用户的主GID为1000,辅助GID有0(root)。漏洞代码逻辑在处理GID 1000后,执行排序操作将GID 0排到了前面,使其躲过后续的低权限替换处理。这样一来,rootsquash就被绕过了。漏洞代码片段如下所示:

for (i = 0; i < rqgi->ngroups; i++) {
    if (gid_eq(GLOBAL_ROOT_GID, rqgi->gid[i]))
        gi->gid[i] = exp->ex_anon_gid;
    else
        gi->gid[i] = rqgi->gid[i];

    /* Each thread allocates its own gi, no race */
    groups_sort(gi);
}

环境配置

服务端

首先构建并启动一个带有漏洞的Linux内核虚拟机环境作为服务器,安装NFS server并创建共享目录:

sudo apt install nfs-kernel-server
sudo mkdir -p /var/nfs/general

接着在/etc/exports配置NFS server并更新记录,使其带root_squash选项对外暴露:

# /var/nfs/general *(rw,sync,no_subtree_check,root_squash)
sudo vim /etc/exports
sudo exportfs -a

客户端

首先安装NFS client:

sudo apt install nfs-common

接着在客户机上创建一个UID为1000的user用户,除了默认GID为1000的组外,为它添加包含root组在内的额外三个组,例如:

uid=1000(user) gid=1000(user) groups=1000(user),0(root),27(sudo),104(kvm)

在客户机上修改/etc/fstab文件,使user用户能够挂载NFS(我设置了虚拟机本地端口映射,所以下面命令中使用127.0.0.1作为NFS服务地址):

# 127.0.0.1:/var/nfs/general /home/user/ans nfs defaults,user 0 0
sudo vim /etc/fstab
mkdir -p /home/user/ans

在客户机上创建/home/user/ans目录,然后连接到NFS server:

mount /home/user/ans

复现调试

在客户机上,首先在ans目录下多次创建文件,发现文件的GID并不是root,也无法写root创建的文件。

准备通过打印调试信息的方式查找原因。在服务端内核源码文件fs/nfsd/auth.c中加入一些打印语句4,重新编译内核。重新建立连接并执行前述测试后,从内核日志中发现rqgi->ngroups变量始终为1。也就是说,客户端user用户的辅助用户组没有被服务端内核fs/nfsd/auth.c处理。现在需要弄清楚问题在客户端(没有发送所有的GID)还是在服务端(只使用了第一个GID)。

在服务端tcpdump抓包分析发现,客户端发送了user用户的所有GID,如下图所示:

Xnip2024-01-22_14-04-35

因此,问题出在服务端。查找资料56后发现,rpc.mountd进程的--manage-gids选项会在服务端查找GIDs,然后用查找结果替换客户端提供的GIDs。于是,在服务端编辑/etc/default/nfs-kernel-server文件,移除--manage-gids选项,并重启NFS服务。

再次测试,成功复现漏洞——NFS目录中有一个只有root用户和root用户组可读的文件hello,我们使用user用户不断去读这个文件,经过几次尝试后可以读取成功。复现效果如下:

image

参考文献