Openwall |

---|---
| 在 Twitter 上关注 @Openwall,获取新版本发布和其他新闻

[<prev] [next>] [day] [month] [year] [list] ``` Message-ID: aCISrQTbLQjaxBZS@kasco.suse.de Date: Mon, 12 May 2025 17:24:26 +0200 From: Matthias Gerstner <mgerstner@...e.de> To: oss-security@...ts.openwall.com Subject: screen: Multiple Security Issues in Screen (mostly affecting release 5.0.0 and setuid-root installations)

大家好,

Screen 中的这些问题已于 2025-04-30 与发行版邮件列表共享,并计划于今天发布。 我们还在我们的博客 [1] 上提供了此报告的渲染版本。

  1. 简介 ========

在 2024 年 7 月,上游 Screen 维护者询问我们 [2] 是否可以查看当前的 Screen 代码库。 我们对此请求的处理优先级较低,因为几年前我们已经粗略地看过了 Screen,没有发现任何问题。 当我们实际找到时间再次研究它时,我们惊讶地发现 Screen 5.0.0 主要版本更新中存在一个本地 root 漏洞,该漏洞影响将其作为 setuid-root 发布的发行版(Arch Linux 和 NetBSD)。 我们还发现了一些额外的、不太严重的问题,这些问题也部分影响了仍在大多数发行版中发现的旧 Screen 版本。

在此电子邮件的附件中,您可以找到针对本报告中描述的问题的两组补丁,一组用于 screen-4.9.1,另一组用于 screen-5.0.0。 这些补丁集分别针对 screen-4.9.1 和 screen-5.0.0 发布 tarball 应用。 由于与上游的沟通存在困难,我们目前没有关于其端发布的错误修复和版本的详细信息。

下一节概述了常见 Linux 和 UNIX 发行版上的 Screen 配置和版本。 第 3 节详细讨论了我们发现的每个安全问题。 第 4 节探讨了 Screen 的 setuid-root 实现中可能存在的其他问题。 第 5 节为改进 Screen 的安全态势提供了通用建议。 第 6 节指出了我们在这些问题的协调披露过程中遇到的问题。 第 7 节提供了一个受影响性矩阵,其中快速概述了各种 Linux 和 UNIX 系统上的情况。

  1. Screen 配置和版本概述 =============================

2024 年 8 月,上游发布了 Screen 的 5.0.0 主要版本。 到目前为止,Arch Linux、Fedora 42 和 NetBSD 10.1 都提供了这个新版本的 Screen。 许多重构更改都进入了此 Screen 版本中,这些更改在某些情况下可以追溯到十年以上。

本报告中讨论的某些问题仅在 Screen 5.0.0 版本中引入,而其他问题也影响 Screen 4.9.1(及更早版本),在撰写本文时,Screen 4.9.1 仍然是大多数 Linux 和 UNIX 发行版中发现的版本。

除非另有说明,否则本报告中的任何源代码引用均基于上游 5.0.0 发布标签 [3]。 针对下面讨论的每个漏洞,都提供了当前 5.0.0 版本和更广泛使用的 4.9.1 Screen 版本的受影响信息。

注意:在撰写本文时,我们经常在尝试访问 Screen 的 Git Web 前端时遇到 HTTP 502“Bad Gateway”错误。 几秒钟后重试通常可以解决该错误。

关于 Screen 多用户模式

Screen 提供了一种多用户模式,允许连接到系统中其他用户拥有的 Screen 会话(如果具有适当的凭据)。 仅当 Screen 安装了 setuid-root 位集时,这些多用户功能才可用。 Screen 的这种配置导致攻击面大大增加,因为在这种情况下,复杂的 Screen 代码以 root 权限运行。

Screen 多用户会话由其名称标识,该名称需要具有 <user>/ 前缀。 以下命令行将创建一个这样的会话:

user1$ screen -S user1/my-multi-user-session

要管理对多用户会话的访问,Screen 维护访问控制列表 (acl),这些列表可以在 Screen 的配置文件 (~/.screenrc) 中配置,或者通过将命令发送到正在运行的 Screen 会话来配置(请参阅 screen(1) 手册页 [4])。 这些 acl 基于其他用户的帐户名,并且可以选择使用密码保护。

访问可以限制为“只读”模式,在这种模式下,没有输入可以传递到终端。

在我们调查的系统中,只有 Arch Linux、FreeBSD 和 NetBSD 安装 Screen 时设置了 setuid-root 位。 在 Gentoo Linux 上,如果设置了 "multiuser" USE 标志,则可以选择分配 setuid-root 位。 某些发行版安装 Screen 时分配了 setgid 位,以使其能够以特定的组凭据运行。 Gentoo Linux 默认就是这种情况,它将 Screen 安装为 setgid-utmp,允许 Screen 在系统范围内的 utmp 数据库中创建登录记录。 Fedora Linux 将 Screen 安装为 setgid-screen,允许 Screen 将套接字放置在 /run/screen 中的系统范围目录中。

  1. 安全问题 ============

3.a) 通过 logfile_reopen() 实现本地 Root 漏洞 (CVE-2025-23395)

此问题影响以 setuid-root 权限运行时 Screen 5.0.0。 函数 logfile_reopen() [5] 在处理用户提供的路径时不会删除权限。 这允许非特权用户以 root 所有权、调用用户的(真实)组所有权和文件模式 0644 在任意位置创建文件。 写入 Screen PTY 的所有数据都将记录到此文件中。 也可以通过这种方式滥用已经存在的文件进行日志记录:数据将被追加到相关文件中,但文件模式和所有权将保持不变。

Screen 在最初打开日志文件时会正确删除权限。 一旦 Screen 认为有必要重新打开日志文件,权限提升就成为可能。 Screen 通过在写入文件之前调用 stolen_logfile() [6] 来检查这一点。 当最初打开的日志文件的链接计数降至零,或者如果其大小意外更改时,将发生对 logfile_reopen() 的调用。 可以在非特权用户的末尾随意触发此条件。

这是一个重现程序,演示了如何在受影响的系统上实现基本的本地 root 漏洞:

使用自定义日志文件路径创建 Screen 会话

(shell1) user$ screen -Logfile $HOME/screen.log

输入组合键以启用记录到配置路径

(screen) user$ H

在另一个 shell 中,删除 Screen 刚刚创建的日志文件,并

用指向特权位置的符号链接替换它

(shell2) user$ rm $HOME/screen.log; ln -s /etc/profile.d/exploit.sh
$HOME/screen.log

返回 Screen 会话,回显一个将记录到

现在重定向的日志文件中的漏洞利用命令。

这需要通过 echo 来添加前导换行符,以防止

bash 提示符破坏漏洞利用。 同样,尾随分号

是必要的,以防止以下控制字符成为

shell 命令的一部分。

(screen) user$ echo -e "\nchown $USER /root;"

现在以 root 身份执行新的登录,并观察正在执行的漏洞利用。

在登录期间,您可能会看到一系列 shell 错误。

root# ls -lhd /root drwxr-x--- 5 user root 4.0K Dec 30 2020 .

这只是实现本地 root 漏洞的一种幼稚方法,它隐藏得不是很好(因为奇怪的错误消息),并且需要实际的 root 用户登录才能触发它。 可能还有许多其他利用它的方法,例如,通过为 sudo 之类的工具编写新的配置文件,或者通过将代码附加到在 /usr/bin 和类似位置找到的特权 shell 脚本。

错误修复

该问题是通过旧的 commit 441bca708bd [7] 引入的,该 commit 现在才成为 5.0.0 版本的一部分。 在此 commit 中,删除了被认为不需要的 lf_secreopen() 函数。 附加的 screen-5.0.0-patches.tar.gz 中的补丁 0001 通过在日志文件重新打开期间重新引入安全文件处理来解决此问题。

受影响的发行版

Arch Linux

Arch Linux 完全受此问题的影响,因为它发布了 5.0.0 版本并分配了 setuid-root 位。 但是,默认情况下未在 Arch 上安装 Screen。

Fedora Linux

受影响的 5.0.0 版本仅在最近发布的 Fedora 42 中找到。 Screen 在那里以 setgid-screen 凭据运行,以便能够写入 /run/screen 目录。 在那里为每个运行 Screen 多用户会话的用户创建一个模式为 0700 的私有目录。 因此,该漏洞利用将不允许写入其他用户的会话目录,只能直接在 /run/screen 中创建文件。 我们在这里能想到的唯一攻击向量是通过声明其他用户会话目录的名称来导致本地 DoS 情况,如果它们尚不存在的话。 另一种攻击向量可能是尝试填满 /run 文件系统(TMPFS)的可用磁盘空间以破坏其他系统服务。

Gentoo Linux

Gentoo Linux 在其稳定的 Screen ebuild 中不受影响,该 ebuild 仍然基于 Screen 版本 4.9.1。

但是,当使用 Gentoo 不稳定的 'app-misc/screen-9999' ebuild 时,将安装受影响的 5.0.0 版本。 如果还设置了 "multiuser" USE 标志,则将应用 setuid-root 位,从而导致 Screen 完全容易受到攻击。

如果没有此 USE 标志,Screen 在 Gentoo Linux 上以 setgid-utmp 身份运行,这允许使用此漏洞来覆盖 /var/log/wtmp 数据库。 这使得有可能破坏数据库的完整性,甚至创建登录条目,这些条目可能会对系统中依赖于此信息的其他特权程序产生不利影响。

FreeBSD

FreeBSD 仍在使用版本 4.9.1。 如果 Screen 升级到 5.0.0,则 FreeBSD 也将受到影响,因为默认情况下 Screen 安装为 setuid-root。

NetBSD

在 NetBSD 上,可以安装受影响的 Screen 5.0.0 版本,并且默认情况下它将以 setuid-root 权限运行。 这使其完全受到该问题的影响。

3.b) 连接到多用户会话时 TTY 劫持 (CVE-2025-46802)

此问题在设置了 multiattach 标志时(即 Screen 尝试连接到多用户会话)的 Attach() 函数中发现。 该函数对当前 TTY 执行模式为 0666 的 chmod() [8]。 当前 TTY 的路径存储在 attach_tty 字符串中:

if ((how == MSG_ATTACH || how == MSG_CONT) && multiattach) { /* snip */ if (chmod(attach_tty, 0666)) Panic(errno, "chmod %s", attach_tty); tty_oldmode = tty_mode; }

幸运的是,在 Screen 中计算出的 TTY 路径经过了充分的正确性探测。 特别是,isatty() 对于 FD 0(用于确定 TTY 路径)需要为真,并且生成的路径需要驻留在 /dev 中。 否则,此 chmod() 将导致另一个本地 root 漏洞。

原始 TTY 模式在函数结尾的第 284 行 [9] 恢复。 我们不太确定此临时权限更改的目的是什么,也许它应该允许目标会话的 Screen 守护程序(可能具有不同的凭据)访问客户端的 TTY 以进行连接过程。

此临时 TTY 模式更改的问题在于,它引入了一个竞争条件,允许系统中的任何其他用户在短时间内打开调用者的 TTY 以进行读取和写入。 我们基于 Linux 的 inotify API 进行了一些简单的测试,并且我们设法使用一个简单的 Python 脚本以这种方式每隔一到三次尝试打开受影响的 TTY。

此问题的影响是,攻击者可以拦截输入到 TTY 中的数据,也可以将数据注入其中。 攻击者可能会试图误导 TTY 的所有者输入密码,或获取其他敏感信息。 此外,可以将控制序列注入到受影响的 TTY 中,这增加了进一步混淆受害者或利用相关终端模拟器中的问题的可能性。

Attach() 函数中还存在一些返回路径,在这些路径中,原始模式永远不会再次恢复。 例如,这种情况发生在第 160 行 [10] 中,如果未找到目标会话并且已设置 "quiet" 命令行参数,则进程会显式退出。 此方面的一个简单的重现程序如下:

检查当前的 TTY 权限,这些权限是安全的

user$ ls -l tty crw--w---- 1 user tty 136, 1 Feb 5 12:18 /dev/pts/1

尝试连接到 root 用户的某个不存在的会话。

请注意,这仅在目标用户的会话目录(例如,

在 $HOME/.screen 中)已经存在时才有效,否则逻辑会提前终止

并且不会发生 chmod()

user$ screen -r -S root/some-session -q

观察现在不安全的 TTY 权限

user$ ls -l tty crw-rw-rw- 1 user tty 136, 1 Feb 5 12:19 /dev/pts/1

Panic() 函数(主要在 Attach() 中用于停止进程执行)正确恢复旧的 TTY 模式 [11]。 只有使用 returneexit() 的代码路径才会受到此缺少 TTY 模式恢复的影响。

错误修复

我们假设有问题的 chmod() 调用很可能只是过去时代的残余,当时使用这种不安全的方法来授予目标 Screen 会话访问新客户端的 PTY 的权限。 如今,Screen 通过 UNIX 域套接字安全地将 PTY 文件描述符传递到目标会话。

因此,要解决此问题,可以删除临时 chmod() 到模式 666。 这就是附加的 screen-4.9.1-patches.tar.gz 中的补丁 0001 和 screen-5.0.0-patches.tar.gz 中的补丁 0004 所做的事情。

在本报告发布前不久,有人向我们指出,此补丁可能会破坏 Screen 中的某些重新连接用例 [12]。 我们可以确认此问题,但同时也发现即使在 Screen 4.9.1 中,此特定用例显然也已被破坏 [13]。 因此,我们决定不再次移动发布日期,也不急于调整此补丁,结果尚不确定。 该补丁仍然可以解决安全问题,现在上游可以公开修复此回归,该回归似乎已经存在于早期版本中。

受影响的发行版

与上一个问题不同,此问题不限于当前的 5.0.0 版本。 自 2005 年以来,Screen 版本中一直存在观察到的行为。 如果所有 Linux 发行版和 BSD 都通过安装 setuid-root 在 Screen 中提供多用户支持,那么所有这些发行版和 BSD 都会受到影响。

从理论上讲,如果 Screen 安装 setuid-root,此问题也会影响 Screen,因为调用者始终有权修改其自身 TTY 的模式。 但是,如果目标会话不属于调用者并且没有可用的 root 权限,则 Screen 将拒绝继续操作。 当用户出于某种原因尝试加入自己拥有的多用户会话时,仍然会触发有问题的代码。 导致这种情况的一个示例调用是 screen -r -S $USER/some-session -q。 受此问题较轻变体影响的系统在第 7 节中标记为部分受影响。

3.c) Screen 默认创建全局可写 PTY (CVE-2025-46803)

在 Screen 版本 5.0.0 中,Screen 分配的伪终端 (PTY) 的默认模式已从 0620 更改为 0622,从而允许任何人写入系统中的任何 Screen PTY。 从安全角度来看,这导致了问题 3.b) 中概述的某些问题,但没有信息泄露方面。

Screen 中默认 PTY 模式的历史非常复杂。 让我们看一下版本 4.9.1(以及许多旧版本)中的情况:

如果您不喜欢允许公共写入您的 pty 的默认值 0622,请定义 PTYMODE。

因此,在此版本中,autoconf 级别的默认模式与源代码级别的默认模式之间存在不一致,但最终(安全的)autoconf 默认模式获胜。

现在让我们看一下 Screen 版本 5.0.0 中的情况:

错误修复

除了几个 ChangeLog 条目之外,我们没有找到任何关于版本 5.0.0 的 Screen 发行说明。 将默认 PTY 模式更改为 0622 似乎不是一个经过深思熟虑的决定。

附加的 screen-5.0.0-patches.tar.gz 中的补丁 0002 通过在 configure.ac 脚本中恢复安全的默认 PTY 模式来解决此问题。

请注意,您需要运行 autoreconf 才能使更改生效。

我们建议打包者主动传递配置开关 --with-pty-mode=0620 以明确此选择,也适用于较旧版本的 Screen。

受影响的发行版

Gentoo Linux 和 Fedora Linux 将显式安全 --with-pty-mode 传递给 Screen 的配置脚本。 对于未列为受影响的发行版以外的发行版,我们没有检查它们是否也这样做,或者它们是否依赖于较旧的 Screen 版本中存在的安全默认设置。

Arch Linux

在 Arch Linux 上,软件包构建不会传递 --with-pty-mode 开关,从而导致应用新的默认设置,从而使当前 Arch Linux 上的 Screen 容易受到此问题的影响。

NetBSD

NetBSD 受此问题的影响与 Arch Linux 相同。

3.d) 通过套接字查找错误消息进行文件存在性测试 (CVE-2025-46804)

这是一种在以 setuid-root 权限运行 Screen 时发生的次要信息泄露,在较旧的 Screen 版本以及版本 5.0.0 中都可以找到。 screen.c 中从第 849 行 [19] 开始的代码以 root 权限检查生成的 SocketPath,并提供错误消息,允许非特权用户推断有关该路径的信息,否则这些信息将不可用。

一种简单的方法是使用 SCREENDIR 环境变量。 以下是一个在当前 Arch Linux 上有效的示例:

这可用于测试 /root/.lesshst 是否存在并且是常规文件

user$ SCREENDIR=/root/.lesshst screen /root/.lesshst 不是一个目录。

这允许推断目录 /root/.cache 存在

user$ SCREENDIR=/root/.cache screen bind (/root/.cache/1426.pts-0.mgarch): 权限被拒绝

这告诉我们路径 /root/test 不存在

user $ SCREENDIR=/root/test screen 无法访问 /root/test:没有这样的文件或目录

错误修复

附加的 screen-4.9.1-patches.tar.gz 中的补丁 0002 和附加的 screen-5.0.0-patches.tar.gz 中的补丁 0005 通过仅在安装 setuid-root 且目标路径不受进程的真实 UID 控制时输出通用错误消息来解决该问题。

受影响的发行版

我们考虑的所有发行版都受到影响。

3.e) 发送信号时的竞争条件 (CVE-2025-46805)

在 socket.c 的第 646 行 [20] 和第 882 行 [21] 中,关于在 setuid-root 上下文中向用户提供的 PID 发送信号时存在检查时间/使用时间 (TOCTOU) 竞争条件。

CheckPid() 函数 [22] 将权限降至真实用户 ID,并使用这些凭据测试内核是否允许向目标 PID 发送信号。 实际信号稍后通过 Kill() 发送,可能使用完整的 root 权限。 此时,先前检查的 PID 可能已被不同的特权进程替换。 也可能欺骗(特权)Screen 守护进程向自身发送信号,因为进程始终允许向自身发送信号。

目前,这应该只允许发送 SIGCONT 和 SIGHUP 信号,因此影响可能仅限于本地拒绝服务或次要的完整性破坏。

当 Screen 安装为 setuid-root 时,此问题会影响 Screen 版本 5.0.0 和较旧的 4 版本。 此问题源于 CVE-2023-24626 的不完整修复 [23]:在此不完整修复之前,即使没有赢得竞争条件,也可以将相关信号发送到任意进程。

错误修复

附加的 screen-4.9.1-patches.tar.gz 中的补丁 0003 和附加的 screen-5.0.0-patches.tar.gz 中的补丁 0006 通过使用真实 UID 权限发送实际信号(就像 CheckPid() 所做的那样)来解决该问题。

受影响的发行版

我们考虑的所有发行版都受到影响。

3.f) 错误的 strncpy() 使用导致发送命令时崩溃

我们认为这是一个非安全问题,但仍然应该优先修复。 此问题仅在 Screen 版本 5.0.0 中发现。

在 commit 0dc67256 [24] 中,许多 strcpy() 调用已被 strncpy() 替换。 作者显然没有意识到 strncpy() 的不幸语义。 此函数并非旨在用于安全字符串处理,而是用于维护固定长度的零填充缓冲区。

因此,当遇到第一个 \0 字节时,strncpy() 不会停止将数据写入目标缓冲区,而是写入零,直到缓冲区完全填满。

除了导致性能下降之外,这还会触发 attacher.c 第 465 行中的一个错误。 在那里应用了以下更改:

这些行是以下 for 循环的一部分,该循环处理命令行参数以将其发送到正在运行的 Screen 会话。

for (; *av && n < MAXARGS - 1; ++av, ++n) { size_t len; len = strlen(*av) + 1; if (p + len >= m.m.command.cmd + ARRAY_SIZE(m.m.command.cmd) - 1) break; strncpy(p, *av, MAXPATHLEN); p += len; }

strncpy() 的调用始终将 MAXPATHLEN 字节作为目标缓冲区大小传递。 对于 for 循环的第一次迭代,当 p 指向 screen.h 第 148 行 [25] 中声明的 struct Message.command.cmd 缓冲区的开头时,这是正确的。 但是,对于 for 循环的后续迭代,当 p 递增 len 时,这不再正确。 这意味着将来的 strncpy() 调用将超出缓冲区末尾写入过量的 \0 字节。

在将多个命令参数传递给正在运行的 Screen 实例时,可以在当前的 Arch Linux 上观察到此结果:

创建一个新的 screen 会话

user$ screen -S myinstance

并再次从中分离

(screen) user$ d

现在尝试向正在运行的会话发送命令

user$ screen -S myinstance -X blankerprg /home/$USER/blanker *** 检测到缓冲区溢出 ***:已终止 中止(核心已转储)

这两个命令参数导致上述 for 循环中进行两次迭代; 第二次迭代将触发缓冲区溢出检测。 仅当使用 _FORTIFY_SOURCE 功能编译 Screen 时,才会出现可见的错误。 否则,即使使用 -fsanitize=address 进行编译,也不会出现任何错误,这可能是因为在目标缓冲区结束后,另一个长缓冲区 char message[MAXPATHLEN * 2] 紧随其后(因此仅应用程序有效负载数据被覆盖)。

此问题允许调用者用零覆盖 cmd 缓冲区后面的 MAXPATHLEN 字节的内存,这可能会导致 Screen 中的完整性破坏,尤其是在以 setuid-root 身份运行时。 由于在内存中紧随其后的是一个大小相等的缓冲区 writeback[MAXPATHLEN],因此不应有可能利用此问题来发挥攻击者的优势。

要解决此问题,需要将 MAXPATHLEN 替换为 p 中实际剩余的字节数。 此外,理想情况下,应将所有 strncpy() 调用替换为 snprintf(target, target_size, "%s", source),以避免零填充目标缓冲区的意外影响。

我们想知道为什么这个问题可以在 Screen 5.0.0 中存在这么长时间而没有人注意到。 一种解释可能是 Screen 版本 5.0.0 目前仅存在于少数发行版中。 另一方面,也许只有少数用户使用此功能向正在运行的 Screen 会话发送命令。 我们仍然在 screen-users 邮件列表 [26] 上找到了一份不久前的报告,该报告似乎正指向此问题。

错误修复

附加的 screen-patches-5.0.0.tar.gz 中的补丁 0003 通过将 strncpy() 更改为 snprintf() 并正确传递目标缓冲区中的剩余空间量来解决此问题。

受影响的发行版

所有发布 screen-5.0.0 的发行版都受到影响。

  1. Screen 的 setuid-root 实现中可能存在的其他问题 ===========================================================

在解决问题 3.e) 的错误修复时,我们还注意到,当目标会话以非 root 身份运行时,CVE-2023-24626 的原始(不完整)错误修复对 Screen 中的多用户模式引入了回归。 在这种情况下,目标会话将权限降至某个 UID X,然后尝试向某个 UID Y(客户端的)发送信号,这始终会失败。

这表明在 Screen 的多用户模式中实际上需要考虑三个不同的 UID:执行特权操作的有效 UID 0、创建会话的用户的真实 UID 和连接到会话的用户的真实 UID。 我们认为当前 Screen 代码没有正确考虑这一点。

这也引起了我们的注意,即由 root 创建的 Screen 多用户会话将“权限降至”创建用户的真实 UID,这将是 UID 0,因此实际上根本没有执行任何权限下降。

  1. 通用建议 ============

从 Screen 5.0.0 中的更改中,我们可以看到,长期以来,人们一直在尝试重构代码库,该代码库在此之前仍然以 K&R 样式的 C 编写。 但是,在此重构过程中,一些长期建立的安全逻辑已被破坏,这导致了问题 3.a) 和