在做服务器安全整改的时候,“OpenSSH版本过旧”几乎是每台内网服务器都逃不掉的整改项。麒麟系统在国产化环境里用得很多,但默认自带的OpenSSH版本普遍停留在7.x左右,安全扫描工具随便跑一遍就是一堆CVE,而且新版客户端和服务端之间的算法协商也经常因为版本太老而出问题。前阵子我负责的一台银河麒麟V10服务器正好赶上这趟整改,需要把系统自带的OpenSSH升级到9.8p1。整个流程走下来,从环境准备到编译安装,再到踩坑排查,花了大半天时间。这篇文章把完整过程整理成一份可复用的操作记录,给正在处理同类任务的运维同行做个参考。
1. 升级前的风险评估与环境确认
1.1 为什么偏偏要动OpenSSH
先说说这次升级的直接原因。安全扫描报告上是满满一页OpenSSH相关的高危漏洞清单,尤其是2024年公开的regreSSHion漏洞(CVE-2024-6387),影响范围覆盖了OpenSSH 8.5p1到9.7p1之间的很多版本,修复这个漏洞的版本正好是9.8p1。这也是为什么很多人一上来就指定要升级到9.8p1,而不是随便装一个新版就完事。
另外还有一层原因来自合规侧。很多等保整改和内部安全基线会明确要求SSH相关组件的版本必须满足某一阈值,而麒麟系统出厂自带的OpenSSH在这类检查面前基本不合格。再加上新版OpenSSH在算法层面做了不少增强,比如支持更新的密钥交换算法,老版本客户端在连接一些较新的服务器时,偶尔会报出no matching key exchange method之类的算法协商失败问题。无论是从安全角度还是日常使用角度,升级OpenSSH都是一件躲不开的事。
1.2 动手前先摸清这五件事
第一次在麒麟系统上做这种升级,我差点栽在一个很基础的环节上。所以这里强烈建议,任何操作之前先花十分钟把下面五件事确认清楚。
第一,系统版本和CPU架构。执行cat /etc/os-release和uname -m,确认是银河麒麟V10的哪个版本,架构是x86_64还是aarch64。这个直接决定了后面准备源码包时要注意什么,虽然OpenSSH本身跨平台,但openssl、zlib这些组件在编译时是纯C源码还好,如果是拿二进制RPM包,架构选错就直接装不上。
第二,当前OpenSSH的版本。ssh -V这条命令会输出类似OpenSSH_7.4p1, OpenSSL 1.1.1k的信息。记下这个版本号,升级前后做个对比,也方便确认你到底跨了几个大版本。
第三,有没有备用通道。这句话是重点中的重点:如果你不在机房,而手头又没有带外管理或者虚拟机快照,那升级前必须先准备一条后路,否则升级过程中一旦sshd出了任何问题,你连服务器都登不进去,那就只能求人了。后面我会单独讲怎么快速开一个临时通道。
第四,包管理器是apt还是yum。麒麟V10的桌面版和服务器版底层体系不完全一样,有的基于Debian,有的基于CentOS,导致包管理器不一样。这个决定了你在准备编译依赖时用apt-get install还是yum install,命令差别很大。
第五,编译工具链是否齐全。OpenSSH用源码编译,gcc、make、openssl头文件、zlib头文件这些是硬性前提。如果系统里连gcc都没有,得先确认软件源可用,不然准备工作就得从装gcc开始。
2. 依赖梳理与离线安装包准备
2.1 OpenSSH升级真正麻烦的是依赖链
很多人以为OpenSSH是一个孤零零的软件,解压、编译、安装三步走就够了,实际远没这么简单。它编译时会动态链接zlib做数据压缩,链接openssl做加密和密钥协商,还要通过PAM做账号认证。这就像做菜,光有主料不够,油盐酱醋都得备齐,不然下锅的时候才发现缺这缺那,火候全耽误了。
对麒麟这种国产化系统来说,依赖链的问题还会被放大。老版本OpenSSH对新系统的适配还好说,反而是新版OpenSSH对系统自带openssl库的版本特别敏感。如果系统里是OpenSSL 1.0.2那一代的老库,直接编译新版OpenSSH大概率会卡在configure阶段,报错信息基本是Can't find recent OpenSSL libcrypto。所以升级OpenSSH前,先把zlib、openssl、pam这三条依赖链的版本列出来看一眼,做到心里有数。
2.2 离线环境下把工具链和源码一次备齐
大多数内网环境的麒麟系统是不能随便访问公网的,所以离线预置这一步特别关键。我在这次操作前的做法是,先找一台可以联网的机器把下面的工具包全部准备好,再拷贝到内网。
如果目标机器本身有软件源或能访问内网Yum/Apt仓库,直接先装编译依赖:
# yum系(部分麒麟服务器版) yum install -y gcc make zlib-devel openssl-devel pam-devel # apt系(部分麒麟桌面版) apt-get install -y build-essential zlib1g-dev libssl-dev libpam0g-dev接着准备源码包。OpenSSH 9.8p1的源码可以去OpenSSH官网下载,另外建议把zlib-1.2.13.tar.gz一并带上,以防目标机器zlib版本过低。放在/usr/local/src目录下备用。
如果目标机器连内网软件源都没有,那就只能靠离线RPM/Deb包了。在能联网的同版本系统上,用yumdownloader或repotrack把openssh相关的软件包连依赖一起拉下来:
yum install -y yum-utils mkdir /root/openssh-rpms repotrack openssh openssh-server openssh-clients -p /root/openssh-rpms然后把整个目录拷到内网机器,用rpm -Uvh *.rpm或dpkg -i *.deb安装。但说实话,在追求指定版本和可控性的场景里,源码编译依然是更稳的路径。
2.3 为什么不建议顺手把OpenSSL也升级
这里要说一个经验之谈。看到新版OpenSSH对openssl版本有要求,很多人的第一反应是先把openssl升级了,觉得底层库越新越好。除非系统自带的openssl确实低于1.1.1,否则千万不要顺手把openssl一起升级。
原因很简单:openssl是系统底层组件,curl、nginx、很多数据库驱动、各种服务全都动态链接着它,单独把它换成一个新版本,很可能导致其它服务在运行时报libssl.so.3: cannot open shared object file之类的错误。严重情况下,yum/apt命令本身都会挂掉,那才是真正的灾难。
这次我检查了一下麒麟V10自带的openssl版本,是1.1.1系列,完全满足OpenSSH 9.8p1的编译要求,所以我没有动系统openssl,直接跳过升级,减少了一个极大的风险变量。如果你检查后发现确实老得离谱,也建议把新版openssl单独编译到/usr/local/openssl,不要覆盖系统的,然后编译OpenSSH的时候用--with-ssl-dir=/usr/local/openssl指定路径。
3. 核心操作:编译安装OpenSSH 9.8p1
3.1 先开一个telnet备用通道,防止被锁在门外
升级sshd这种服务,最忌讳的就是直接开干。我处理这件事的第一步,不是解压源码,而是先给服务器开一个telnet备用通道。telnet虽然是明文协议,平时绝对不推荐使用,但作为升级窗口期内的临时保命通道,它是成本最低、见效最快的手段。
麒麟系统上临时启用telnet很简单:
yum install -y telnet-server telnet systemctl start telnet.socket systemctl enable telnet.socket然后确认23端口放行。很多内网环境还有firewalld或iptables,需要临时放行:
firewall-cmd --zone=public --add-port=23/tcp当然前提是你能搞定telnet登录后的账号认证,root登录还要检查/etc/securetty里有没有pts/0等终端配置。等OpenSSH升级完成、SSH验证通过后,记得立即关掉这个通道:
systemctl stop telnet.socket systemctl disable telnet.socket这个备用通道看起来很简单,但关键时刻真的能救命。有一次我远程升级一台服务器,sshd配置测试没通过,服务起不来,要不是提前开着telnet,就只能买机票去机房了。
3.2 编译安装前的准备与依赖安装
在确定系统信息、依赖版本都OK之后,我把准备好的源码包传到服务器上,开始正式操作。
cd /usr/local/src tar -zxvf openssh-9.8p1.tar.gz cd openssh-9.8p1编译之前还要做两件事。第一,确认存在sshd系统用户,如果不存在则手动创建:
id sshd || useradd -r -s /sbin/nologin sshd第二,创建OpenSSH运行时需要的特权分离目录:
mkdir -p /var/empty/sshd chmod 755 /var/empty/sshd chown root:root /var/empty/sshd这个目录是OpenSSH做privilege separation用的,很多编译安装失败或启动报错都跟它有关系,提前建好能省不少事。
3.3 编译参数与安装命令详解
然后就是编译参数。这一步我建议别直接用./configure默认参数,要结合麒麟系统的特点来。我用的命令如下:
./configure \ --prefix=/usr/local/openssh \ --sysconfdir=/etc/ssh \ --with-pam \ --with-md5-passwords \ --with-privsep-path=/var/empty/sshd \ --without-openssl-header-check每个参数都有它的用意:
--prefix=/usr/local/openssh是指定安装目录。不要直接用默认的/usr/local,更不要直接覆盖系统自带的openssh目录,这样万一出问题,原系统文件基本没被破坏,回滚空间更大。--sysconfdir=/etc/ssh是让新版本继续读取原有配置,这样可以沿用在/etc/ssh里的主机密钥和配置习惯,不用把配置搬到新路径,省了后续一堆麻烦。--with-pam这个是必须的,不启用PAM的话,系统账号的密码登录基本上会失灵。--with-md5-passwords是为了兼容老系统里还在使用MD5散列的账号密码,加上基本没坏处。--with-privsep-path是指定特权分离目录路径,对应刚才创建的/var/empty/sshd。--without-openssl-header-check是在确认系统openssl库可用但头文件版本检查过严时使用的,跳过configure阶段对openssl头文件版本的强校验。
确认没有报错后,执行编译和安装:
make -j$(nproc) make install关于编译过程,我想多说一句。如果报zlib.h: No such file or directory,说明没装zlib-devel;如果报openssl/opensslv.h: No such file or directory,说明没装openssl-devel。这些都是依赖缺失,补齐再重新configure即可,不要硬着头皮往下走。
3.4 替换二进制、建立软链与调整服务
编译安装完成之后,最坑的一步来了。很多人以为make install结束就大功告成了,其实新二进制还躺在/usr/local/openssh下面,系统的sshd还是旧版本。这时候如果不做替换,直接重启sshd服务,系统会继续用旧二进制,看起来升级成功了,实际上是白忙一场。
先备份原文件,避免出问题无法回滚:
for f in /usr/sbin/sshd /usr/bin/ssh /usr/bin/scp /usr/bin/sftp; do [ -f $f ] && cp $f $f.bak.$(date +%Y%m%d) done然后建立软链,指向新版本:
ln -sf /usr/local/openssh/sbin/sshd /usr/sbin/sshd ln -sf /usr/local/openssh/bin/ssh /usr/bin/ssh ln -sf /usr/local/openssh/bin/scp /usr/bin/scp ln -sf /usr/local/openssh/bin/sftp /usr/bin/sftp这样做的好处是,原有的systemd服务单元文件里写的是ExecStart=/usr/sbin/sshd,我们用软链覆盖后,服务启动时会自然加载新二进制,不需要大改sshd.service文件。如果发现你的服务文件写的是其它路径,还得去/usr/lib/systemd/system/sshd.service里把ExecStart改成实际路径。
接下来生成主机密钥,除非你确定保留原密钥而原目录里已有,否则执行一下:
ssh-keygen -A然后测试配置是否正确:
/usr/sbin/sshd -t没有输出就代表配置没问题。如果报Permissions xxx are too open这类错误,通常是/etc/ssh下的密钥文件权限不对,私钥要600,公钥要644,用chmod修正就行。
3.5 验证与回归:不要着急重启系统
升级完成的验证顺序非常关键。我刚升级完那会儿,心里还挺着急想看看效果,但克制住了冲动作了系统性的验证。
先在服务器本机测试连接:
ssh -v localhost用-v模式可以看到完整的调试输出,如果认证、算法协商、会话建立都能跑通,说明基础功能正常。然后确认版本:
ssh -V输出已经变成OpenSSH_9.8p1,说明二进制替换成功。
接着检查监听端口是否正常:
ss -lntp | grep 22确认sshd在监听22端口。最后再找一台远程机器,用实际业务账号试登录一次,确认远程场景也没问题。
整个验证通过之后,还要专门提醒一句:升级后不要马上重启系统。我见过有人升级完顺手一条reboot下去,结果sshd服务因为某个依赖问题没起来,整台服务器只能靠物理控制台去抢救。正确做法是先观察一段时间,确认一切运行正常,再安排后续的维护窗口重启。哪怕要重启,也建议先手动执行一次systemctl restart sshd,确认服务能被系统正常拉起再说。
4. 常见问题与排查技巧实录
4.1 动态库加载失败的两种典型场景
编译安装最大的坑往往不在编译,而在运行时。比如执行sshd时直接报:
sshd: error while loading shared libraries: libcrypto.so.1.1: cannot open shared object file这就是典型的动态库路径问题。原因要么是系统里原来的openssl库路径不在默认搜索路径中,要么是二进制依赖的库版本和系统里实际存在的库不匹配。
排查方法很直接:
ldconfig -p | grep libcrypto看看系统能不能找到依赖库。如果找不到,确认/usr/local/openssl/lib或系统原有openssl的lib目录下有没有对应的.so文件,有的话就把它加入动态库配置:
echo "/usr/local/openssl/lib" > /etc/ld.so.conf.d/openssl.conf ldconfig还有另一种情况,系统里同时存在多个openssl版本,sshd链接到了不存在的那个。这种时候可以用ldd /usr/local/openssh/sbin/sshd查看它实际依赖的库路径,再对症处理。
4.2 sshd服务起不来的高频原因
升级后最让人紧张的时刻就是执行systemctl restart sshd后服务没有如预期启动。根据我的经验,出现这类问题最常见的原因不外乎下面几个:
| 现象 | 可能原因 | 处理方法 |
|---|---|---|
| 报错 Missing privilege separation directory: /var/empty/sshd | 特权分离目录不存在或权限不对 | mkdir -p /var/empty/sshd && chmod 755 /var/empty/sshd |
| 报错 Privilege separation user sshd does not exist | sshd用户缺失 | useradd -r -s /sbin/nologin sshd |
| 报错 /etc/ssh/sshd_config: Bad configuration options | 配置文件里有新老版本不兼容的参数 | 用备份的旧配置逐项比对,删掉已废弃的选项 |
| 服务起不来但无明确报错 | systemd单元文件路径还是旧的 | 检查ExecStart路径,执行systemctl daemon-reload |
| 端口被占用 | 旧sshd进程还在占用22端口 | 先确认并停掉旧进程再重启服务 |
碰到服务起不来,先看journalctl -u sshd或/var/log/messages里的日志,报错信息通常已经把答案写出来了,别一上来就重新编译。
4.3 root无法登录/密码认证失败的排查
还有一种高频问题是服务起来了,但登录不进去,要么root直接被拒,要么密码怎么输都不对。
root被拒通常是sshd_config里PermitRootLogin的默认值问题。新版OpenSSH默认改成prohibit-password,也就是禁止root密码登录,只允许密钥登录。如果业务上有root密码登录需求,需要在/etc/ssh/sshd_config里明确设置为:
PermitRootLogin yes PasswordAuthentication yes改完记得systemctl restart sshd。
密码认证失败还有一个隐蔽原因是/etc/pam.d/sshd文件缺失。编译安装时,如果pam相关模块没有正确配置,sshd无法通过PAM的认证链路,表现就是密码输入正确但登录还是失败。解决办法是从备份中恢复旧版自带的PAM配置,或者找一个同版本系统拷贝一份过来。
如果系统开了SELinux,升级后还要注意安全上下文。很多编译安装的文件SELinux标签是错的,需要执行:
restorecon -Rv /etc/ssh /usr/local/openssh不然即使权限配置全对,SELinux也会把sshd挡在外面。
4.4 升级后的回滚方案
升级前我特意把所有原始文件做了备份,就是为了给回滚留条后路。万一升级后出现不可控的问题,可以快速回到原来的状态。
回滚操作很简单:
# 恢复原始二进制 cp /usr/sbin/sshd.bak.20240101 /usr/sbin/sshd cp /usr/bin/ssh.bak.20240101 /usr/bin/ssh # 重启服务 systemctl restart sshd如果连配置文件都改过,同样把/etc/ssh/sshd_config的备份覆盖回去。另外提醒一点,升级时下载的rpm包、源码包不要急着删,如果回滚到旧版本后发现驱动或依赖有异常,还能再拿这些包出来分析。
4.5 避坑清单速查表
最后整理一份升级OpenSSH的避坑速查表,这是我反复踩坑后总结出来的:
| 检查项 | 容易踩的坑 | 正确做法 |
|---|---|---|
| 备用通道 | 直接开干,升级失败无法远程 | 先开telnet备用,确保有物理控制台或快照 |
| 依赖判断 | 盲目升级系统openssl | 只在低于1.1.1时才处理,否则不动底层库 |
| 二进制替换 | make install后不建软链 | ln -sf把新二进制链到/usr/sbin和/usr/bin |
| 配置文件 | 直接用默认配置 | 沿用/etc/ssh原配置,增量修改 |
| 服务文件 | 忘记daemon-reload | 修改unit后执行systemctl daemon-reload |
| 权限问题 | 密钥文件权限过宽 | 私钥600,公钥644,目录755 |
| 回归验证 | 升级完立刻重启 | 先本机连接+远程连接验证,观察稳定后再安排重启 |
| PAM配置 | 删掉或覆盖/etc/pam.d/sshd | 确认PAM文件存在,否则密码认证必然失败 |
这次麒麟系统升级OpenSSH处理下来,最大的感受就是操作本身不复杂,难的是在离线、远程、高风险的环境里把每一步细节都做扎实。特别是二进制替换和服务管理这两块,很多人栽了跟头还不知道问题出在哪里。希望这份记录能帮你少走弯路。如果你也在折腾类似的服务器升级,建议先把上面的检查清单过一遍再动手,遇到具体问题欢迎在评论区交流。