
做运维的都知道只要系统上了等保或者安全扫描OpenSSH升级几乎是躲不过去的一道坎。尤其是RHEL / CentOS 7.9这种已经进入生命周期末期的系统自带的OpenSSH版本普遍偏老扫描器随便照一下就是CVE列表里躺了好几年的大洞。更麻烦的是生产环境往往在内网隔离网段根本连不上外网yum源想升级只能把源码包拷进去离线编译。最近我正好接手了一批7.9机器的OpenSSH离线升级任务前前后后踩了不少坑把完整流程和排查思路整理出来给同样被这个问题困住的兄弟做个参考。这篇文章覆盖从环境盘点、依赖编译、OpenSSH安装到服务切换的完整链路所有操作都在纯内网环境验证过适用于RHEL 7.9和CentOS 7.9x86_64架构。新手可以照着一步步操作老手可以重点看第四部分的坑点总结尤其是升级后登录不上这种翻车现场怎么救回来。1. 为什么要离线升级OpenSSH背景与坑点1.1 等保合规与漏洞背景先说最现实的驱动力。等保2.0和行业安全基线检查里SSH服务版本是必查项。OpenSSH 7.4这个CentOS 7.9自带的版本在漏洞库里挂着不少中高危CVE包括但不限于用户名枚举、信息泄露、权限提升这类问题。扫描报告一旦出来限期整改是跑不掉的。内网机器还有个特点——系统盘和网络都是隔离的安全团队只给一个整改窗口比如周末凌晨两小时。这就意味着升级方案必须一次成功而且不能影响现有业务连接。还有一个隐藏问题内网机器很多是用了多年的老环境glibc版本、编译工具链都可能不全离线升级不是简单把rpm包装上就完事。1.2 离线环境下的升级难点分析离线升级OpenSSH本质上是解决三个问题依赖缺失、版本兼容、服务连续性。依赖这块OpenSSH编译需要OpenSSL、zlib、PAM开发库。OpenSSL尤其关键OpenSSH的加密通道完全依赖它。CentOS 7.9自带的OpenSSL是1.0.2k这个版本本身也有漏洞所以升级OpenSSH时通常要一起把OpenSSL升掉。但OpenSSL又不是随便乱升的——yum、curl、wget这些系统组件都动态链接了系统OpenSSL不能直接替换系统库否则yum可能直接罢工。版本兼容上OpenSSH 8.8以上版本默认禁用了ssh-rsa算法SHA-1老客户端会连不上。很多内网环境里还有老设备用ssh-rsa登录升级完新版本后这些设备就是Connection closed by remote host得提前想好对策。服务连续性最让人头疼。升级sshd过程中一旦配置出错或者服务起不来远程连接就断了而人还在机房外面。所以保底通道是必须的——telnet虽然明文不安全但关键时刻能救命。1.3 方案选型源码编译 vs 第三方RPM升级OpenSSH有两条主流路线一是拿源码编译二是找第三方维护的RPM包比如知名第三方仓库。我的建议是纯内网环境优先源码编译。理由有三个第一第三方RPM依赖关系复杂离线环境下搞依赖树本身就容易翻车第二源码编译可以把OpenSSL、OpenSSH都安装到自定义路径不影响系统默认组件风险可控第三等保整改往往有版本下限要求源码编译可以精确选版本。源码编译也有缺点——编译期间需要gcc、make等工具链有些精简安装的机器连gcc都没有。这个问题在后面准备工作里会细说。2. 动手前的准备工作依赖盘点与保底通道2.1 版本盘点与环境检查在碰任何源码包之前先把目标机器的家底摸清楚。需要确认四件事系统版本、架构、当前OpenSSH版本、当前OpenSSL版本。# 查看系统版本 cat /etc/redhat-release # 查看架构 uname -m # 查看当前OpenSSH版本 ssh -V 21 # 查看当前OpenSSL版本 openssl version我这次操作的机器都是CentOS 7.9 x86_64自带的OpenSSH 7.4p1OpenSSL 1.0.2k。升级目标是OpenSSH 9.6p1同时把OpenSSL升到1.1.1w。还要检查编译工具链# 检查gcc、make是否安装 gcc --version make --version # 检查PAM开发库 rpm -qa | grep pam-devel如果没有gcc这就麻烦了。内网环境没法yum install只能找同版本系统的安装光盘或者yum仓库缓存来补。CentOS 7.9的安装ISO里自带gcc、make这些包mount上去用rpm -ivh逐个安装就行。这一步虽然繁琐但务必在升级前做好否则编译到一半发现没编译器进退两难。2.2 依赖软件包的获取清单离线环境所有东西都要提前准备好。我列一下我这次用的源码包清单和下载地址在内网外的机器上下好再拷进去软件包版本说明zlib1.2.13OpenSSH编译依赖openssl1.1.1w加密库OpenSSH依赖openssh9.6p1目标升级版本openssl-devel1.0.2k系统自带如果系统里没有需要从ISO补pam-devel系统自带没有的话也要从ISO补源码包下载后用MD5/SHA256校验一下完整性然后传到目标机器的/usr/local/src目录。传文件的方式内网环境常用scp、ftp或者直接U盘拷。U盘拷贝时注意挂载格式FAT32单文件不能超过4GB不过这几个源码包都没那么大问题不大。2.3 提前开通telnet保底通道这是整个升级过程中最重要的保险措施没有之一。OpenSSH升级过程中任何一步出错sshd服务可能起不来telnet就是唯一的救命通道。CentOS 7.9默认没装telnet-server需要提前装好并启动# 检查是否已安装 rpm -qa | grep telnet-server # 如果没有从系统ISO或镜像站下载安装 # telnet-server-0.17-64.el7.x86_64.rpm # telnet-0.17-64.el7.x86_64.rpm # xinetd-2.3.15-14.el7.x86_64.rpm rpm -ivh telnet-server-*.rpm telnet-*.rpm xinetd-*.rpm # 配置xinetd管理telnet cat /etc/xinetd.d/telnet EOF service telnet { disable no flags REUSE socket_type stream wait no user root server /usr/sbin/in.telnetd log_on_failure USERID } EOF # 启动服务 systemctl restart xinetd systemctl enable xinetd防火墙放行telnet端口默认23firewall-cmd --permanent --add-port23/tcp firewall-cmd --reload然后从另一台机器测试telnet连接能登录成功再继续后面的操作。注意telnet是明文传输只在升级期间临时开放升级完成后必须立即关闭。如果公司安全策略不允许开telnet退而求其次是准备好物理console机房KVM或者确认带外管理卡如iLO/iDRAC可用。2.4 下载源码包与传输源码包版本选择上我建议OpenSSH选9.x的当前最新稳定版OpenSSL选1.1.1系列1.1.1w是最终维护版。OpenSSL 3.x在CentOS 7.9上编译会面临glibc版本过老的问题尽量别碰。下载完成后我习惯把所有源码包放在/usr/local/src统一解压cd /usr/local/src tar -zxvf zlib-1.2.13.tar.gz tar -zxvf openssl-1.1.1w.tar.gz tar -zxvf openssh-9.6p1.tar.gz解压后建议先看下编译说明README、INSTALL文件确认有没有特别要求。OpenSSH 9.6的INSTALL文件里会写明依赖的最低版本我之前见过有人不看说明直接编译因为PAM版本不够导致configure直接失败。3. 离线升级OpenSSH完整实操3.1 编译安装zlibzlib是基础库OpenSSL和OpenSSH都依赖它。虽然CentOS 7.9系统里已经有zlib但版本比较老1.2.7为了兼容性我们把它安装到独立目录。cd /usr/local/src/zlib-1.2.13 ./configure --prefix/usr/local/zlib make -j4 make install-j4是启用4个并行编译任务机器核多的话能明显加快速度。如果机器的CPU核数少可以改成-j2或直接不写这个参数。编译完成后zlib会被安装到/usr/local/zlib不影响系统原有的zlib库。注意编译OpenSSL时要用--with-zlib-lib/usr/local/zlib/lib这样的参数指定它的位置。3.2 编译安装OpenSSL到自定义路径OpenSSL是OpenSSH的核心依赖也是最容易出问题的环节。直接覆盖系统OpenSSL会引发连锁反应yum、wget都会坏所以这里必须安装到自定义路径。cd /usr/local/src/openssl-1.1.1w ./config --prefix/usr/local/openssl --openssldir/usr/local/openssl shared zlib \ --with-zlib-lib/usr/local/zlib/lib --with-zlib-include/usr/local/zlib/include注意几个关键配置项--prefix指定安装路径OpenSSL会装到/usr/local/opensslshared生成动态共享库OpenSSH编译需要链接动态库zlib启用zlib压缩支持-fPIC如果configure报错说需要PIC可以在config后面追加这个参数尤其当系统里其它库是以PIC方式编译时make -j4 make install编译完成后验证一下安装结果/usr/local/openssl/bin/openssl version注意这里不要设置LD_LIBRARY_PATH指向/usr/local/openssl/lib否则会影响系统全局的动态库加载顺序。OpenSSH编译时会通过--with-ssl-dir明确指定使用哪个OpenSSL运行时会通过rpath找到对应的库。如果OpenSSL编译过程中报错No such file or directory: perl或者Cant locate IPC/Cmd.pm这是系统Perl版本太老导致的。解决办法是用./config no-tests跳过测试模块构建或者手动升级Perl这个成本更高建议先试no-tests。3.3 编译安装OpenSSH这是整个升级的核心步骤。OpenSSH的configure参数决定了它的依赖位置、认证方式、运行路径一个参数不对就可能导致编译失败或登录异常。cd /usr/local/src/openssh-9.6p1 ./configure --prefix/usr/local/openssh \ --sysconfdir/etc/ssh \ --with-ssl-dir/usr/local/openssl \ --with-zlib/usr/local/zlib \ --with-pam \ --with-md5-passwords \ --mandir/usr/share/man参数含义如下--prefix安装到/usr/local/openssh--sysconfdir配置文件目录设置为/etc/ssh这样sshd_config、ssh_host_*_key等文件沿用系统原有位置避免迁移配置的麻烦--with-ssl-dir指定OpenSSL目录--with-zlib指定zlib目录--with-pam启用PAM认证支持否则会绕开系统的密码认证策略、账户锁定策略这是很多运维踩的坑--with-md5-passwords兼容旧系统的MD5密码哈希--mandirman手册页安装到系统默认位置--sysconfdir/etc/ssh这一点我特别强调一下。如果保留默认的--prefix下的目录新版本sshd会使用新的配置目录而系统原来的sshd配置比如PermitRootLogin、PasswordAuthentication这些不会被加载。我把配置目录直接指到/etc/ssh新版本sshd会读取原有配置同时用新的配置模板覆盖部分缺失项。configure完成后编译安装make -j4 make install编译过程可能会报openssl/xxx.h: No such file or directory这是OpenSSL头文件路径没被找到。检查configure时--with-ssl-dir参数是否正确或者确认OpenSSL是否成功安装到指定目录。3.4 配置与启动sshd服务新版OpenSSH安装完成后需要处理几个关键配置和服务管理文件。用/usr/local/openssh下的新版服务程序替换系统默认的sshd程序# 停止系统自带sshd服务 systemctl stop sshd # 替换二进制文件 cp /usr/sbin/sshd /usr/sbin/sshd.bak.$(date %Y%m%d) cp /usr/local/openssh/sbin/sshd /usr/sbin/sshd # 替换客户端工具 cp /usr/bin/ssh /usr/bin/ssh.bak.$(date %Y%m%d) cp /usr/local/openssh/bin/ssh /usr/bin/ssh cp /usr/local/openssh/bin/scp /usr/bin/scp cp /usr/local/openssh/bin/sftp /usr/bin/sftp这样做的目的是让systemctl restart sshd时依然通过原有的systemd服务单元启动新版程序后续管理方式不变。然后校验配置并重启服务# 校验sshd配置语法 /usr/sbin/sshd -t # 如果输出类似 /etc/ssh/sshd_config line 25: Bad configuration option 的报错 # 说明旧配置里有新版已移除的选项需要注释掉 # 重启sshd systemctl restart sshd # 查看服务状态 systemctl status sshd关键配置项检查# /etc/ssh/sshd_config 核心配置确认 Port 22 PermitRootLogin yes # 如果运维需要root远程登录保持yes但建议配合密钥认证 PasswordAuthentication yes # 升级期间先保持yes避免密码登录被卡 PubkeyAuthentication yes # 允许密钥登录 UsePAM yes # 必须为yes否则PAM认证不生效配置/usr/local/openssh/etc下的PAM文件# OpenSSH 9.6 安装时会生成/etc/pam.d/sshd的模板 # 确认系统原有的PAM配置还在没有被覆盖 cp /etc/pam.d/sshd /etc/pam.d/sshd.bak.$(date %Y%m%d) cp /usr/local/openssh/etc/sshd.pam /etc/pam.d/sshd不过这个替换不建议直接做因为系统原版的PAM配置包含了pam_localuser、pam_succeed_if等规则直接替换可能会影响本地账号登录。我建议保留系统原版的/etc/pam.d/sshd只用它来做PAM认证OpenSSH自带的模板仅做参考。设置SELinux如果系统启用了# 查看SELinux状态 getenforce # 如果是Enforcing需要放行sshd相关端口和连接 # 新版本sshd二进制文件的SELinux上下文可能不对恢复一下 restorecon -Rv /usr/sbin/sshd /usr/bin/ssh重启后如果SELinux拦截了sshd日志里能看到SELinux is preventing /usr/sbin/sshd from name_connect access on the tcp_socket port 22。处理方法是用audit2why或直接搜索/var/log/audit/audit.log里的相关记录然后设置布尔值# 允许sshd访问网络 setsebool -P ssh_sysadm_login on接着处理systemd服务配置让sshd服务可以管理新版二进制# 查看原有的sshd服务定义 systemctl cat sshd # ExecStart/usr/sbin/sshd -D $OPTIONS # 我们已经替换了/usr/sbin/sshd所以服务定义不用改最后确认sshd监听正常netstat -tlnp | grep :22 ss -tlnp | grep :22 # 应该看到sshd监听在0.0.0.0:223.5 升级后的验证步骤升级完成后不要急着收工验证环节必须做足。先查版本号ssh -V 21 # 输出OpenSSH_9.6p1, OpenSSL 1.1.1w 11 Sep 2023然后从客户端测试连接。这里要特别注意升级后新版本默认禁用了ssh-rsa算法如果你的客户端工具比较老比如Windows自带的ssh还是老版本连接时可能报Unable to negotiate with ...: no matching host key type found。解决办法是临时在服务端/etc/ssh/sshd_config里加一行允许老算法HostKeyAlgorithms ssh-rsa PubkeyAcceptedAlgorithms ssh-rsa重启sshd后老客户端就能连了。但这个属于妥协方案安全巡检时这个配置会拉低评分建议在用密钥认证逐步替换老客户端后移除。还要验证密钥登录是否正常ssh -i ~/.ssh/id_rsa userserver以及配置文件关键项是否生效sshd -T | grep -E permitrootlogin|passwordauthentication|usepam如果之前开通了telnet保底通道认证ssh连接一切正常后关闭telnetsystemctl stop xinetd systemctl disable xinetd firewall-cmd --permanent --remove-port23/tcp firewall-cmd --reload至此OpenSSH离线升级主流程完成。4. 常见问题与排查技巧实录实际升级过程中我遇到的问题比预设的要多得多。下面这些是我在多次操作中积累的经验按问题类型分类整理每个都有背景和解决过程。4.1 configure阶段报错与处理报错checking for openSSL... no这是最经典的报错。原因多半是configure没找到OpenSSL头文件或库文件。检查--with-ssl-dir的路径是否正确确认 OpenSSL 是否真的装到了那个目录ls /usr/local/openssl/include/openssl/ssl.h ls /usr/local/openssl/lib/libssl.so如果文件不存在说明OpenSSL编译安装失败回头重新做OpenSSL那一步。还有个小技巧configure时加上CFLAGS-I/usr/local/openssl/include LDFLAGS-L/usr/local/openssl/lib能提高成功率。报错configure: error: *** zlib.h missing原因是zlib开发库没找到。用--with-zlib/usr/local/zlib指定路径或者加上CPPFLAGS和LDFLAGS环境变量。报错configure: error: *** Cannot find PAM headers缺PAM开发库。CentOS 7.9系统里通常有pam-devel如果确认没有需要从ISO里找pam-devel包补装。4.2 升级后登录不了的排查思路升级后ssh端口连不上是最吓人的情况。我在自己测试机器上就翻过一次车好在有telnet保底。排查步骤记好按顺序来确认服务状态systemctl status sshd看是否active (running)看监听端口ss -tlnp | grep 22确认sshd确实在监听看防火墙firewall-cmd --list-all检查22端口是否被放行还要注意有没有装iptables两个防火墙可能同时存在看SELinuxgetenforce如果是Enforcing参考上面restorecon和布尔值处理看日志journalctl -u sshd -n 50tail -50 /var/log/secure其中/var/log/secure里的记录最有价值。比如常见的Jul 11 02:34:17 server sshd[12345]: fatal: Cannot bind any address这是端口被占用或者监听地址无效。可能是原来还有别的sshd实例在跑先pkill -9 sshd清理再重启。如果登录时提示Connection closed by remote host多半是算法协商失败按上文的方法打开HostKeyAlgorithms ssh-rsa或PubkeyAcceptedAlgorithms ssh-rsa。还有个容易忽略的点——sshd_config里的AllowUsers或AllowGroups。如果配置了白名单而白名单里没有当前登录用户就会直接拒绝连接。升级前备份的配置文件里如果有这类限制要检查是否要保留。4.3 服务启动失败与日志分析编译安装后第一次启动sshd失败多数原因集中在四个方面配置语法错误、二进制库依赖缺失、权限上下文错误、PID文件冲突。配置语法错误/usr/sbin/sshd -t会直接告诉你哪行配置写错了。常见的是旧版配置选项在新版已废弃比如UsePrivilegeSeparation在9.6里被移除了Protocol 2也默认不需要写了。处理方式就是注释掉。库依赖缺失新编译的sshd二进制可能找不到libcrypto.so之类的库。/usr/sbin/sshd -t # 如果输出error while loading shared libraries: libcrypto.so.1.1: cannot open shared object file解决办法是让系统动态库加载器找到新库的位置echo /usr/local/openssl/lib /etc/ld.so.conf.d/openssl-1.1.1.conf ldconfig /usr/sbin/sshd -t权限上下文错误如果升级后的sshd被SELinux拦截日志里会出现SELinux is preventing /usr/sbin/sshd from write access on the file /var/run/sshd。恢复上下文restorecon -Rv /var/run /usr/sbin/sshdPID文件冲突如果原来sshd的PID文件还在新进程可能无法创建报sshd: fatal: Cannot open PID file /var/run/sshd.pid as mode 0644。删掉旧PID文件重启即可rm -f /var/run/sshd.pid systemctl restart sshd4.4 独家避坑经验总结最后整理几条我在多次实操中总结出的经验这些都是踩过坑才记住的第一永远保留一份旧版sshd二进制副本。我在替换/usr/sbin/sshd前一定会备份到/usr/sbin/sshd.bak.yyyymmdd。万一新版程序有问题可以秒级回滚。同时也要备份/etc/ssh/sshd_config、/etc/pam.d/sshd这些关键配置文件。第二编译参数里的--with-pam一定不能省。我第一次升级时觉得系统密码认证够用了没加PAM结果系统账号的密码策略、fail2ban、账户锁定全部失效。运维登录审计发现密码错误次数限制不生效这个坑相当深。第三升级完了别急着关telnet先重启一次系统验证自动启动。我之前在一台机器上验证了ssh连接正常就关了telnet结果后面机器重启发现sshd服务没起来因为pid文件和xxx机制的锅人又不在现场只能让机房的人帮忙做console操作。第四/etc/ssh目录下sshd_config的文件权限要正确。OpenSSH对配置目录权限敏感sshd_config不能对group和其他用户可写否则启动时会报Bad permissions。如果从别的机器拷贝了配置文件过来记得chmod 600 /etc/ssh/sshd_configchmod 700 /etc/ssh。第五升级完成后检查一下sshd的依赖库引用。用ldd /usr/sbin/sshd检查是否有 not found 的依赖。我之前就因为OpenSSL的libssl.so版本不对导致新sshd运行时使用的是系统旧库虽然能启动但部分加密算法不可用。正确的依赖应该是libcrypto.so.1.1 /usr/local/openssl/lib/libcrypto.so.1.1 libssl.so.1.1 /usr/local/openssl/lib/libssl.so.1.1 libz.so.1 /usr/local/zlib/lib/libz.so.1如果ldd显示的是/lib64/libcrypto.so.10说明rpath没生效需要重新编译或者在编译时用LDFLAGS显式指定rpath。第六关于升级时机的选择。我一般会把操作拆成两个窗口第一个窗口做环境准备装编译工具、开通telnet、传包这个可以在工作日的低峰期完成第二个窗口才做正式的编译和替换这个必须放在停机窗口内。为什么因为编译期间CPU占用高可能影响线上业务而且configure阶段不停止服务不会影响现有连接完全可以在低峰期把这些前期工作做掉。个人经验是OpenSSH升级本身不算复杂真正考验人的是在极端情况下的应变能力。把保底通道准备好把备份做足把每个步骤的验证做细基本不会出大问题。如果后续想降低手工操作的风险可以把这个流程写成脚本把版本检查、依赖清理、编译安装、配置替换、服务重启、自检这几个环节串起来脚本化之后运维效率会高很多。