首次通过 SSH 连接一台新服务器时,客户端会弹出「The authenticity of host can't be established」警告。这一提示并非多余 —— 它正是防范中间人(MITM)攻击的第一道屏障。然而,许多生产环境随手将 StrictHostKeyChecking 设为 no,或在 DNS 验证不完善时启用 VerifyHostKeyDNS,反而让这道屏障形同虚设。本文聚焦首次 SSH 连接场景,系统分析这两种配置选项的安全边界与工程实践。
首次连接时的握手逻辑
SSH 在建立连接时,服务器会向客户端出示其主机密钥(host key)的公钥部分。客户端随后将此公钥的指纹与本地 ~/.ssh/known_hosts 文件中的记录进行比对。如果目标主机此前未曾连接过,SSH 无法验证服务器身份,此时弹窗询问是否信任该主机密钥。
这个机制背后的安全意图非常清晰:首次建立信任后,后续连接会检查服务器返回的密钥是否与本地记录的指纹一致。若攻击者在首次连接时伪造服务器身份将伪造密钥注入客户端,该伪造密钥将被永久记录;未来真实服务器恢复时,客户端会因为指纹不匹配而拒绝连接 —— 这实际上是一把双刃剑:如果首次接受了一个恶意密钥,攻击者即可长期劫持后续所有连接。
StrictHostKeyChecking 的三个档位
ssh -o StrictHostKeyChecking=yes(或 no/ask)控制客户端对主机密钥缺失或变更的处理策略。
yes — 强制验证,连接目标主机时,若密钥不在 known_hosts 中,或记录的密钥与服务器返回的不一致,连接直接失败。适用于管理相对稳定的服务器集群,可确保任何密钥变更(无论合法更换还是攻击替换)都会被检测到。缺点在于首次部署新服务器时需要手动预先登记指纹,否则 CI/CD 脚本会因连接失败而中断。
ask(默认值) — 首次遇到新主机时暂停连接并等待用户确认;已记录的主机如果密钥变化,则行为同 yes。兼顾了一定的便利性与安全性,适合运维人员偶尔登录新机器的场景。
no — 关闭验证,SSH 不会检查密钥是否匹配,直接放行。这意味着攻击者在任意时机实施 MITM 时,客户端完全不会察觉。这是生产环境中应当极力避免的配置。
VerifyHostKeyDNS:DNS 辅助验证的风险
VerifyHostKeyDNS(取值 yes/no/delayed)让 SSH 尝试通过 DNS 的 SSHFP 记录获取服务器公钥指纹,以辅助或替代本地 known_hosts 验证。其理论优势在于:当 SSHFP 记录由权威 DNS 发布时,可在首次连接前验证服务器身份,无需手动提前登记指纹。
然而,这套方案存在一个根本性依赖:如果 DNS 本身被污染(如 DNS 欺骗或未启用 DNSSEC 的解析路径),攻击者完全可以在 DNS 层伪造 SSHFP 记录,从而让客户端信任攻击者的伪造密钥。换言之,DNS 验证的安全性等价于 DNS 链路的整体安全水位。若 DNS 服务不可信,VerifyHostKeyDNS 的防护价值将反转为新的攻击面。
当前主流安全实践建议在生产环境中将 VerifyHostKeyDNS 保持为默认值 no,改为依赖可靠的 known_hosts 管理流程。仅在 DNSSEC 完整部署且 DNS 基础设施可信的环境中,才考虑将其设为 yes。
分场景配置方案
场景一:固定服务器集群(DevOps / 基础设施团队)
# ~/.ssh/config
Host *.internal.example.com
StrictHostKeyChecking yes
UserKnownHostsFile /etc/ssh/ssh_known_hosts
通过集中式 known_hosts 文件(可由 Puppet/Ansible 分发)管理所有服务器指纹,首次连接时通过带外渠道(Console 截图、打印指纹清单等)验证新服务器指纹后,将其加入该文件。
场景二:偶尔接入新服务器(个人开发者)
# ~/.ssh/config
Host *
StrictHostKeyChecking ask
UpdateHostKeys ask
保留首次连接的确认提示;UpdateHostKeys ask 让 SSH 在服务器搬迁后自动更新旧指纹(而非直接拒绝),降低误判率。
场景三:自动化脚本(CI/CD Pipelines)
ssh -o StrictHostKeyChecking=accept-new \
-o UserKnownHostsFile=/tmp/ci_known_hosts \
-o LogLevel=ERROR \
user@deploy-target
accept-new 在密钥未知时接受(仅限首次),已知密钥变化时拒绝,同时将 LogLevel 调至 ERROR 避免污染构建日志。注意每次 Pipeline 运行后清理临时 known_hosts 文件,防止跨构建状态残留。
场景四:危险配置示例(请勿模仿)
# 严重安全风险:关闭所有主机密钥检查
ssh -o StrictHostKeyChecking=no user@prod-server
此配置下,任意中间人可轻松伪造服务器身份,后续所有命令执行均被劫持。
验证与监控:确保配置落地
光有配置还不够,需要验证配置是否生效:
- 审计现有
.ssh/config:定期扫描是否存在StrictHostKeyChecking no或VerifyHostKeyDNS yes的条目。可用grep -r "StrictHostKeyChecking no" ~/.ssh/config进行检查。 - 模拟首次连接场景:在内网测试机上启动伪造 SSH 服务,验证客户端是否正确拒绝连接(
Host key mismatch错误应出现)。 - CI 阶段加入指纹预登记步骤:在 Ansible/Provisioning 脚本中,首次连接服务器前先通过安全渠道(堡垒机打印、VPN 内邮件)获取指纹并写入
known_hosts,再执行部署任务。
结论
首次 SSH 连接是 MITM 攻击的高危窗口,但也是建立长期信任的关键节点。正确的策略是:以 StrictHostKeyChecking=yes 或 ask 为基准,确保未知主机必须经过验证才可接入;同时将 VerifyHostKeyDNS 保持在 no 状态,除非 DNS 基础设施本身具备 DNSSEC 可信验证能力。对于动态环境,优先使用集中式 known_hosts 管理而非 DNS 辅助,以移除对 DNS 安全性的额外依赖。
资料来源:SSH StrictHostKeyChecking 配置参考(sshfriendly.com)与 VerifyHostKeyDNS 安全分析(lalatendu.info)。
内容声明:本文无广告投放、无付费植入。
如有事实性问题,欢迎发送勘误至 i@hotdrydog.com。