☰
OpenSSH 9.8p1升级实战:修复regreSSHion漏洞与OpenSSL依赖
2026/10/3 12:45:38 网站建设 项目流程

2024年7月1日,OpenSSH官方紧急发布了9.8p1,修复了一个代号为regreSSHion的远程代码执行漏洞,对应CVE-2024-6387。当时我的第一反应不是"又一个高危漏洞",而是"这次轮到了sshd"。做过Linux运维的人都清楚,sshd长期暴露在22端口上,承担着所有远程登录入口的职责,一旦它出了可被远程利用的漏洞,整台机器的安全边界就形同虚设。这篇保姆级教程,就把我从收到漏洞通告到完成OpenSSH 9.8p1升级、顺带处理OpenSSL老版本依赖问题的完整过程写出来。整个流程覆盖环境评估、OpenSSL 3.0编译安装、OpenSSH 9.8p1源码编译、服务切换验证和回滚方案,适用于CentOS 7、Ubuntu 22.04这些主流Linux环境,照着做就能把风险控制住。

1. 为什么偏偏要赶在9.8p1:regreSSHion不是普通漏洞

很多人看到"OpenSSH又发新版了"第一反应是“日常更新”,但9.8p1这次的发布节奏和版本跨度非常反常。官方直接把版本号从9.7跳到了9.8,而且同步修复了一个可以直接导致root权限远程代码执行的严重安全问题。升级不再是“选做题”,而是“必答题”。

1.1 一个2006年修复过的老问题为何会复活

CVE-2024-6387的本质是sshd中的一个信号处理竞争条件(signal handler race condition)。sshd在客户端连接建立后,如果客户端一直不完成身份认证,LoginGraceTime设置的超时计时器会触发SIGALRM信号。问题在于,这个信号处理器内部调用的函数并不是异步信号安全的,当它和主流程中的其他操作同时发生时,就可能出现内存损坏,进而被攻击者利用执行任意代码。

最棘手的一点是,这个漏洞不需要任何认证信息,也不需要合法的账号密码。攻击者只要能够通过网络访问到sshd端口,反复建立空闲连接来触发竞争窗口,就有可能拿到root shell。整个利用过程通常在几小时内完成,如果目标系统没有开启地址空间随机化(ASLR),利用难度还会进一步降低。

这个问题的讽刺之处在于,它其实是2006年CVE-2006-5051修复过的老问题。当时OpenSSH通过修改signal handler解决了不安全函数调用的问题,但在后续代码重构中,这个修复被部分回退了,导致8.5p1到9.7p1之间的版本重新暴露在风险之下。官方称之为regreSSHion,既是在描述"回归",也是在提醒运维人员别轻视历史包袱。

1.2 升级范围的判定:哪些版本中招、哪些不受影响

判断自己是否需要升级,先看两个维度:系统架构和OpenSSH版本。

受影响的是:64位glibc Linux系统上,OpenSSH版本在8.5p1到9.7p1之间的sshd。这也是当前绝大多数云服务器和物理机默认使用的组合。不受影响的情况包括:OpenBSD系统、32位Linux、使用musl libc的Alpine Linux、以及低于8.5p1但已经应用过旧补丁的Red Hat系发行版。

我在实际排查中发现,很多CentOS 7默认自带的是OpenSSH 7.4p1,单看版本号确实不在受影响区间内,但问题在于这类老系统往往还带着老旧的OpenSSL 1.0.2k,而OpenSSL 1.0.2和1.1.1两个长期支持分支都已经停止维护。也就是说,即使sshd本身暂时不被这个CVE波及,配套的加密库也已经是安全盲区。这也是为什么本次升级我会把OpenSSL一起纳入范围:既然要动编译链,就一次把技术债还清。

2. 动手前先盘点环境:版本、系统、服务管理方式

源码编译升级OpenSSH最怕两件事:一是远程操作中断导致失联,二是新版本与系统既有配置不兼容导致sshd无法启动。这两件事都可以通过充分的预检来避免。花十分钟做环境盘点,比升级失败后花两小时救数据划算得多。

2.1 三步看清当前版本和依赖

第一步,确认当前OpenSSH和OpenSSL版本。注意ssh -V的输出是写到stderr的,直接执行屏幕上能看到,但如果不重定向,脚本或文档里抓取时容易漏掉信息。

ssh -V 2>&1 # 或者查看sshd本身的版本 /usr/sbin/sshd -V 2>&1 openssl version -a

第二步,确认操作系统发行版和init系统。不同发行版在编译依赖、服务名和配置文件路径上都有差异,不能一概而论。

cat /etc/os-release ps -p 1 -o comm= systemctl status sshd 2>/dev/null || service sshd status

第三步,确认当前sshd的监听端口、配置文件和认证方式。升级前必须知道当前的登录链路是什么样,否则替换完二进制后可能发现配置文件里有关键参数变动,导致彻底无法登录。

ss -tlnp | grep :22 grep -E '^(Port|PermitRootLogin|PubkeyAuthentication|PasswordAuthentication)' /etc/ssh/sshd_config

2.2 不同发行版的兼容性差异

下面这个表是我在实际操作中踩过坑后总结出来的,直接决定你后面要不要额外处理一些环境问题。

发行版默认OpenSSL默认OpenSSH注意点
CentOS 71.0.2k7.4p1系统自带的OpenSSL老得离谱,但yum、curl等大量系统组件依赖它,绝对不能直接覆盖
CentOS 81.1.1k8.0p1依赖库相对新,编译OpenSSL 3.0基本无障碍
Rocky/AlmaLinux 93.0.78.7p1系统自带OpenSSL 3.x,可以跳过OpenSSL升级步骤,直接编译OpenSSH
Ubuntu 20.041.1.1f8.2p1需要额外确认libpam0g-dev和zlib1g-dev已经安装
Ubuntu 22.043.0.28.9p1系统自带OpenSSL 3.x,同样可以跳过OpenSSL升级,但为了统一版本我一般还是会编译安装最新3.0.x
Debian 11/121.1.1n / 3.0.x8.4p1 / 9.2p1apt源里openssl版本比CentOS 7干净很多,编译依赖用build-dep最省事

如果是CentOS 7这种老系统,我强烈建议OpenSSL和OpenSSH都编译安装到独立目录,不要动系统自带的任何openssl相关库。因为yum在安装或更新软件包时会重新生成依赖关系,一旦发现libssl.so.10被替换或删除,整个包管理器都可能瘫痪。

2.3 备份策略:不备份就升级,是给自己埋雷

源码编译升级OpenSSH,备份的重点不是"整个系统快照",而是下面这几个具体对象:

# 备份配置目录 cp -a /etc/ssh /etc/ssh.bak.$(date +%Y%m%d) # 备份关键二进制 mkdir -p /opt/ssh-backup/bin cp -a /usr/sbin/sshd /opt/ssh-backup/ cp -a /usr/bin/ssh /opt/ssh-backup/ cp -a /usr/bin/scp /opt/ssh-backup/ cp -a /usr/bin/sftp /opt/ssh-backup/ # 确认没有任何进程正在占用待替换的文件(正常sshd常驻时会占用,但备份不受影响) lsof /usr/sbin/sshd

另外要多说一句:如果服务器上有大量存量会话,替换sshd二进制后,已经建立的连接不会断,新连接才会走新版本的sshd。所以备份完二进制后,不需要担心当前会话被切断。真正要担心的是restart服务之后新版本启动失败。因此,强烈建议操作前先打开一个"逃生"通道,比如云控制台的VNC、带外管理口,或者在另一个端口临时启动一个老版本的sshd实例。这样即使正式服务挂了,也不至于被锁在门外。

3. OpenSSL 3.0升级:单独编译安装,别碰系统自带库

OpenSSL升级是整个过程中最需要克制的一步。很多第一次做升级的人,下意识想的是"把系统自带的OpenSSL卸载了,装新版本上去"。这个想法在纯实验环境可行,在真实服务器上就是灾难。系统里几十个程序链接的都是/lib64/libssl.so.10这类旧库文件,你把它换成新版本,ABI不兼容,轻则curl断掉,重则整个系统网络栈崩溃。

3.1 为什么OpenSSH 9.8p1建议搭配OpenSSL 3.x

OpenSSH 9.8p1在configure阶段会检测OpenSSL版本,虽然它也能连旧版本的libssl,但从维护角度讲,OpenSSL 1.1.1和1.0.2都已经EOL,后续不会再收到任何安全补丁。如果继续用它们编译OpenSSH,等于把升级OpenSSH的努力浪费了一半:sshd是新的,底层加密库全是老的已知漏洞。

长期支持分支里我建议选择OpenSSL 3.0.x。它经过多年迭代,稳定性已经接近1.1.1的成熟度,同时又具备QUIC、FIPS模块等新能力。我写这篇文章时,3.0分支的小版本已经更新到3.0.15,建议下载这个版本或者去官网选3.0系列的最新补丁版本。别追大版本号,够用就好。

3.2 编译三步走:解压、config、make install

把源码下载到/usr/local/src下进行编译,是最经典也最清晰的路径:

cd /usr/local/src wget https://www.openssl.org/source/openssl-3.0.15.tar.gz tar xzf openssl-3.0.15.tar.gz cd openssl-3.0.15 ./config --prefix=/usr/local/openssl-3.0.15 \ --openssldir=/usr/local/openssl-3.0.15/ssl \ shared no-async make -j$(nproc) make install

这里几个参数需要解释一下。--prefix指定了OpenSSL的安装根目录,我选择装到独立的/usr/local/openssl-3.0.15下面,这样所有文件都在一个目录里,后续回滚非常方便。--openssldir指定证书配置目录,OpenSSL会在这里放置openssl.cnf和证书目录结构。shared表示生成动态库,这是必须的,因为OpenSSH编译时默认会链接libcrypto动态库。no-async是很多人在编译时忽略的坑,它禁用异步I/O模块。在某些旧内核或者缺少完整内核头文件的环境里,async模块会编译失败,加上这个参数可以省掉很多折腾。

make -j$(nproc)是并行编译,nproc获取CPU核心数。如果服务器内存比较小,建议把并行度降一降,比如-j2,避免编译时内存被打满导致OOM。

3.3 动态库路径:ld.so.conf还是rpath

编译完OpenSSL后,最重要的一个环节是让系统找到新库。很多教程会教你把/usr/local/openssl-3.0.15/lib64写进/etc/ld.so.conf.d/,然后执行ldconfig。这个方案简单高效,但影响范围很大:只要程序加载了libcrypto.so.3,都会切到这个新版本。如果这个新版本和某个老程序不兼容,那个老程序就会开始报错。

更精细的方案是用rpath。在编译OpenSSH时,把新OpenSSL库的路径固化到可执行文件里,这样一来,只有ssh、sshd这些OpenSSH二进制会使用新OpenSSL库,其他系统程序仍然使用自带的旧库。这个隔离效果正好满足了"只升级目标服务"的需求:

export LDFLAGS="-Wl,-rpath,/usr/local/openssl-3.0.15/lib64 -L/usr/local/openssl-3.0.15/lib64" export CPPFLAGS="-I/usr/local/openssl-3.0.15/include"

如果你的服务器用途比较单一,比如就是一个内网堡垒机,那用ld.so.conf方案也可以。但我个人的习惯是:能用rpath隔离就尽量隔离,避免"为了修OpenSSH的漏洞,反而引入了新的系统级隐患"。

3.4 验证新OpenSSL是否可用

编译安装完成后,用绝对路径执行新版本,确认它正常加载:

/usr/local/openssl-3.0.15/bin/openssl version # 预期输出:OpenSSL 3.0.15 31 Jul 2024 (Library: OpenSSL 3.0.15 ...)

再检查一下动态库路径是否被正确嵌入到后续要编译的程序中。现在暂时不用管,等OpenSSH编译完,用ldd /usr/sbin/sshd检查libcrypto.so.3的来源,就能确认rpath是否生效。

4. OpenSSH 9.8p1编译安装:configure参数才是重头戏

OpenSSL就位后,开始编译OpenSSH。这个阶段真正决定成败的不是make那一下,而是configure参数是否匹配你的系统环境。参数配错,后面启动阶段会报出一堆莫名其妙的错误。

4.1 下载源码与准备编译依赖

OpenSSH 9.8p1的源码可以到OpenSSH官网或国内镜像站下载。建议下载到/usr/local/src统一管理:

cd /usr/local/src wget https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-9.8p1.tar.gz tar xzf openssh-9.8p1.tar.gz cd openssh-9.8p1

编译依赖方面,CentOS 7和Ubuntu的命令完全不同。CentOS 7需要安装如下软件包:

yum install -y gcc make pam-devel zlib-devel perl

Ubuntu 22.04则需要:

apt update apt install -y build-essential libpam0g-dev zlib1g-dev

这里有个容易忽略的点:因为我们要用新编译的OpenSSL,所以系统自带的openssl-devel或libssl-dev装不装都不影响OpenSSH编译。但zlib-devel是必须的,OpenSSH默认启用zlib压缩支持,缺少这个头文件,configure会提醒找不到zlib库。

4.2 configure关键参数逐一说明

下面是完整的configure命令,我加了很多注释,方便你对照自己的环境修改:

./configure \ --prefix=/usr \ --sysconfdir=/etc/ssh \ --with-ssl-dir=/usr/local/openssl-3.0.15 \ --with-pam \ --with-privsep-path=/var/empty/sshd \ --with-md5-passwords \ --with-kerberos5=/usr/lib64 \ LDFLAGS="-Wl,-rpath,/usr/local/openssl-3.0.15/lib64 -L/usr/local/openssl-3.0.15/lib64" \ CPPFLAGS="-I/usr/local/openssl-3.0.15/include"

逐个讲清楚,这直接关系到后续能不能少踩坑:

  • --prefix=/usr:指定安装目录为/usr。这样make install会把新的ssh、sshd、scp等二进制直接替换到/usr/bin和/usr/sbin下,当前shell的PATH立刻生效,不需要额外做符号链接。如果你更保守,可以装到/usr/local/openssh,然后手动维护PATH和systemd服务指向,但操作会复杂很多。
  • --sysconfdir=/etc/ssh:配置文件目录。保持系统原有路径,能够直接沿用已有的sshd_config和host key。
  • --with-ssl-dir=/usr/local/openssl-3.0.15:指定使用新编译的OpenSSL头文件和库。
  • --with-pam:启用PAM支持。绝大多数Linux发行版在sshd中启用PAM是为了支持密码认证、账户锁定、审计等功能。如果不启用,可能会出现"能登录但last记录不到、登录失败但没有任何审计日志"等奇怪问题。
  • --with-privsep-path=/var/empty/sshd:权限分离目录。OpenSSH通过把sshd拆分成特权进程和非特权进程来提高安全性,这个目录是非特权进程的临时运行空间,必须存在且权限正确,否则sshd启动时直接报错。
  • --with-md5-passwords:允许使用MD5加密的密码字段。如果你系统里的用户密码是MD5格式而你想兼容它们,就加上这个选项。
  • LDFLAGS和CPPFLAGS:这里就是我上一步提到的rpath隔离方案。让OpenSSH二进制运行时直接找到/usr/local/openssl-3.0.15/lib64下的libcrypto.so.3,而不会去抢其他程序用的旧库。

如果你在configure阶段遇到"OpenSSL version headers do not match library"之类的版本不一致报错,可以先确认/usr/local/openssl-3.0.15/include/openssl/opensslv.h和lib目录下的libcrypto确实来自同一版本。如果确认无误仍然报错,可以加一个--without-openssl-header-check跳过版本检查,但务必保证版本本身是一致的。

4.3 make与install:覆盖旧版本前先做三件事

configure通过后,执行编译:

make -j$(nproc)

编译过程通常在几分钟内结束。在make install前,先把这三件事做完:

  1. 确认备份目录里的二进制齐全,见2.3节的清单。
  2. 查看一下当前系统sshd_config里是否存在自定义配置块。虽然9.8p1向下兼容大部分旧配置,但极少数废弃关键字会在sshd -t阶段就被拒绝。先心里有数,后面处理时就不会慌。
  3. 确认带外管理通道可用。至少确保有一个通过KVM、控制台或其他非SSH方式登录服务器的途径。

完成后执行安装:

make install

安装过程会覆盖/usr/bin/ssh、/usr/sbin/sshd、/usr/bin/scp等文件。安装结束后,先不要立刻重启服务,先做一次配置语法检查:

/usr/sbin/sshd -t

这一步非常关键。它输出为空说明配置正常,如果有问题,会明确指示哪一行配置有问题。看到报错先冷静,逐行解决,千万不要在重启服务后再去排查。

4.4 检查关键目录和权限

安装完成后,检查权限分离目录是否存在且属主正确:

mkdir -p /var/empty/sshd chown -R root:root /var/empty chmod 711 /var/empty chmod 755 /var/empty/sshd

同时检查host key的权限。OpenSSH 9.8p1对host key的权限要求更严格,私钥权限必须是600,公钥可以644:

chmod 600 /etc/ssh/ssh_host_*_key chmod 644 /etc/ssh/ssh_host_*.pub

检查完这些,用ldd再确认一下动态库链接到了新OpenSSL:

ldd /usr/sbin/sshd | grep ssl # 预期输出包含类似: # libcrypto.so.3 => /usr/local/openssl-3.0.15/lib64/libcrypto.so.3

如果看到libcrypto.so.1.1或者libssl.so.10,说明rpath没有生效,这时候就检查configure时的LDFLAGS是否传入正确,重新编译。

5. 服务切换与验证:别把自己锁在门外

编译安装完成,但服务还是旧版在跑,zi只完成了80。最终的切换和验证环节,最容易出问题的地方往往不在"启动新版本"本身,而在于切换方式不够平滑。

5.1 先测试新sshd,再动正式服务

我不推荐直接systemctl restart sshd,除非你完全确认配置没有兼容性问题。更稳妥的方式是先开一个测试端口,手动拉起一个新版sshd实例:

/usr/sbin/sshd -f /etc/ssh/sshd_config -p 2222 -E /var/log/sshd-test.log

这条命令会在2222端口启动新的sshd进程,并把日志输出到/var/log/sshd-test.log。然后从本机或另一台机器连接测试:

ssh -v -p 2222 root@127.0.0.1

如果这个过程能正常弹出密码或密钥验证、看到Shell,说明新版sshd运行正常,可以放心替换正式服务。测试完毕,kill掉这个临时进程:

pkill -f "sshd.*-p 2222"

注意这个pkill可能会匹配到别的进程,谨慎起见可以用ss -tlnp先查一下2222端口的PID,再精确删除。

5.2 重启正式服务并验证版本

确认测试无误后,正式重启服务:

systemctl restart sshd # 如果是SysVinit系统: # service sshd restart

重新加载后,用下面几条命令核对版本、监听状态和进程信息:

ssh -V 2>&1 /usr/sbin/sshd -V 2>&1 ss -tlnp | grep :22 ps -ef | grep [s]shd tail -n 20 /var/log/secure 2>/dev/null || journalctl -u sshd -n 20

此时ssh -V输出应该明确显示OpenSSH_9.8p1,并且日志里没有报错。

5.3 启动失败后的冷启动排查

冷启动必须提到台面上讲。如果sshd -t明明显示没问题,但restart后进程起不来,最常见的三个原因是:

  1. host key不存在或权限异常。表现为日志里出现"sshd: no hostkeys available"或"sshd: permission denied on key"。解决办法是重新生成host key:ssh-keygen -A。
  2. /var/empty/sshd权限不对。表现为"missing privilege separation directory"或"sshd: /var/empty/sshd must be owned by root and not group or world-writable"。照着4.4节修复权限即可。
  3. libcrypto动态库找不到。表现为"error while loading shared libraries: libcrypto.so.3: cannot open shared object file"。用ldd检查,确认rpath或ldconfig配置是否正确。

真到了无法救场的地步,回滚方案要能立刻执行。我用的是备份目录直接覆盖回去:

systemctl stop sshd cp -a /opt/ssh-backup/sshd /usr/sbin/sshd cp -a /opt/ssh-backup/ssh /usr/bin/ssh systemctl start sshd

执行完这几条,服务会退回升级前的状态。回滚后不要急着离开,把配置文件改动也一并还原,然后重新检查一次sshd -t。

6. 升级后的配置兼容与安全收尾:别让9.8p1的优势打折扣

二进制升级成功,不代表整个工作就完结了。OpenSSH版本升级后,最常遇到的问题有两类:旧参数被废弃导致配置校验失败,以及新版本默认策略更严格导致某些旧登录方式失效。

6.1 旧版sshd_config在新版本下的兼容性处理

升级后,用这条命令查看实际生效的配置:

/usr/sbin/sshd -T

它会输出所有生效配置项,即使你没写在sshd_config里。如果你在日志中看到类似"Deprecated option"的警告,直接编辑/etc/ssh/sshd_config,删掉对应行。常见的需要关注的关键字有过时的UseDNS、GSSAPIAuthentication等。

另外,9.8p1对公钥算法和主机密钥类型的要求比老版本更严格。如果之前还在使用DSA或较弱的RSA host key,建议直接生成新的ed25519密钥,并在sshd_config中明确HostKey路径。升级后我一般会用下面这组命令把host key刷新一遍,并保持配置一致性:

install -m 600 /dev/null /etc/ssh/ssh_host_ed25519_key ssh-keygen -A chmod 600 /etc/ssh/ssh_host_*_key /usr/sbin/sshd -t systemctl restart sshd

6.2 9.8p1实际使用中给运维带来的变化

从我实机操作的经验看,9.8p1最直观的变化是编译产物对OpenSSL的依赖链路更敏感了。如果你按照本文的方案编译,会不会发现sshd二进制启动内存占用略高了那么一点,这正常,因为它使用的OpenSSL 3.0模块在加载时会有额外的初始化开销,不必过度担心。

另外,新版本对错误日志的记录粒度更细。例如网络上有人扫描22端口,以前可能只记录"Connection closed by authenticating user",9.8p1会记录更多连接细节和认证失败前的状态。这对日常安全审计是好事,但也意味着/var/log/secure或journald日志量会上升。建议提前配置logrotate,避免日志把磁盘占满。

6.3 加固三板斧:登录方式、密钥轮换、日志监控

升级版本只是排掉了已知的雷,真正的安全水位还要靠配置加固来维持。我每次完成sshd版本升级后,都会顺手做三件事。

第一件事是收紧登录入口。如果运维场景不需要密码登录,直接关掉密码认证:

# /etc/ssh/sshd_config PermitRootLogin prohibit-password PubkeyAuthentication yes PasswordAuthentication no

第二件事是确认host key和用户authorized_keys的属主权限。sshd对.ssh目录权限要求很严格,用户目录的.ssh权限应该是700,authorized_keys是600,否则会出现"Authentication refused: bad ownership or modes"。

第三件事是增加失效熔断。在sshd_config中配置合理的MaxAuthTries和LoginGraceTime,降低暴力破解风险:

MaxAuthTries 3 MaxSessions 4 LoginGraceTime 30

配置修改后同样要跑一遍/usr/sbin/sshd -t再重启。

最后再分享一个关于编译升级的小技巧

编译OpenSSH时,不同版本之间对configure参数的容忍度差异不小。我在CentOS 7上第一次编译9.8p1时,曾因为系统自带的PKG_CONFIG_PATH指向了老版OpenSSL的pc文件,导致configure阶段始终检测到1.0.2的头文件。当时排查了很久,最终找到的办法是在configure前把环境变量强制清干净:

export PKG_CONFIG_PATH=/usr/local/openssl-3.0.15/lib64/pkgconfig

这个小细节看起来不起眼,但在跨版本升级场景下特别管用。如果你卡在configure的版本检测上,先检查PKG_CONFIG_PATH,再考虑加--without-openssl-header-check这类跳过参数。这样的问题往往一查一个准。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询