暗色模式

SSH 服务器安全加固实战:从密钥认证到 PerSourcePenalties

技术教程
2026-08-02
9
0

为什么 SSH 需要加固

只要把一台 Linux 服务器接到公网,几乎立刻就能在日志里看到来自全世界的暴力破解尝试:扫描器扫到 22 端口开放后,就开始用常见用户名和弱密码批量试探。默认配置的 sshd 存在几个历史遗留问题:允许 root 直接登录、密码认证默认开启、对失败尝试没有内置惩罚机制。2024 年爆出的 CVE-2024-6387(regreSSHion) 更是让 OpenSSH 8.5p1–9.7p1 的未打补丁系统暴露在远程代码执行风险下。

好在从 OpenSSH 9.8 开始,官方逐步把防御能力内建进了 sshd:PerSourcePenalties 按来源 IP 自动惩罚、ML-KEM 后量子密钥交换在 10.2 起默认启用。本文给出一套可直接落地的加固清单,按「密钥认证 → 配置加固 → 算法收紧 → 内置防爆破 → 网络兜底 → 双因素」的纵深防御顺序展开,全程在 Ubuntu 系(20.04+,含最新的 26.04 LTS)上验证可操作。

第 1 步:加固前的安全准备

动手前先做好两件事,避免把自己锁在门外:

  1. 备份配置sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
  2. 保留逃生通道:新开一个已登录的 SSH 会话不要关闭,用第二个会话执行下面的修改;一旦配置错误,还能用旧会话回滚。

Ubuntu 的 /etc/ssh/sshd_config 末尾默认有 Include /etc/ssh/sshd_config.d/*.conf,因此推荐把加固项写进独立的 drop-in 文件(而不是改主文件),升级或排错都更干净。注意文件按字典序生效,后加载的覆盖先加载的——云镜像的 /etc/ssh/sshd_config.d/50-cloud-init.conf 可能重新打开密码认证,所以我们的文件要取一个靠后的名字:

sudo tee /etc/ssh/sshd_config.d/99-hardening.conf <<'EOF'
# SSH 加固配置(后续步骤逐步追加)
EOF
sudo sshd -t          # 校验语法,无输出即通过
sudo systemctl reload ssh

sshd -t 只校验语法不生效;sshd -T(大写)则打印生效后的完整配置,用来核对最终结果。每次改完都先 sshd -treload,这是最安全的节奏。

第 2 步:用密钥认证替换密码登录

密码认证是爆破的根源,密钥认证则让暴力破解在数学上不可行。推荐使用 Ed25519 算法——现代、快速、密钥短(RSA 4096 只有 FIPS 等合规场景才需要):

# 本机(客户端)生成密钥对,-a 100 提高 KDF 迭代次数
ssh-keygen -t ed25519 -a 100 -C "astarry@workstation"

将公钥推送到服务器:

ssh-copy-id -i ~/.ssh/id_ed25519.pub astarry@blog.astarry.top

对多台服务器,建议在 ~/.ssh/config 里给每台主机起别名并指定密钥,省去每次敲参数:

Host blog
    HostName blog.astarry.top
    User astarry
    IdentityFile ~/.ssh/id_ed25519
    ServerAliveInterval 60

确认公钥登录成功后,再关闭密码认证。在 99-hardening.conf 中追加:

# 仅允许密钥登录
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
注意:KbdInteractiveAuthentication 是 OpenSSH 9.8 起的新指令名(替代已废弃的 ChallengeResponseAuthentication),老版本系统上用后者。

心法:永远用「先确认新方式可用,再关闭旧方式」的顺序操作——先 ssh-copy-id 并验证能登录,才关密码。反过来做就是自锁的教科书式死法。

第 3 步:核心 sshd_config 加固

把下面这些项追加进 99-hardening.conf

# 禁止 root 直接登录,管理员用普通用户 + sudo
PermitRootLogin no

# 白名单:只允许指定用户/组登录(未匹配的用户直接拒绝)
AllowUsers astarry
# AllowGroups wheel

# 限制认证尝试次数与会话数
MaxAuthTries 3
MaxSessions 5
MaxStartups 10:30:100

# 未认证连接 30 秒超时
LoginGraceTime 30

# 空闲连接 10 分钟后断开(300s × 2 次探测)
ClientAliveInterval 300
ClientAliveCountMax 2

# 禁用一切转发与隧道
DisableForwarding yes
X11Forwarding no
PermitTunnel no
GatewayPorts no

# 不做反向 DNS 查询(省时且避免 DNS 故障影响登录)
UseDNS no
# 记录更详细日志,便于审计
LogLevel VERBOSE

逐项说明:

  • PermitRootLogin no:root 是爆破的第一目标用户名,禁用后攻击者连尝试的入口都没有;日常操作一律 sudo
  • AllowUsers:比 DenyUsers 更推荐的显式白名单——默认拒绝,只有名单内的人可登录。
  • MaxAuthTries 3:每个连接最多 3 次认证失败。配合第 5 步的 PerSourcePenalties,暴力破解会同时受次数和来源双重限制。
  • LoginGraceTime 30:连接建立后 30 秒内未完成认证即断开,防止攻击者挂着一堆半连接占资源。
  • DisableForwarding yes:一条指令关闭 TCP/X11/agent 全部转发(OpenSSH 7.4–9.9 曾存在它不覆盖 AllowTcpForwarding 的缺陷,本清单同时显式写 X11Forwarding no 兜底)。日常管理服务器根本用不到转发,需要时再对指定用户用 Match 块放行。

第 4 步:算法与协议收紧

2026 年的现状:OpenSSH 10.2 起(Ubuntu 26.04 LTS 自带)默认启用 ML-KEM 后量子密钥交换(mlkem768x25519-sha256),DSA 已被彻底移除;最新的 10.4(2026-07 发布)进一步加入了实验性的 ML-DSA44 + Ed25519 复合后量子签名。对绝大多数服务器,保持发行版默认的现代算法即可,手动精简反而容易写出不兼容的列表把老客户端挡在门外。

如果你确实想显式收紧(例如面向等保审查需要书面说明),一个保守的配置如下——只排除已被淘汰的 SHA-1 系算法,保留后量子与经典两条路线:

# 去掉 diffie-hellman-group*-sha1 等旧 KEX,保留 ML-KEM 后量子与 curve25519
KexAlgorithms mlkem768x25519-sha256,curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521

# 仅保留 AEAD 加密(chacha20-poly1305 与 AES-GCM),剔除全部 CBC/3DES
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com

# 优先 encrypt-then-MAC 的 SHA-2 系,剔除 hmac-md5 / hmac-sha1
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com

# 加密流量上限与重协商周期
RekeyLimit 1G 1h
谨慎提醒:旧客户端(OpenSSH 9.5 之前)可能无法与 ML-KEM 默认 KEX 协商成功,若线上有老客户端,优先升级客户端而不是回退算法。

与其纠结算法清单,不如盯紧补丁:算法限制救不了实现漏洞(regreSSHion 就是例证),保持 apt update && apt upgrade 及时升级 OpenSSH 才是治本。

第 5 步:内置防爆破:PerSourcePenalties

OpenSSH 9.8(2024)引入的 PerSourcePenalties 把 fail2ban 的核心能力内建进了 sshd:检测到某来源 IP 出现恶意行为(认证失败、不认证就断开、触发崩溃等),就自动拒绝该地址一段时间,无需外部守护进程、不依赖日志解析。10.3 起新增 invaliduser 类(探测不存在用户)并增强了 GSSAPI 下的用户枚举防护。

# 各类恶意行为的惩罚时长;min/max 是累计惩罚的下限与上限
PerSourcePenalties authfail:30s noauth:2s grace-exceeded:30s crash:90s invaliduser:30s min:15s max:10m

# 按 IP 还是按网段惩罚:IPv4 /24、IPv6 /64(默认 32:128 只惩罚单 IP)
PerSourceNetBlockSize 24:64

# 豁免白名单:办公室出口 IP、跳板机、监控探针
PerSourcePenaltyExemptList 203.0.113.10,2001:db8:100::/64

各惩罚类含义:

类别触发条件
authfail一次或多次认证失败后断开
noauth未尝试认证就断开(扫描器最爱)
grace-exceeded超过 LoginGraceTime 未登录
crash行为导致 sshd 子进程崩溃
invaliduser尝试不存在的用户名(10.3+)
min / max累计惩罚的下限 / 上限

三点实用提醒:

  1. 不要对 noauth 惩罚过重:监控工具、ssh-keyscan 这类只握手不登录的客户端会全部中招,留 1–2 秒即可。
  2. NAT 注意:若公司多台机器共享一个出口 IP,把出口 IP 加进 PerSourcePenaltyExemptList,否则一个人输错密码,全公司一起被罚。
  3. 验证:改完 sshd -t && systemctl reload ssh,然后用 journalctl -u ssh -f 观察,会看到 srclimit_penalise: activating ipv4 penalty ... 的日志。用 sshd -T | grep -i per 核对生效值。

第 6 步:网络层兜底:Fail2Ban 与防火墙

PerSourcePenalties 已覆盖 SSH 层,Fail2Ban 作为网络层兜底仍值得保留——它不依赖 OpenSSH 版本,还能顺带保护其他服务(Web 后台、邮件等):

sudo apt install fail2ban
sudo tee /etc/fail2ban/jail.local <<'EOF'
[DEFAULT]
bantime = 1h          # 封禁时长
findtime = 10m        # 统计窗口
maxretry = 5          # 窗口内失败 5 次触发

[sshd]
enabled = true
EOF
sudo systemctl enable --now fail2ban

查看封禁情况:sudo fail2ban-client status sshd。如果你改了 SSH 端口,记得把 jail 里的 port 同步(虽然本文建议不必改端口——换端口只是「隐形」,扫描器同样能发现,还徒增运维复杂度,属于安全通过隐蔽)。

防火墙层面,若你使用 nftables/UFW,可再加一层细粒度限速:仅放行可信来源、对公网来源限制新建连接速率。云厂商的安全组里只放行你的固定 IP,是最省事的一层。

第 7 步:进阶:SSH 双因素认证(TOTP)

密钥 + 密码是「双因子」的说法其实不成立——两者都是「你知道/你拥有的东西」的变体。真正的双因素是把认证叠加到密码之外的第二渠道。用 PAM 的 TOTP 实现:

sudo apt install libpam-google-authenticator
google-authenticator    # 交互式生成,扫码存入手机,选择 y/n 按提示即可

配置 PAM 与 sshd:

echo 'auth required pam_google_authenticator.so nullok' | sudo tee -a /etc/pam.d/sshd
# 在 99-hardening.conf 追加:
# 先验证密钥,再要求 TOTP 动态码
# AuthenticationMethods publickey,keyboard-interactive
# KbdInteractiveAuthentication yes

重启 sshd 后,登录流程变为:验证密钥 → 提示输入验证码 → 登录成功。nullok 表示没有二维码的用户仍可登录(防止未配置 TOTP 的账号被锁),全部账号配置完成后应去掉。注意 PAM 与 SSH 的交互在不同发行版上差异较大,务必保留一个逃生会话测试通过后再固化。

验证加固效果与排错

完整配置后做一次系统核验:

sudo sshd -t                                   # 语法检查
sudo sshd -T | grep -iE 'permitrootlogin|passwordauthentication|per-source'  # 核对生效值
sudo systemctl reload ssh
journalctl -u ssh -f                           # 观察登录/惩罚日志
sudo fail2ban-client status sshd               # 查看封禁列表

从客户端侧排错用 -vvv 看协商过程:

ssh -vvv blog

若配置导致登录失败,应急回滚:把 drop-in 文件改名或删除 → sudo sshd -tsudo systemctl reload ssh → 用保留的逃生会话重新登录。

顺带一提,升级到 10.4 后 sshd -T 输出的指令名改为混合大小写(如 PubkeyAuthentication),如果写脚本解析过输出,注意这个不兼容变更。

小结

把本文的清单浓缩成一张验收表:

关键项状态
认证方式Ed25519 密钥 + PasswordAuthentication no
账户控制PermitRootLogin no + AllowUsers 白名单
会话限制MaxAuthTries 3 + LoginGraceTime 30 + 空闲超时
攻击面禁用全部转发、LogLevel VERBOSE
内置防爆破PerSourcePenalties + 豁免清单
网络兜底Fail2Ban + 防火墙/安全组限源
进阶TOTP 双因素(可选)

对照 OpenSSH 发布说明 保持版本更新,定期检查 journalctl -u ssh 里的异常登录尝试,这套配置足以挡住绝大多数公网扫描与暴力破解。

参考资料

发表评论

暂无评论,快来抢沙发吧!