简介:面向Ubuntu服务器运维人员与安全管理员,针对OpenSSH旧版本存在的已知漏洞,提供一键升级至9.5p1并同步更新zlib-1.3压缩库的完整方案。压缩包共3个文件,包括两个gz格式的源码压缩包和一个sh自动化升级脚本,总大小仅3.18MB,轻量且便于分发;目前已有705人学习下载,适用于存在同类升级与安全加固需求的场景。脚本封装了环境检查、源码编译、安装覆盖、服务重启及版本验证等关键步骤,同时兼顾旧配置备份与sshd_config调整,有效降低升级过程中的误操作风险。描述中还给出了编译工具安装、解压目录规划、配置文件修改等细节,可作为一份简洁的操作参考。用户既能获取OpenSSH与zlib的最新源码,也能直接复用自动化脚本完成批量部署,升级后有助于修复安全漏洞并借助zlib-1.3提升压缩性能与兼容性。
1. 修 sshd 漏洞不能只等 apt:Ubuntu 源码编译 OpenSSH 9.5p1 的完整链路
扫描报告里出现 openssh 高危漏洞,你第一反应是apt upgrade,结果提示已经是最新版——但ssh -V一看,版本号还停留在几年前。这是 Ubuntu 仓库版本滞后的老问题,尤其是 openssh 这类基础组件,发行版只会在系统大版本更新时才会同步大版本号。要在一台存量 Ubuntu 服务器上把 OpenSSH 升到 9.5p1,源码编译几乎是唯一路径,而编译 openssh 前还得先把 zlib 升到 1.3,否则很多新特性根本编不出来。这套一键升级资源就是把这两件事打包到一起:先处理 zlib 依赖,再编 OpenSSH,最后把配置和服务接管都处理好。适合做等保整改、漏洞复测、内网安全加固的运维和开发,也适合第一次手动编译系统组件的同学照着跑一遍,看清整个链路。
2. 为什么升 OpenSSH 要动 zlib:先搞清楚依赖再动手
2.1 先确认当前版本和系统环境
升级前我会先做三件事:确认当前的 openssh 和 zlib 版本、确认系统的编译工具链是否完整、确认 sshd 的配置目录和启动方式。这些信息直接决定后面脚本怎么写、要不要提前装依赖包。
ssh -V dpkg -l | grep zlib cat /etc/os-release which sshd && sshd -V 2>&1 | head -n 1这里ssh -V输出的是客户端版本和服务端版本,OpenSSH 9.5p1 会同时覆盖两者。dpkg -l | grep zlib用来确认系统里 zlib 的库文件版本,注意 Ubuntu 里存在 libz-dev、zlib1g、zlib1g-dev 等多个相关包,其中 zlib1g-dev 是编译时需要的头文件包。/etc/os-release确认发行版版本,不同 Ubuntu 版本的依赖行为有差异。最后sshd -V会同时输出编译参数里包含的默认路径,比如默认的配置目录和二进制目录,后续编译安装时要避免把路径搞乱。
2.2 编译工具链与依赖审计
OpenSSH 源码编译依赖 make、gcc、perl 以及 libssl-dev、libpam0g-dev。其中 libssl-dev 是关键,因为 sshd 的加密通信依赖 OpenSSL 库;libpam0g-dev 决定编译出来的 sshd 是否支持 PAM 认证,很多服务器登录方式依赖 PAM,少了这个头文件后面登录验证会翻车。
sudo apt update sudo apt install -y build-essential gcc make perl libssl-dev libpam0g-dev zlib1g-dev装完之后建议跑一遍dpkg -l | grep -E "libssl-dev|libpam0g-dev"确认头文件真的装上了。有些精简镜像缺了build-essential,编译时会直接在 configure 阶段报C compiler cannot create executables,这类报错不是源码问题,是基础工具链缺失。
2.3 OpenSSH 9.5p1 为什么绑定 zlib 1.3
OpenSSH 的压缩传输依赖 zlib 库,编译时通过--with-zlib指定 zlib 的安装路径。Ubuntu 自带仓库里 zlib1g 的版本通常停留在 1.2.x,而 OpenSSH 9.5p1 的构建脚本在新版本里对 zlib 的 API 调用有更新,部分特性会报错或自动降级。更实际的原因是:如果你只编 OpenSSH 而不动 zlib,编译出来的 sshd 动态链接的还是系统旧版 libz.so,相当于新版本套了个旧内核,压缩传输相关的行为跟旧版没有区别。
这套资源把 zlib 1.3 单独编译安装到/usr/local/zlib,再让 OpenSSH 显式指定这个路径。这样做的优势是隔离干净:系统自带的 zlib1g 继续服务其他依赖它的程序,新版 OpenSSH 用自己目录下的 libz.so.1.3,两边互不干扰。代价是升级后 sshd 的依赖路径变了,如果没把动态库路径配置好,sshd 会起不来——这个坑在第 5 章会详细展开。
3. 一键升级脚本实操:zlib 编译、OpenSSH 编译与服务接管
3.1 脚本主流程与版本变量
资源包里脚本的设计遵循一个固定顺序:设置版本变量 → 下载源码 → 编译安装 zlib → 编译安装 OpenSSH → 备份旧配置 → 安装新二进制 → 重启 sshd。整个流程里最核心的版本变量就是两个:ZLIB_VERSION=1.3和OPENSSH_VERSION=9.5p1,后续所有路径、文件名都从这两个变量推导。
#!/bin/bash set -euo pipefail ZLIB_VERSION="1.3" OPENSSH_VERSION="9.5p1" ZLIB_SOURCE="zlib-$ZLIB_VERSION.tar.gz" OPENSSH_SOURCE="openssh-$OPENSSH_VERSION.tar.gz" INSTALL_PREFIX="/usr/local" ZLIB_PREFIX="$INSTALL_PREFIX/zlib" OPENSSH_PREFIX="$INSTALL_PREFIX/ssh"这里set -euo pipefail是必须的,任何一步出错脚本立即停止,避免编译失败后继续执行安装导致系统 sshd 被损坏。ZLIB_PREFIX和OPENSSH_PREFIX分别指定安装目录,这两个目录后面会分别传给--with-zlib和--prefix。
3.2 zlib 1.3 编译:configure 参数与校验
zlib 的编译相对简单,没有复杂的依赖,但要注意它默认安装到/usr/local/lib,如果直接让它用默认路径,升级完 openssh 时系统的动态库查找路径里可能找不到新版 libz。所以我建议指定独立前缀。
cd /usr/local/src tar -zxf "$ZLIB_SOURCE" cd "zlib-$ZLIB_VERSION" ./configure --prefix="$ZLIB_PREFIX" make -j"$(nproc)" sudo make install--prefix=/usr/local/zlib让整套 zlib 文件落在独立目录,后续 OpenSSH 编译时--with-zlib=/usr/local/zlib精确指向这里。make -j$(nproc)表示用全部 CPU 核心并行编译,机器核数少或内存紧张时改成make -j2更稳。装完后验证一下关键产物:
ls -l "$ZLIB_PREFIX"/lib/libz.so* "$ZLIB_PREFIX"/bin/zlib-flate -h 2>&1 | head -n 2如果 libz.so.1.3 存在但 zlib-flate 不存在,可能是 configure 阶段没开全部工具,不过这不影响 OpenSSH 编译,核心库文件在就行。
3.3 OpenSSH 9.5p1 编译:关键参数与 PAM 支持
OpenSSH 的 configure 参数是整个脚本里最需要说清楚的部分,尤其是有几个参数直接决定升级后 sshd 能不能正常启动、能不能保留原有登录方式。
cd /usr/local/src tar -zxf "$OPENSSH_SOURCE" cd "openssh-$OPENSSH_VERSION" ./configure --prefix="$OPENSSH_PREFIX" \ --with-zlib="$ZLIB_PREFIX" \ --with-ssl-dir=/usr/lib/x86_64-linux-gnu \ --with-pam \ --sysconfdir=/etc/ssh \ --with-md5-passwords make -j"$(nproc)" sudo make install几个参数逐个解释:
--prefix=/usr/local/ssh:指定 sshd 和 ssh 客户端二进制安装到独立目录,不覆盖系统/usr/bin/ssh。--with-zlib=/usr/local/zlib:显式指向刚编译好的 zlib 1.3。--with-ssl-dir:指定 OpenSSL 库路径。Ubuntu 里 libssl.so 在/usr/lib/x86_64-linux-gnu,如果服务器是 32 位系统或 ARM 架构,这个路径要改成对应的 multilib 路径。这个参数没指对,configure 阶段会报OpenSSL header not found。--with-pam:启用 PAM 认证支持。很多服务器依赖 LDAP、MFA 等 PAM 模块,少了这个参数编译出来的 sshd 会退回简单密码认证。--sysconfdir=/etc/ssh:保持配置文件目录不变,这样/etc/ssh/sshd_config不用迁移。--with-md5-passwords:兼容某些老系统的 MD5 加密口令。
编译完成后,新二进制在/usr/local/ssh/sbin/sshd,但系统服务文件/lib/systemd/system/ssh.service里启动路径还是/usr/sbin/sshd。所以在安装完新二进制后,脚本要做一次软链接管:
sudo mv /usr/sbin/sshd /usr/sbin/sshd.bak sudo ln -s "$OPENSSH_PREFIX/sbin/sshd" /usr/sbin/sshd这样 systemctl 重启 ssh 服务时,实际拉起的是新版本,路径上不会产生歧义。
3.4 配置备份与服务重启的前置检查
升级过程中最容易忽略的一件事是:旧的 sshd 配置文件里可能有新版不兼容的废弃参数。9.5p1 对某些历史遗留指令的容错更严格,比如PermitRootLogin without-password在新版里虽然暂时兼容但已经是 deprecated。所以脚本在重启服务前强制跑一次配置体检:
sudo /usr/local/ssh/sbin/sshd -t echo $?sshd -t只做配置语法与参数校验,不真正启动服务。如果返回非 0,说明某条配置需要调整,此时先别重启 sshd,否则远程会话直接断。我第一次升级时就是没跑这步,配置文件里残留一个已被 9.5p1 移除的参数,重启后远程连接全部拒绝,最后只能走带外控制台进去改配置,血泪经验。
4. 升级后的验证清单:服务状态、版本号与连接行为
4.1 服务启动状态的第一轮检查
安装完成不意味着升级成功,真正的验证从服务重启开始。以下命令按顺序跑:
sudo systemctl restart ssh sudo systemctl status ssh --no-pager ss -tlnp | grep :22 ssh -V /usr/local/ssh/sbin/sshd -V 2>&1 | head -n 1systemctl status看的是服务是否处于 running 状态,注意日志里有没有fatal:字样。ss -tlnp | grep :22确认 22 端口正在监听,监听 IP 是否符合预期。最后的sshd -V输出里能看到编译时指定的路径和特性列表,如果显示的 zlib 路径是/usr/local/zlib,说明链接的是新版。
如果服务状态是 failed,先看/var/log/auth.log里 sshd 的最后几行,最常见的错误是error: Could not load host key: /etc/ssh/ssh_host_rsa_key,这通常是配置文件 HostKey 路径指向了不存在的文件,或者权限不对。
4.2 新连接与密钥登录的完整验证
服务起来只是第一步,真正要验证的是“新建立的连接能否正常走完认证流程”。建议开一个额外的 SSH 会话窗口,保持旧连接不中断,再从新窗口尝试登录:
ssh -vv user@server_ip -p 22-vv会输出详细调试信息,包括客户端与服务端协商的算法、密钥交换方式和认证方法。重点看三处:有没有成功交换 host key、有没有提示Authenticated、有没有报Connection closed by remote host。
如果服务器用的是密钥登录,还要确认~/.ssh/authorized_keys的路径和权限没有被改动。OpenSSH 升级后如果报Authentication refused: bad ownership or modes,大概率是家目录或者.ssh目录的权限从 755 变成 775 之类的错误设置,执行chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys能解决。
4.3 与防火墙和端口占用的联动排查
部分服务器上 sshd 升级后出现端口无法连接,但systemctl status ssh显示 active。这种情况常见原因是系统里残留了旧的 sshd 进程占着 22 端口,而新进程启动时端口绑定失败但服务管理器没报错。排查命令:
ps aux | grep sshd ss -tlnp | grep sshd sudo lsof -i :22看到多个 sshd 进程同时存在时,把旧的杀掉再重启服务:
sudo pkill -u root -f "/usr/sbin/sshd.bak" sudo systemctl restart ssh防火墙方面,如果之前配了ufw allow OpenSSH,升级后规则不会变;但如果使用的是docker-proxy或其他 NAT 端口转发,注意新版 sshd 默认监听 IPv6 的行为可能和旧版不同,检查/proc/sys/net/ipv6/bindv6only的状态。
5. 源码升级 OpenSSH 的常见问题与避坑指南
5.1 sshd 启动失败:动态库链接指向旧版 zlib
升级完成后执行systemctl restart ssh直接失败,journalctl -u ssh显示sshd: error while loading shared libraries: libz.so.1: cannot open shared object file。原因在于 sshd 二进制链接的是/usr/local/zlib/lib/libz.so.1,但系统动态库默认搜索路径/etc/ld.so.conf.d/里没包含这个目录,运行时找不到依赖。解决方法是创建 ldconfig 配置文件并刷新缓存:
echo "/usr/local/zlib/lib" | sudo tee /etc/ld.so.conf.d/zlib-local.conf sudo ldconfig刷新后再启动服务。这个坑在 64 位系统和 32 位系统上都会出现,区别只在路径细节。建议在脚本里安装完成后强制跑一次ldconfig,不要等 sshd 启动失败再查。
5.2 远程升级中途断连:唯一会话直接翻车
如果你只用当前这一个 SSH 会话执行升级脚本,编译到一半网络抖动断连,系统里可能只剩一半的二进制文件。这不是脚本问题,是操作习惯问题。我一般做法是升级前先装一个终端复用器,确保断开后可以重新附着:
sudo apt install -y tmux tmux new -s openssh_upgrade后续所有编译和安装都在tmux的会话里执行。断连后重新登录服务器,执行tmux attach -t openssh_upgrade即可回到原会话。另一个保底手段是提前把 sshd 的启动命令改成手动控制,例如先测试sudo /usr/local/ssh/sbin/sshd -D -e前台运行,确认能正常监听端口后才重启服务。
5.3 私钥登录失效:StrictModes 与目录权限的坑
升级后使用私钥登录时被拒绝,auth.log里记录Authentication refused: bad ownership or modes。很多情况下不是 OpenSSH 的锅,而是系统里其他服务改过家目录权限。新版 OpenSSH 对~/.ssh目录和authorized_keys文件的权限校验更严格,超过 755 的目录直接拒绝。解决方法是逐级修正:
chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys chmod 755 ~同时确认/etc/ssh/sshd_config里StrictModes的值是yes或no之一。如果强制要求能登录就换no,但生产环境不推荐。
5.4 回滚的致命误区:直接用 apt 覆盖编译产物
升级后发现问题想降级回系统自带版本,直接执行apt install --reinstall openssh-server,结果发现 sshd 版本没变,依旧显示 9.5p1。原因是/usr/sbin/sshd已经被软链指向/usr/local/ssh/sbin/sshd,apt 包管理器重新安装时没有覆盖这个软链,服务实际用的还是编译版。正确回滚顺序是手动摘除软链再让 apt 恢复:
sudo rm /usr/sbin/sshd sudo mv /usr/sbin/sshd.bak /usr/sbin/sshd sudo systemctl restart sshsshd.bak是升级脚本提前备份的旧二进制。每次升级前强制生成这个备份,是我屡次踩坑后养成的习惯。
5.5 sudo 与 su 会话异常:PATH 环境变量被污染
升级后发现手动执行ssh命令报command not found,原因是新版本 ssh 客户端安装在/usr/local/ssh/bin/ssh,而/usr/bin/ssh还是旧二进制。如果旧客户端的动态库不兼容新配置,报错会更隐蔽。我通常处理方式是在/etc/profile.d/下加一个路径配置:
echo 'export PATH="/usr/local/ssh/bin:$PATH"' | sudo tee /etc/profile.d/openssh-path.sh source /etc/profile注意这会让所有新会话默认优先使用新版客户端,如果只想影响当前会话,直接在~/.bashrc里追加那一行 export 即可。
6. 把这次升级变成可复用资产:离线包与一键回滚脚本
升级本身是一次性动作,但服务器不止一台。我在完成第一台机器后,会把整套源码包和编译产物打包分发到后续机器,避免每台都重新从源码编译。核心做法是保留/usr/local/zlib和/usr/local/ssh两个目录,打包后在其他机器上解压,再做一次软链和 ldconfig:
tar -czf openssh-upgrade-bundle.tar.gz /usr/local/zlib /usr/local/ssh目标机上执行解压并配置动态库路径,其余步骤不变。离线包能显著缩短多台服务器的批量升级时间,从每台 15 分钟降到每台 2 分钟。
回滚脚本我建议单独写一个rollback.sh,内容比升级脚本更简单——恢复旧二进制、恢复旧动态库路径、重启服务:
#!/bin/bash set -euo pipefail PREFIX="/usr/local/ssh" BACKUP_BIN="${PREFIX}/sbin/sshd.bak" sudo systemctl stop ssh sudo rm /usr/sbin/sshd if [ -f "$BACKUP_BIN" ]; then sudo cp "$BACKUP_BIN" /usr/sbin/sshd fi sudo ldconfig sudo systemctl start ssh这个脚本不追求优雅,只追求在任何情况下能把 sshd 拉回可用状态。从那以后我每次升级系统组件都强制先跑一遍离线打包,再花两分钟写好回滚命令——保底手段的存在就是为了让大胆动手的人不至于狼狈收场。希望帮到你。
本文还有配套的精品资源,点击获取