简介:面向Rocky Linux 9.5运维人员的OpenSSH升级加固包,针对旧版SSH服务的高危漏洞,提供升级至10.0p1并配套OpenSSL 3.5.0的自动化方案,适配等保合规及内网安全加固需求。资源共6个文件,含5个RPM包与1个shell脚本,约10.96MB;RPM包覆盖核心组件与服务依赖,脚本负责串联安装和配置。整个升级过程无需源码编译,脚本自动处理依赖顺序、旧配置备份与sshd重启校验,避免因手工操作导致远程连接中断。目前已有205人学习下载。读者可获得一套可直接落地的加固模板,既适合单机应急修复,也便于批量部署和作为安全基线参考,并可借此掌握RPM方式升级SSH的关键流程与注意事项。
1. 先认清一个现实:OpenSSH 老旧版本,是每台 Linux 机器迟早要还的债
作为一线运维,我判断一台 Linux 服务器是否“干净”,第一件事就是敲ssh -V。Rocky Linux 9.5 自带的 OpenSSH 版本偏旧,而很多安全扫描工具(比如各类漏洞评估系统)对 SSH 服务端的版本号极其敏感,一旦爆出高危 CVE,安全团队就会发来整改工单。最近接手的这台 x86_64 架构服务器就是典型场景:业务方要求必须完成 openssh 加固,且服务不能中断超过一个维护窗口。与其手工下载源码包编译,不如直接用现成的 rockylinux9.5-ssh10.0p1-ssl3.5.0-rpm-x86-64 升级加固脚本,把 SSH 升级到 OpenSSH 10.0p1、OpenSSL 3.5.0,再批量套用加固基线。这篇笔记就围绕这个脚本,讲清楚它到底帮你做了什么、你拿到手后怎么用,以及我在实际加固过程中踩过的那些坑。
2. 动手前的分析与选型:不要一上来就跑脚本,先算清楚风险账
2.1 系统环境摸底:版本、架构、依赖的三重确认
任何升级脚本都不是凭空运行的,第一步永远是确认目标机器环境与脚本预期是否匹配。我拿到脚本后的第一件事,不是解压执行,而是先做环境采集,最少要确认三个维度:操作系统大版本、架构、当前 SSH/SSL 版本。
# 确认操作系统版本与架构 cat /etc/os-release | grep -E "^(NAME|VERSION)=" uname -m # 确认当前 OpenSSH 与 OpenSSL 版本 ssh -V 2>&1 openssl version -a这段命令输出的核心价值在于“对齐预期”。脚本名称里写明了 rockylinux9.5、x86_64、ssh10.0p1、ssl3.5.0,如果你的系统是 CentOS Stream 或者 AlmaLinux,RPM 依赖可能能装上,但os-release里的标识、/etc/redhat-release的版本判断逻辑可能会让脚本直接退出。uname -m也必须确认是 x86_64,aarch64 架构的机器千万不要尝试,RPM 包是为特定架构编译的,强行安装只会得到wrong ELF class之类的报错。当前 SSH/SSL 版本则用于判断升级跨度:比如从 OpenSSH_8.7p1 跳到 10.0p1,中间跨了多个大版本,配置文件兼容性需要格外留意。
版本确认完之后,还要看一个容易被忽略的东西:系统里是否装了第三方安全软件或 SELinux 强制模式。常见做法是将 SELinux 先临时设为 permissive 再升级,脚本执行完后立刻恢复 enforcing,否则 sshd 的监听端口、进程行为都可能被 SELinux 策略拦截,表现为升级完成后 SSH 服务起不来。
2.2 选型对比:为什么选 RPM 包而不是源码编译
在 Linux 上安装新版本 OpenSSH 无外乎三条路:发行版官方仓库(版本太旧)、源码编译(可控但麻烦)、第三方 RPM 包(直接安装)。这三个方案我用表格做一个横向对比,方便你根据自己的环境决定是否继续往下走:
| 方案 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| 系统仓库安装 | 依赖自动处理、卸载干净 | 版本往往落后一到两年 | 只求安全补丁,不求大版本升级 |
| 源码编译安装 | 版本任意、编译参数可控 | 依赖库冲突多、卸载麻烦、需要编译环境 | 离线环境、特殊编译参数需求 |
| 第三方 RPM 包 | 安装快、依赖打包完整、便于卸载 | 必须信任打包方、架构匹配强 | 内网环境、批量升级、等保加固 |
我给出的建议非常明确:能做 RPM 就不要走编译。源码编译 OpenSSH 有一个非常头疼的问题——它和 OpenSSL 的版本耦合很深,编译时必须指定--with-ssl-dir,而系统自带的 OpenSSL 3.0.x 和脚本要用的 OpenSSL 3.5.0 之间存在 ABI 差异。源码编译时如果系统里存在多个 OpenSSL 版本,ldd /usr/local/sbin/sshd的结果会很混乱,有时 sshd 能起来,但ssh -Q cipher列出的算法集跟你预期的完全不同,这类黑匣子问题排查起来极其消耗精力。RPM 包的好处是依赖关系被打包者提前处理好了,只要源配置正确、事务顺序正确,安装完成后系统整体仍处于一致状态。
选型确定后,脚本执行的思路就清晰了:先装 OpenSSL 3.5.0(因为 OpenSSH 10.0p1 依赖它),再装 OpenSSH 10.0p1,最后做配置加固与重启验证。顺序错了,依赖解析就会失败,RPM 事务会直接中止。
2.3 脚本功能边界:它能做什么、不能做什么
我习惯在动手前把脚本的“功能边界”划清楚,否则容易误判。这类升级加固脚本通常包含四件事:备份旧文件、安装新 RPM、迁移配置、加固 sshd 配置。
# 查看解压后的目录结构 tree -L 2 rockssh/常见的目录结构是:RPMs/下放所有 rpm 文件,conf/下放 sshd_config 加固模板,scripts/下放升级与回滚脚本。使用前先明确边界——脚本负责“升级与基础加固”,不负责“业务侧改造”。比如你的应用通过 SSH 隧道连接数据库,升级后默认禁用TCPForwarding会让业务直接中断;你在跑自动化运维平台,脚本重启 sshd 时所有基于 SSH 的批处理任务都会断开会话。这些属于业务适配,脚本是无法替你判断的。
3. 升级加固脚本拆解:从备份到切换的完整执行路径
3.1 备份:升级前必须有一粒后悔药
所有升级工作开始之前,备份是不可跳过的步骤。很多人以为 RPM 升级失败了大不了yum downgrade,实际操作里你会发现,RPM 数据库一旦在升级中途出现事务一致性错误,降级也会失败。所以脚本第一步通常是创建一个带时间戳的备份目录,把当前配置与二进制全部拷走。
#!/bin/bash # 备份目录:/root/backup/openssh_backup_$(date +%Y%m%d_%H%M%S) BK_DIR="/root/backup/openssh_backup_$(date +%Y%m%d_%H%M%S)" mkdir -p "$BK_DIR" # 备份关键配置和二进制 cp -a /etc/ssh/sshd_config "$BK_DIR/" 2>/dev/null cp -a /etc/ssh/ssh_config "$BK_DIR/" 2>/dev/null cp -a /etc/pam.d/sshd "$BK_DIR/" 2>/dev/null cp -a /usr/sbin/sshd "$BK_DIR/sshd.bak" 2>/dev/null cp -a /usr/bin/ssh "$BK_DIR/ssh.bak" 2>/dev/null # 导出当前 RPM 包清单,便于回滚 rpm -qa | grep -E "openssh|openssl" > "$BK_DIR/rpm_list.txt" # 记录版本哈希 md5sum /usr/sbin/sshd /usr/bin/ssh /etc/ssh/sshd_config >> "$BK_DIR/hash_before.txt"这段逻辑里有一个细节值得注意:备份不仅备份了文件,还要记录hash_before.txt。为什么?升级后如果你需要确认“二进制到底换没换”,单看版本号容易被伪装,直接对比 md5 是最可靠的。升级完成后,拿新版本文件的 md5 和这个备份清单比对,就知道哪些文件被替换了、哪些还是原样。出现问题时,rpm_list.txt也能让你快速识别依赖关系是否被破坏。
备份完成后,还有一个操作是很多人会漏掉的——检查是否有进程正在占用旧版本的 sshd 动态库。用lsof | grep sshd可以列出各远程会话对应的二进制路径,如果一个用户通过 SSH 连接登录了很长时间,他的会话可能仍然映射到旧版 sshd。此时直接替换二进制通常没问题,因为 Linux 允许删除正在使用的文件,但你要知道这些旧进程会一直存活到会话断开,期间systemctl restart sshd不会影响已建立的连接,会造成“明明升级了,怎么还有人连在旧进程上”的错觉。
3.2 RPM 依赖处理与安装顺序:让 RPM 事务一次通过
RPM 安装的依赖解析,是整个升级过程中最容易翻车的一步。脚本通常会先安装新版 OpenSSL,再安装 OpenSSH 相关组件,原因在于 OpenSSH 10.0p1 的二进制对 OpenSSL 3.5.0 的符号表有硬性依赖,如果先装 OpenSSH,ldconfig 缓存里找不到新版本 OpenSSL,就会直接报libcrypto.so.3相关错误。
# 进入 RPM 包目录 cd rockssh/RPMs # 先安装 OpenSSL 相关包 rpm -Uvh --nodeps openssl-3.5.0-*.rpm openssl-libs-3.5.0-*.rpm openssl-devel-3.5.0-*.rpm 2>&1 | tee /tmp/openssl_install.log # 刷新动态链接库缓存 ldconfig # 再安装 OpenSSH 相关包 rpm -Uvh --nodeps openssh-10.0p1-*.rpm openssh-server-10.0p1-*.rpm openssh-clients-10.0p1-*.rpm 2>&1 | tee /tmp/openssh_install.log这里用--nodeps是实际环境里经常需要的,原因值得解释一下:脚本会把所有 OpenSSH 包一起安装,但系统里可能存在旧版本的依赖冲突,比如openssh-clients依赖的libedit版本旧于当前系统提供的版本,RPM 数据库里记录的依赖关系未更新,此时--nodeps可以绕过元数据层面的检查,强行完成文件层面的替换。
--nodeps并不是无脑使用,有两个前置条件:第一,你确认过包内容完整、来源可靠;第二,安装完成后必须立即执行rpm -Va校验文件一致性。常见的错误做法是拿到脚本就整段复制,跳过ldconfig,导致 sshd 起不来。ldconfig的作用是更新动态链接库缓存,OpenSSL 安装后新库文件被放在了/usr/lib64,但不刷新缓存,运行时仍会找到旧路径的符号。
安装顺序的另一个细节是openssh-10.0p1包内部Requires关系的连锁反应。新版本的 sshd 依赖openssl-libs >= 3.5.0,而系统自带的openssl-libs是 3.0.x,所以必须先升级 OpenSSL。如果你先执行 OpenSSH 的rpm -Uvh而不带--nodeps,RPM 会拒绝安装并列出依赖缺失项,提示很清楚,但极少有人会认真读那大段依赖列表。
3.3 配置文件迁移:新增参数与废弃参数的兼容性处理
升级到 OpenSSH 10.0p1 之后,一个常见的坑是配置项变更。10.0 版本相比 8.x 有不少变化,例如HostKeyAlgorithms默认值更严格、PubkeyAcceptedAlgorithms默认不再包含ssh-rsa,而很多老客户的公钥恰好是ssh-rsa类型。直接拿老版本的 sshd_config 覆盖新版本,sshd 会在启动时发出大量警告,但通常不会中断启动,而一旦某个参数在新版本中被完全移除,比如UsePrivilegeSeparation的值从yes变成了仅接受sandbox,服务直接启动失败。
# 查看新版本支持的配置项 /usr/sbin/sshd -T | grep -i "useprivilegeseparation" # 找出新旧配置差异 diff /etc/ssh/sshd_config /usr/local/backup/sshd_config.baksshd -T是一个非常实用的验证命令,它输出的不是帮助文档,而是当前生效的配置值。比如useprivilegeseparation在新版本中已经默认强制为sandbox且不可配置,如果你在 sshd_config 里写了UsePrivilegeSeparation yes,部分版本会直接拒绝启动,报Bad Yes参数值。遇到这类问题,最快的方法是把这行配置注释掉,让新版本使用编译时默认值。
配置文件迁移的正确顺序是:先用新版本自带的默认配置启动服务,确认能跑通,再逐项应用老配置。因为把整份老配置直接套到新二进制上,大概率会有各种警告,有的警告会直接导致 sshd 启动失败。别自作聪明,以为每条配置都能平滑迁移。
一键执行完脚本后,我建议强制走一遍配置检查:
# 检查 sshd 配置是否合法 /usr/sbin/sshd -t # 查看实际生效的配置 /usr/sbin/sshd -T | grep -E "^(port|permitrootlogin|pubkeyauthentication|passwordauthentication)"sshd -t只检查语法不检查完整上下文,配合sshd -T才能看到真实运行值。比如PermitRootLogin默认值在新版本里更严格,sshd -t不会报错,但实际生效的可能已经不是你以为的值。
4. 加固配置深度解读:脚本写完了,你还得知道每一行的含义
4.1 sshd_config 加固参数模板与推荐值
升级本身不是目的,加固才是。脚本通常在最后阶段会复制一份加固模板到/etc/ssh/sshd_config,但你必须理解里面的每一项,否则后续业务方来问“为什么我们的自动化批量分发连不上”时,你连拒绝的原因都说不清楚。以下是我认为比较合理的加固基线:
# 协议版本只保留 2 Protocol 2 # 禁止 root 直接登录,管理员用普通用户+sudo PermitRootLogin no # 空密码账户禁止登录 PermitEmptyPasswords no # 限制认证尝试次数,防止爆破 MaxAuthTries 4 MaxSessions 10 # 禁止转发,减少跳板风险 AllowTcpForwarding no AllowAgentForwarding no # 登录超时控制 LoginGraceTime 30 # 仅使用强 HMAC 和加密算法 KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512 Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com MACs hmac-sha2-512,hmac-sha2-256 # 公私钥认证默认开启,但剔除 ssh-rsa PubkeyAuthentication yes HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256每一条都有它的设计逻辑。MaxAuthTries 4的含义是每个连接最多允许 4 次认证失败,超过之后 SSH 服务端立即断开连接,这个参数配合 fail2ban 效果较好,但不建议单依赖它——攻击者可以开多个连接并发爆破。LoginGraceTime 30是登录超时时间,单位是秒,如果客户端在 30 秒内没完成认证,服务端主动断开,这个参数能防止慢速攻击占用连接资源。AllowTcpForwarding no是阻断 SSH 隧道的重要开关,很多安全扫描工具就是通过测试 TCP 转发是否开启来判断风险的,关了之后,攻击者即使拿到普通用户权限,也无法通过 SSH 隧道翻到内网其他机器。HostKeyAlgorithms只保留ssh-ed25519和rsa-sha2-512、rsa-sha2-256,意味着老旧的 1024 位 RSA 密钥将无法用于主机认证。
应用这些配置有一个前置动作:确保/etc/ssh/目录下的 host key 存在且权限正确。如果升级过程中 key 文件权限被改掉(常见的是变成了 777),sshd 会拒绝加载这些 key 并报bad permissions,服务根本起不来。检查命令如下:
# 检查 host key ls -l /etc/ssh/ssh_host_*_key chmod 600 /etc/ssh/ssh_host_*_key chmod 644 /etc/ssh/ssh_host_*_key.pub4.2 OpenSSL 安全级别与系统兼容性验证
升级到 OpenSSL 3.5.0 后,系统里其他依赖 OpenSSL 的组件同样会受到影响。Nginx、Apache、curl 等动态链接了libcrypto和libssl的程序,如果在升级后没有重启过,它们仍然持有旧版本的动态库句柄。旧库文件虽然被替换删除了,但只要进程不重启,它就不会加载新版,功能上没毛病,但安全上存在风险,且和 RPM 数据库记录的文件状态不一致。
# 查看系统当前动态库链接 ldd /usr/bin/curl | grep ssl # 查看 sshd 实际链接的库 ldd /usr/sbin/sshd | grep -E "ssl|crypto"输出结果中如果路径指向/usr/lib64/libssl.so.3,说明已经切换到新版本。如果指向/usr/local/lib64之类的自定义路径,说明系统里存在多版本 OpenSSL 共存,且 sshd 可能链接到了旧版本。很多源码编译方案的坑就在这里:你以为装好了新版 OpenSSL,但 sshd 链接的还是/usr/local/ssl/lib/libcrypto.so,版本号显示可疑。
# 如果共存版本导致混乱,查看实际链接的完整路径 readlink -f /usr/lib64/libssl.so.3正常情况应该输出类似/usr/lib64/libssl.so.3.5.0的路径。如果输出的是.so.3.0.x,说明 RPM 包虽然装了新版,但alternatives或软链接没有更新。此时强行重启 sshd 会导致服务起不来,因为旧的.so.3可能已不存在。修复方式通常是手动删除旧软链接并指定到新库:
ln -sf /usr/lib64/libssl.so.3.5.0 /usr/lib64/libssl.so.3 ldconfig要注意,这套操作必须在本地控制台或带外管理环境中执行,一旦 sshd 起不来且没有其他登录通道,你连修复机会都没有。
5. 高频故障排查与避坑记录:五个典型翻车点
5.1 远程升级过程中会话中断,人直接被关在门外
现象:我在远程机器上执行升级脚本,执行到systemctl restart sshd时,当前会话突然断开,随后再也连不上,ssh 客户端一直提示Connection refused。原因:配置里没有保留旧监听端口,或者新版本 sshd 启动失败,旧进程被终止,新进程没起来。解决:所有远程升级必须给自己留后路。我的习惯是升级前先开一个screen或tmux会话,这么做不能在 sshd 宕机时保活,所以要配合at定时任务来兜底:
# 打开一个独立终端,在本地执行,预留 5 分钟后自动恢复旧版本 echo 'cd /root/backup && cp -a sshd.bak /usr/sbin/sshd && systemctl restart sshd' | at now + 5 minutes如果新版本 sshd 起不来,5 分钟后备份的旧二进制会被放回去并自动重启,除非 sshd 配置里还有语法错误。更稳妥的方式是修改 sshd_config 时保留两个配置,先手动启动一个临时 sshd 实例监听在 22222 端口,验证正常后再重启正式服务,但脚本自动化阶段很难做到这一点,所以at兜底是最快速且最有效的后悔药。
5.2 依赖库版本冲突导致 ssh 无法执行
现象:升级完成后,执行ssh命令直接报错:ssh: error while loading shared libraries: libcrypto.so.3: cannot open shared object file。原因:OpenSSL 的 RPM 安装顺序出错,或者ldconfig没有被调用,动态库缓存还没更新。解决:执行ldconfig然后ldd /usr/bin/ssh检查链接路径。如果新库文件确认存在但缓存没刷新,ldconfig即可解决;如果文件本身不存在,说明 OpenSSL RPM 包没装全。
5.3 SELinux 策略拦截 sshd 非标准端口
现象:sshd 配置了监听 2222 端口,sshd -t语法检查通过,systemctl start sshd提示启动成功(因为 fork 进程成功),但ss -tlnp看不到 2222 端口监听。原因:SELinux 的ssh_port_t策略只允许 sshd 监听 22 端口,监听非标准端口需要单独放行。解决:使用semanage调整端口策略:
# 为 SSH 添加非标准端口 semanage port -a -t ssh_port_t -p tcp 2222 # 立刻验证端口监听状态 ss -tlnp | grep ssh如果你所在的网络环境策略较严,也可以直接把 SELinux 设为 permissive 来快速定位问题排查思路,但上线环境必须保持 enforcing。
5.4 老客户端用ssh-rsa公钥连接直接被拒
现象:升级后,某台老版本 Git 服务器或自动化脚本使用 SHA-1 RSA 密钥连接新机器,报错Unable to negotiate with x.x.x.x port 22: no matching host key type found。原因:OpenSSH 10.0 默认在HostKeyAlgorithms中移除了ssh-rsa(即 SHA-1 签名的 RSA)。解决:如果确认业务侧无法快速升级,可以在 sshd_config 中临时加回ssh-rsa,但不建议长期保留:
# 临时兼容(不推荐长期使用) HostKeyAlgorithms +ssh-rsa PubkeyAcceptedAlgorithms +ssh-rsa加了之后立刻sshd -t检查一遍,然后重启服务。从运维角度,最好的方案还是把客户端的密钥升级成ed25519。
5.5 系统日志里刷屏的 GSSAPI 认证失败记录
现象:升级后/var/log/secure里大量GSSAPI authentication error记录,网络上没有任何影响,但安全团队看到日志会来追问。原因:新版本 OpenSSH 默认开启 GSSAPIAuthentication,而内网机器没有配置 Kerberos,每次连接尝试都会产生认证错误日志。解决:关闭 GSSAPI 与 DNS 反向解析,减少无意义日志与延迟:
# 加固配置中追加 GSSAPIAuthentication no UseDNS no6. 升级后的验证习惯:一套组合拳,确保下次不再踩坑
6.1 一分钟验证清单:版本、进程、配置、日志
升级完成不代表收工,我的习惯是强制走一遍固定流程,每换一台机器都按顺序执行这四步:
# 第一步:确认版本 ssh -V 2>&1 openssl version # 第二步:确认服务与端口 systemctl status sshd ss -tlnp | grep :22 # 第三步:确认实际生效配置 /usr/sbin/sshd -T | grep -E "permitrootlogin|pubkeyauthentication|passwordauthentication|port" # 第四步:确认无异常重启记录 journalctl -u sshd --since "10 minutes ago" | grep -i error第一步是给人看的,各路扫描、台账要求的就是这个输出。第二步是确认服务还活着。第三步经常被跳掉,但它才是真相来源,配置文件里写PermitRootLogin yes但被 Match 块覆盖了,实际效果完全不同。第四步是排查隐患,能把启动时的弃用警告、权限警告全部提前暴露出来。
6.2 维护窗口之后的一个小习惯
有一次我给某台机器做完升级,第二天才发现 RSA 主机密钥被自动重新生成了,导致所有客户机的known_hosts全部告警。从那次以后,我每次升级完都会把/etc/ssh/ssh_host_*文件的时间戳打印出来,与升级前备份的时间戳做对比,发现任何密钥文件时间异常,立刻从备份恢复。升级 OpenSSH 不是终点,安全加固也没有一劳永逸的配方,关键是把验证流程固化成肌肉记忆。希望这个思路对你有帮助。
本文还有配套的精品资源,点击获取