第一次在线上环境里看到安全扫描报告把 SSH 服务标成高危时,我还是有点懵的。明明密码策略、登录失败锁定、端口白名单都做了,怎么问题还出在最基础的加密算法上。后来才意识到,SSH 服务能不能扛住攻击,不只是看密码强不强、密钥管得好不好,更要看它愿意跟客户端协商哪些算法。如果服务端还在默许 3des-cbc、RC4、hmac-sha1 这种老古董,等于主动把门锁降级成了插销。这篇就把我在生产环境里修复 SSH 弱加密算法的完整过程写出来,从原理、排查、方案设计到最终落地,给同样踩坑的运维同行一个可以直接抄作业的参考。
1. 为什么要关注 SSH 弱加密算法
1.1 SSH 服务原理:一次连接到底协商了什么
很多人对 SSH 的理解停留在"用 22 端口加密登录远程服务器"这个层面,但实际登录背后发生的事比想象中复杂。SSH 协议不是一上来就传账号密码,而是要经过 TCP 连接、版本交换、算法协商、密钥交换、主机密钥验证、会话加密等多个阶段。
算法协商这一步是安全性的分水岭。客户端和服务端各自维护一份支持的算法列表,包括密钥交换算法(KexAlgorithms)、主机密钥算法(HostKeyAlgorithms)、对称加密算法(Ciphers)、消息认证码算法(MACs)等。连接建立时双方会互相发送自己支持的算法清单,然后按照"取交集、选优先级最高"的规则确定本次会话实际使用的算法组合。
问题恰恰出在这个"取交集"的机制上。服务端如果把弱算法也放在列表里,客户端只要愿意用,协商结果就会降级到最薄弱的一环。比如服务端同时支持 aes256-gcm 和 aes128-cbc,老客户端又恰好优先请求 aes128-cbc,那本次会话的数据加密就用了已经被学术界判定为不安全的 CBC 模式。运维人员以为 SSH 加密很可靠,实际上流量早就被套上了老式算法。
1.2 弱加密算法常见的攻击路径
从实际攻击场景来看,弱加密算法主要暴露三类风险。
第一类是算法降级攻击。攻击者不需要直接破解强算法,而是通过伪造或篡改算法协商过程,让客户端和服务端落到弱算法上。只要服务端配置里还留着弱算法,攻击者就有了可乘之机。这类攻击对用户是透明的,登录界面没有任何异常提示。
第二类是已知算法漏洞利用。RC4(arcfour)加密算法早已被证明存在明显偏差,可以在足够多的密文中恢复明文;3des-cbc 的 64 位分组长度在面对现代密码分析时也不再安全;hmac-sha1 和 hmac-md5 作为消息认证码,碰撞攻击成本逐年下降。这些算法一旦被协商成功,等于给攻击者递了一根撬棍。
第三类是暴力破解与横向渗透的组合利用。弱算法本身不一定直接被攻破,但会明显降低攻击者的破解成本,并让嗅探到的会话数据更容易被离线分析。尤其在混合云、多机房互通的环境下,SSH 流量一旦经过不可控链路,弱算法带来的风险就会被无限放大。
1.3 安全扫描为什么总把这些算法标成高危
我一开始也疑惑,为什么扫描器动不动就把 SSH 降级、弱加密算法标成 High 甚至 Critical。这些扫描器通常采用 IETF 维护的算法推荐标准和各大厂商的最佳实践。只要服务端支持任何已被列入"不应使用"清单的算法,扫描结果就会告警,不会去判断该算法实际是否被使用。
打个比方,你家里的防盗门装了一把 C 级锁芯,但门框上还并排挂着一把老式挂锁。小偷不一定每次都会去开挂锁,但只要挂锁还在,风险评估就认为存在被打开的可能性。安全扫描的思路也一样:只要弱算法还在服务端列表里,就算平时协商到的都是强算法,风险等级依然居高不下。
所以修复弱加密算法的本质,就是做减法,把协议协商阶段的可选范围缩到安全基线之内,让系统根本没有机会走到弱算法那条路径上。
2. 动手前先做算法清单体检
2.1 用 ssh -Q 获取版本支持范围
修复之前必须搞清楚两件事:当前 OpenSSH 服务端支持哪些算法,以及当前配置实际启用了哪些算法。前者是版本的固有能力,后者才是需要关注的对象。
OpenSSH 7.6 及以上版本提供了一组很实用的查询命令。在服务器上执行ssh -Q cipher可以列出所有支持的对称加密算法,ssh -Q mac列出消息认证码算法,ssh -Q kex列密钥交换算法,ssh -Q key列主机密钥类型。这组命令的输出结果跟具体配置文件无关,是二进制文件编译时带进去的能力边界。
真正的生效配置需要通过sshd -T查看。这条命令会读取当前配置文件,把最终生效的参数打印出来,比直接看 sshd_config 更靠谱。因为 sshd_config 里支持 include 指令、条件匹配块,肉眼容易漏掉某些优先级关系。执行sshd -T | grep -E "ciphers|macs|kexalgorithms"就能拿到当前服务端实际对外公布的算法列表。
注意:sshd -T 需要 root 权限执行,某些发行版在非 root 用户下会提示需要权限或无法完成全部配置解析。
2.2 用 ssh-audit 扫描线上服务
光看服务端自己输出的列表还不够,因为配置最终要通过网络对外体现。推荐在任意一台能访问目标服务器的机器上,用 ssh-audit 做一次外部视角的扫描。这个工具是一个 Python 脚本,不需要安装到目标服务器上,执行起来很方便。
git clone https://github.com/jtesta/ssh-audit cd ssh-audit python3 ssh-audit.py -p 22 192.168.1.100扫描结果会按颜色分级,红色标记的是不推荐算法,黄色是存在隐患的算法,绿色是安全的算法。输出里还会附带改进建议,比如"remove cipher aes128-cbc"或者"use hostkey algorithm ssh-ed25519"。这个工具检查得非常细,甚至能给出本版本 OpenSSH 是否已经连接到密码学不安全算法端口上的建议。
实测下来,ssh-audit 的输出比手动看配置更直观,而且能发现一些隐藏问题。比如曾经有一次扫描结果显示主机密钥算法里有 ssh-rsa(这里指 SHA-1 签名的 RSA 密钥),但 sshd_config 里完全没有配置 HostKeyAlgorithms。原因是当前 SSH 版本默认启用了这个算法,没写配置不等于没有隐患。
2.3 读懂扫出来的算法列表
拿到扫描结果后,重点看三类算法。
加密算法里,凡是带 cbc 的,例如 aes128-cbc、aes256-cbc、3des-cbc,基本可以直接划进禁用名单。blowfish-cbc、cast128-cbc 这类老牌算法也已经没有启用价值。rc4、arcfour 但凡存在都应该立即处理。当前推荐保留的是 CTR 模式的 aes 系列和 AEAD 类算法,比如 aes128-ctr、aes192-ctr、aes256-ctr、aes128-gcm@openssh.com、aes256-gcm@openssh.com、chacha20-poly1305@openssh.com。
消息认证码算法里,hmac-md5、hmac-md5-96、hmac-sha1、hmac-sha1-96、hmac-ripemd160 这类都建议禁用。推荐保留的是 sha2 系列,比如 hmac-sha2-256、hmac-sha2-512,以及支持 Encrypt-then-MAC 模式的变体。
密钥交换算法里,diffie-hellman-group1-sha1 属于必须删除的,group14-sha1 也建议升级到 group14-sha256 或 group16-sha512。当前主流是 curve25519-sha256、curve25519-sha256@libssh.org,以及 ecdh-sha2-nistp256/384/521。需要说明的是,nistp 系列的椭圆曲线虽然在部分安全社区有争议,但在正式推荐的 OpenSSH 默认算法里依然在列,实际使用问题不大。
3. 修复方案设计:不是删得越狠越好
3.1 服务端算法白名单怎么定
设计算法白名单时,第一反应往往是只留最安全的几种算法。但在生产环境里,这种思路容易把自己玩死。因为线上不只有最新版 OpenSSH 对连你,还有各种运维脚本、监控系统、备份工具、网络设备,它们内置的 SSH 客户端版本参差不齐。
我的建议是分三步做:
第一步,清理掉明确不安全的算法,不带任何犹豫。CBC 系加密、RC4、hmac-md5、group1-sha1、hmac-sha1 等全部移除。
第二步,保留当前版本 OpenSSH 默认支持且安全性没有争议的算法作为主体。以 RHEL/CentOS 8 自带的 OpenSSH 8.0 为例,最后我保留的加密算法是 aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com, chacha20-poly1305@openssh.com。MAC 算法保留 hmac-sha2-256, hmac-sha2-512, umac-128-etm@openssh.com, hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com。密钥交换算法保留 curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group14-sha256, diffie-hellman-group16-sha512。
第三步,确认主机密钥算法。OpenSSH 8.0 默认支持 ssh-ed25519、ecdsa-sha2-nistp256/384/521、rsa-sha2-256、rsa-sha2-512,以及已标记为废弃的 ssh-rsa。如果直接写死 HostKeyAlgorithms,务必把 rsa-sha2 系带上,同时移除 ssh-rsa。要是不写这一项,默认行为里 ssh-rsa 可能还在,扫描器照样会标记。
3.2 老客户端和兼容性如何处理
设计白名单的时候,如果测试发现某些老设备连不上,别急着把弱算法加回去。先按"老设备到底缺哪个算法"的思路排查,再针对性处理。
比如某些网络设备自带的 SSH 客户端只支持 diffie-hellman-group1-sha1 或 diffie-hellman-group14-sha1,对应服务端就需要保留 group14-sha1。这种情况下可以评估两台设备之间的链路是否可信、是否在隔离网段。如果链路是可控内网,可以单独为这几台设备所在的网段做条件配置,设置宽松一点的算法集,而不是在全局配置里无差别放开。
OpenSSH 的 Match 条件块可以解决这个诉求。下面是一个简化的例子:
Match Address 10.20.30.0/24 KexAlgorithms curve25519-sha256,diffie-hellman-group14-sha1这样一来,普通用户和业务系统走全局强算法,指定老网段单独兼容。等老设备完成升级后,再把这个条件块删掉即可。这种局部放行的方式在安全审计时也说得清楚,风险范围是明确可控的。
3.3 证书主机密钥的同步升级
部分环境里 SSH 用的不是普通公钥认证,而是基于 CA 签发的证书认证。这种情况下不光要改算法列表,还要检查 CA 本身的算法强度。如果 CA 密钥仍然是 RSA 1024 或基于 SHA-1 签名,即使把 sshd_config 改得再严格,信任链源头依然薄弱。
建议确认 CA 密钥对采用 RSA 3072/4096 或 Ed25519,签名算法不涉及 SHA-1。如果证书签发体系在别的团队手里,至少要在本服务端配置里把TrustedUserCAKeys指向的 CA 公钥文件更新到强算法版本,并重新分发生成的用户证书。
这块内容很多人容易忽略,但安全扫描的视野往往是从握手算法一路看到认证链的,任何一个环节出现 SHA-1,都会被单独拎出来告警。
4. 生产环境完整修复流程
4.1 修改前准备与备份
正式动手前,先做三件事。
第一件事,备份配置文件。这个操作简单但不能省。执行cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%F_%H%M%S),出现任何问题都可以快速回滚。
第二件事,确认当前所有 SSH 会话清单。执行who或ss -tnp | grep :22,确认当前有哪些活跃会话。修改配置前最好确保有至少两个稳定的会话窗口,或者使用 nohup 方式保留一个后台任务做兜底。
第三件事,检查是否有针对 sshd 的自动化任务。比如配置管理工具 Ansible、SaltStack 会在同步时重写 sshd_config,监控系统会定时探测 22 端口。要确认修改不会被自动化任务覆盖,否则排查问题时容易出现"改了等于没改"的处境。
4.2 修改 sshd_config 核心配置
用编辑器打开/etc/ssh/sshd_config,在文件末尾追加或替换以下几行配置。建议把算法配置集中放在一个区域里,加上注释,方便后来的人快速定位。
# Security hardening: disallow weak algorithms Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com,chacha20-poly1305@openssh.com MACs hmac-sha2-256,hmac-sha2-512,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group14-sha256,diffie-hellman-group16-sha512 HostKeyAlgorithms ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,rsa-sha2-256,rsa-sha2-512这里有几个细节需要说明。
Ciphers 的顺序会影响协商优先级。OpenSSH 在服务端配置里给出的算法顺序即优先级顺序,所以把性能和安全兼顾的 chacha20-poly1305 放在后面,把兼容性最好的 aes128-ctr 放在前面,实际使用中可以让不同客户端各自选中自己最合适的算法,又不会掉到不安全区间。
MACs 里建议优先保留 EtM(encrypt-then-mac)变体。这类算法先加密后计算 MAC,比传统的先 MAC 后加密设计更安全,能避免针对 CBC 模式的 oracle 攻击变形。
KexAlgorithms 里 curve25519-sha256 是当前默认最优选择。group14-sha256 和 group16-sha512 作为兼容性选项保留,能满足绝大多数现代化客户端的协商需求。
HostKeyAlgorithms 要特别注意,不要漏掉 rsa-sha2-256 和 rsa-sha2-512。如果你服务器上主要用的还是 RSA 主机密钥,漏掉这两个会让所有基于 RSA 主机密钥的客户端连接失败。而 ssh-rsa 这个老算法一定要移除,它是知名扫描器重点关注的对象。
4.3 语法校验与平滑生效
修改配置文件后,不要直接重启服务。先执行sshd -t做语法检查。这条命令只校验配置格式,不会影响当前运行中的服务。输出没有任何提示就代表配置语法没问题。
接着执行systemctl reload sshd让配置生效。reload 和 restart 不同,reload 只是在运行中的 sshd 主进程上重新读取配置,不会断开已经建立的 SSH 连接。对于依赖长连接传输大文件或跑长时间任务的场景,这个差异非常重要。
这里还要提一个容易踩的坑:某些发行版上/etc/ssh/sshd_config文件默认含有Include /etc/ssh/sshd_config.d/*.conf指令,后加载的 .conf 文件会覆盖主文件里的同名配置。如果系统里存在/etc/ssh/sshd_config.d/下有其他配置文件,光改主文件可能不生效。修改后一定要用sshd -T | grep -E "ciphers|macs|kexalgorithms"确认最终生效值,不要只看文件内容。
4.4 新窗口验证与回滚预案
配置生效后,千万不要关闭当前连接。重新开一个终端窗口,尝试 SSH 登录同一台服务器。验证内容包括:
ssh -vvv user@server加上-vvv参数可以看到详细的算法协商过程。输出里会有kex: algorithm: curve25519-sha256、kex: host key algorithm: ssh-ed25519、cipher: aes256-gcm@openssh.com、mac: hmac-sha2-256之类的信息。逐项核对是否都在白名单内。
另外建议顺手用nmap --script ssh2-enum-algos -p 22 server_ip再从外部扫一次,确认服务端实际支持的算法列表已经更新。这一步能避免客户端因为本地缓存或代理导致的验证偏差。
如果新窗口连接失败,或者协商出来的算法不符合预期,马上回滚。回滚操作很简单:用之前备份的配置文件覆盖回去,再执行sshd -t && systemctl reload sshd。整个过程不会影响已建立的会话,因为 reload 不会断连。
5. 常见问题与排查技巧实录
5.1 连接失败:no matching cipher / kex
最常见的连接报错是Unable to negotiate with x.x.x.x port 22: no matching key exchange method found。这个报错直译是找不到匹配的密钥交换算法。原因通常是客户端支持的算法和服务端白名单没有交集。
排查思路很直接:看客户端支持哪些算法,再看服务端启用哪些算法。客户端侧用ssh -Q kex查支持列表,服务端用sshd -T | grep kexalgorithms查生效列表。两个列表取交集,如果为空,说明真的没有共同语言。
这类问题往往出现在老设备上。比如某台旧交换机只支持 diffie-hellman-group1-sha1,而服务端白名单里没有这个算法。解决办法是评估是否需要单独为这个设备所在的网段开条件配置,而不是全局加回 group1-sha1。因为把 group1-sha1 加到全局,等于让所有客户端都有机会降级到这个算法,违背了修复初衷。
5.2 配置不生效:优先级问题
有一种情况很迷惑:配置文件明明改了,sshd -T 输出的算法列表却还是老样子。这大概率是 Include 指令的优先级问题。
OpenSSH 解析配置的原则是"后出现的配置项覆盖前面出现的配置项"。主配置文件中如果有Include /etc/ssh/sshd_config.d/*.conf,并且 include 指令写在文件末尾,那么 .conf 目录下的任何同名配置项都会覆盖主文件内容。
排查时先执行sshd -T | grep -i "include"或者直接查看主文件开头部分是否引用了额外目录。如果确认存在 .conf 覆盖,要么把算法配置统一写到 .conf 文件里,要么临时把 .conf 里的相关配置项注释掉。最稳妥的做法是统一管理,只保留一个配置入口,避免多处维护形成漂移。
另外,有些自动化脚本会定期重写配置文件。如果配置总是被重置,检查 cron、Ansible、SaltStack 或系统加固脚本是否存在相关逻辑。我之前就碰到过加固脚本每周日凌晨自动把 Ciphers 重置成默认值的情况,排查了很久才发现是 cron 任务在作怪。
5.3 自动化任务和旧脚本的兼容性
线上环境经常会有一批基于老参数写的自动化脚本。比如某些发布系统会直接执行scp -c aes128-cbc,或者用带-o Ciphers=arcfour的 ssh 命令传输数据。这种脚本在弱算法禁用后会立刻报错。
应对办法是逐个梳理这些脚本,能改的直接把算法参数改成现代算法,不能改的评估是否需要临时白名单。但这里有个原则:能用升级脚本解决的,不要在服务端放行弱算法。放行了一个 arcfour,可能就是为了一个两年前就该废弃的脚本,风险完全不匹配。
排查自动化脚本时可以通过登录日志辅助判断。所有因为算法协商失败而产生的连接尝试都会记录在/var/log/secure或/var/log/auth.log里,看到对应报错后顺藤摸瓜找到调用方。
5.4 别把自己锁在门外的操作纪律
生产环境调整 SSH 配置最担心的就是把自己锁在门外。除了前面说过的新窗口验证法,还有几条操作纪律值得分享。
第一,不要在业务高峰期做这类变更。SSH 配置重载理论上影响面可控,但万一出现兼容性问题,恢复过程会变成一场灾难。
第二,始终保持有一个可用窗口。哪怕只是开个交互式 shell 挂着,也比全部依赖新连接验证要安全。因为 reload 不会断已有连接,只要有一个可用窗口,即使新连接全部失败,也能从容回滚。
第三,准备好带外管理通道。比如云控制台的 VNC/管理终端,或者物理服务器上的 BMC/IPMI。这个通道跟 SSH 是独立的,能在 SSH 完全不可用的情况下进行救援操作。有了这层保障,再改配置心里就不慌了。
6. 修复后的常态化审计
6.1 把算法审计纳入巡检脚本
弱算法修复不是一次性的工作。新版本 OpenSSH 会不断调整默认算法列表,安全基线也在持续变化。今天安全的算法,可能过几年就不再推荐。建议把 SSH 算法审计纳入定期巡检脚本。
一个简单的巡检逻辑是:每天用 ssh-audit 扫描一次关键服务器,把扫描结果中标记为 fail 的项输出到日志,配合通知机制。这样任何一台服务器出现配置回退或者新引入弱算法,都能第一时间发现。
如果环境里没有专门的密钥管理平台,用 crontab 加一个脚本也能实现基本效果。脚本内容就是把 ssh-audit 的结果和上次记录的基线做 diff,有差异就告警。核心是让算法清单处于可审计状态,而不是改完一次就再也不看。
6.2 变更记录和监控回溯
建议把本次修复的时间、修改的参数、涉及的服务器清单、验证命令和结果都记录下来。这个记录不只是为了应付审计,更是为了后续排障时能快速判断"这个配置到底是哪次变更引入的"。
我习惯把这类变更记录放在配置管理仓库里,用 Git 做版本跟踪。sshd_config 的每次修改都提交一次,配合 commit message 写清楚变更原因和影响面。半年后回看时,能非常清楚地还原每次安全加固的决策过程。
监控方面,可以对 SSH 连接失败率做告警。尤其是因为算法协商失败产生的连接拒绝,这类错误在正常运维中不应该出现,一旦批量发生,说明有设备或脚本没有跟上算法更新节奏。及时处理这些不兼容的连接,能避免后续大规模变更时出现意外。
还有一个小技巧:可以周期性执行一次远端算法扫描,对比上一轮结果。如果发现某台服务器的算法列表突然变了,首先要查是不是有人手动改了 sshd_config,其次要查是不是包管理器更新了 OpenSSH 版本导致默认配置变化。这种被动发现机制能有效兜住低级失误。
我自己在几次修复过程中养成了一个习惯:每次调整完算法白名单,都会顺手把当时的 sshd -T 输出和 ssh-audit 扫描结果存一份快照。等下次做安全整改时,翻出这些快照,对比当前状态,变化一目了然。这套方法在应对等保测评和安全自查时特别好用,因为所有证据都是现成的,不需要临时去回忆。如果你也在处理类似的 SSH 加固任务,建议从体检开始,一步步走完整个流程,不要跳步,这套操作在生产环境里是完全可以落地的。