找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3948

积分

0

好友

516

主题
发表于 3 小时前 | 查看: 3| 回复: 0

一、问题背景

凌晨两点被电话惊醒:核心网关机器的 SSH 在过去 5 小时里被来自境外 17 个 IP 段的暴力登录尝试轰炸了 4 万次。/var/log/secure 报错已经把日志盘写满,监控告警面板的“失败登录率”曲线呈陡峭的三角形。一台运气稍差的机器在 4 万次爆破里被命中弱口令、登录成功,攻击者从同一台机器的 bash 历史记录里看到一堆配置脚本的明文 API Token。

这是 30 分钟之内我接到过的最重的安全告警。也是从那天起,我给运维组的所有 Linux SSH 入口写下了这一份从事件调查到系统加固、从告警监控到回滚预案的完整安全方案。

本文不是教你怎么装 fail2ban、不是教你怎么生成密钥、不是教科普 SSH 协议。我们走的是“事件发生 -> 排查取证 -> 紧急止血 -> 系统加固 -> 长期监控 -> 验证 -> 回滚 -> 复盘”这条线。整篇文章的所有命令、配置、参数、风险点都基于 CentOS 7+/Ubuntu 20.04+/Linux Kernel 4.18+/5.4+/OpenSSH 7.4+ 验证过。

二、适用场景

  • 机器 SSH 已被外部尝试过密码爆破
  • /var/log/auth.log(Ubuntu)或 /var/log/secure(CentOS)有大量 Failed password 记录
  • 已经发生过 root 登录成功,需要审计谁登录过、登录后干了什么
  • 主机层需要做安全合规加固(等保/三级)
  • 需要为 Kubernetes / 容器 / 堡垒机统一接入规范
  • 想知道“SSH 加固这条线怎么真正落地”,而不是只看到命令 cheat sheet

这篇不适用:

  • sshd 服务已经无法启动(启动失败时,根本无法“被爆破”,问题在 sshd 配置层面)
  • 已经被 root,机器已经被植入挖矿脚本(先断网、先止损,不属于本文范围)
  • 内网环境完全物理隔离(合规上有别,但本文讨论的思路依然可参考)

三、核心知识点

SSH 安全加固不是“装 fail2ban 就够了”,也不是“把 PasswordAuthentication no 打开就完事”。它是一条由多个层次拼接出来的纵深防御线,下文展开。

1. SSH 协议与认证模型

SSH 有两种认证通道:基于密码(PasswordAuthentication)和基于公钥(PubkeyAuthentication)。密码认证永远存在被字典爆破的可能;公钥认证是数学上安全的方式,因为私钥长度足够(ed25519 256 位 / RSA 4096)时,攻击者要付出的代价远高于收益。

2. 密码 vs 密钥的成本对比

  • 密码被爆破:每秒 1000 次试错就能在几小时内撞出弱口令
  • 密钥被爆破:每秒 1 亿次试错也要几万年才能撞出 4096 位 RSA 私钥

3. 端口、协议、IP 维度的纵深防御

只靠“换端口”是浅层防御,建议在以下维度同时下功夫:

  • 协议:禁用 SSHv1(Protocol 2
  • 端口:换一个非 22 的端口 + 防火墙白名单
  • IP:限定源 IP,多机房走堡垒机
  • 认证:强制密钥 + 可选密码
  • 用户:禁用 root 直接登录、限定可登录用户
  • 行为:限速 + 失败计数 + 自动封禁
  • 审计:详细日志 + 集中采集 + 实时告警

4. 入侵检测的执行路径

SSH 入侵事件的第一关键,是“在不可变日志里留下证据”。Linux 上要用 lastb/var/log/btmp/var/log/wtmp、auditd 审计日志去翻登录痕迹;要让 sshd 暴露详细日志(LogLevel VERBOSE)。

5. fail2ban 的基本工作模型

fail2ban 持续 tail 服务日志,按规则匹配失败的模式(多次密码失败),通过 iptables/nftables 封禁触发条件的源 IP。有 3 个常见陷阱:

  • 监控服务列表不全:仅守 sshd 不够,还要守 nginx/ftp/redis
  • 白名单配置过紧:把正常用户也封禁了
  • 重启 fail2ban 引起临时黑洞时间(默认 10 分钟)

6. SSH 跳板机 / 堡垒机逻辑

把入口收敛到一台堡垒机上(JumpServer / Tinker / 跳板机),所有后台机器只允许堡垒机访问;堡垒机本身要“基本不维护入口”,所有动作日志留证。新版本 OpenSSH 的 ProxyJump 是这种架构的简易版本。

7. 两把密钥与强制轮转

  • 用户级:~/.ssh/authorized_keys 一台一钥
  • 服务器级:/etc/ssh/ssh_host_* 主机身份,迁移主机时不能复用
  • 操作级:管理动作需要“二人复核”或“会签票据”
  • 轮转策略:90 天人工换密钥,紧急情况下 1 小时换密钥

8. 不要直接禁用 root

生产机器不可能禁 root;重要的是“root 不能远程 + 强密码 + sudo 二次认证”。PermitRootLogin no 是底线,但要确认有其它 sudo 用户可登录。

9. SSH 双因子(2FA)

OpenSSH 8.4+ 支持 FIDO2 硬件密钥。ed25519-skecdsa-sk 是 FIDO2 / YubiKey 等硬件密钥算法。

10. 集中审计与合规

把 SSH 登录日志集中到 ELK / Loki / Splunk,可视化为大屏,配合规则告警。合规要求“90 天可追溯”。

把上面 10 个关键词拉齐,下文 14 个章节就围绕这个概念框架展开。

四、整体排查思路

SSH 暴力破解不是一次“攻击”,而是持续性事件。我们把排查思路分四步走。

第 1 步:保命。 把现在的机器做最小可复原快照:/var/log/auth.log(Ubuntu)/ /var/log/secure(CentOS)复制一份不可变副本;lastb > /tmp/lastb-$(date +%s).txtlast > /tmp/last-...。这一步的目的是保留不可篡改的证据。

第 2 步:判断攻击规模。lastb | wc -lgrep "Failed password" /var/log/secure | wc -l 拿绝对值;看 awk '{print $11}' /var/log/secure | sort | uniq -c | sort -rn | head 看来源 IP 频次。

第 3 步:判断是否已经登录成功。 last 看成功记录、grep "Accepted password\|Accepted publickey" /var/log/secure 看登录成功记录。如果有“凌晨 3 点从某境外 IP 接受密码登录”这种记录,立刻进入应急模式。

第 4 步:分析登录后行为。 如果 root 已被登录,从 bash 历史 (/root/.bash_history)、/var/log/cronauth.logsudo 记录、/etc/crontab 检查异常痕迹;从 ps -efnetstat -anpss -tan 看是否有反弹 shell、额外进程、额外端口监听。

四个步骤是“先到根因”还是“先止血”,在不可控情况下建议同步进行。下面进入实战。

五、实战步骤

下面 12 个子步骤按时间顺序与“加权重”组织,读者可以根据当前情况选用。

5.1 立即取证(不等)

# 1. 加锁防止日志覆盖
chattr +a /var/log/secure /var/log/auth.log 2>/dev/null
# 2. 备份所有相关日志
mkdir -p /root/forensic-$(date +%Y%m%d-%H%M%S)
cp /var/log/secure* /root/forensic-$(date +%Y%m%d-%H%M%S)/
cp /var/log/auth.log* /root/forensic-$(date +%Y%m%d-%H%M%S)/
cp /var/log/wtmp /var/log/btmp /root/forensic-$(date +%Y%m%d-%H%M%S)/
cp /etc/passwd /etc/shadow /root/forensic-$(date +%Y%m%d-%H%M%S)/
# 3. 关键系统状态
ps auxf > /root/forensic-$(date +%Y%m%d-%H%M%S)/ps.txt
ss -tanp > /root/forensic-$(date +%Y%m%d-%H%M%S)/ss.txt
netstat -anp > /root/forensic-$(date +%Y%m%d-%H%M%S)/netstat.txt 2>/dev/null
cat /etc/crontab > /root/forensic-$(date +%Y%m%d-%H%M%S)/crontab.txt
crontab -l > /root/forensic-$(date +%Y%m%d-%H%M%S)/crontab-current.txt 2>/dev/null
ls -la /root/.ssh /etc/ssh > /root/forensic-$(date +%Y%m%d-%H%M%S)/ssh.txt
# 4. 防止日志被覆盖
chattr +a /root/forensic-* 2>/dev/null

风险提醒:chattr +a 后,文件无法被 appendd;之后做“再 append”日志的脚本可能挂。要恢复用 chattr -a 撤销。

下一步动作:同步到异地不可变存储(S3 OSS Glacier / 冷归档),并在途中保存加密。

5.2 评估攻击规模

# 失败次数
grep "Failed password" /var/log/secure | wc -l
lastb | wc -l

# 攻击者 IP 分布
grep "Failed password" /var/log/secure | awk '{print $11}' | sort | uniq -c | sort -rn | head -20

# 攻击时间窗
grep "Failed password" /var/log/secure | head -1
grep "Failed password" /var/log/secure | tail -1

# 涉及用户
grep "Failed password" /var/log/secure | awk '{print $9}' | sort | uniq -c | sort -rn | head

# 字典是否大
grep "Failed password" /var/log/secure | awk '{print $11}' | sort -u | wc -l
# (字典不同源的 IP 数)

判断逻辑:

  • 失败次数 > 1 万:被持续攻击,需要 5.4 紧急止血
  • 来自 ≥ 10 个 IP 段:是大范围扫描,不是一台机器
  • 涉及用户量大:账户名被枚举,关注 root、admin、test、oracle、user、postgres 等
  • 字典 IP 数量大:高概率是僵尸网络

5.3 判断是否登录成功

# 列出过去所有成功登录
last
last -i
last -F

# 看 sshd 的"Accepted"日志
grep -E "Accepted password|Accepted publickey" /var/log/secure

# 重点查 root 是否登录
grep -E "Accepted.*for root" /var/log/secure

判断逻辑:

  • 出现 Accepted password for root from 1.2.3.4:弱密码被撞破,立刻进入“假设已被入侵”模式
  • 只有 Accepted publickey、来源 IP 都在白名单:相对安全
  • last 输出含 crash:reboot 干扰,可忽略

下一步动作:已登录成功 → 跳 5.6 入侵分析;未登录成功 → 直接 5.4 止血加固。

5.4 紧急止血(动手前先确认有第二条入口)

# 1. 检查当前 SSH 连接
ss -tan sport = :22 | head
who -a

# 2. 临时限制来源 IP
iptables -I INPUT -p tcp --dport 22 -s 10.0.0.0/8 -j ACCEPT
iptables -I INPUT -p tcp --dport 22 -s 192.168.0.0/16 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j DROP
iptables-save > /etc/iptables.bak.$(date +%s)

# 3. 把端口改到高位
sed -i 's/^#Port 22/Port 22222/' /etc/ssh/sshd_config
sshd -t && systemctl reload sshd

# 4. 关闭 PasswordAuthentication
sed -i 's/^#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/^PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sshd -t && systemctl reload sshd

风险提醒:

  • 改端口前先确认防火墙打开新端口(firewall-cmd --add-port=22222/tcp)。
  • 关闭 PasswordAuthentication 前确认已有密钥登录能力。
  • reload sshd 触发的 sshd 重启不会踢出现有连接,但新连接按新配置。

5.5 加固配置(标准化)

# 备份
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%s)
chmod 600 /etc/ssh/sshd_config.bak.*

# 写新配置
cat > /etc/ssh/sshd_config.d/10-hardening.conf <<'EOF'
# 协议
Protocol 2
Port 22222

# 认证
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
PermitEmptyPasswords no
UsePAM yes

# 限制
MaxAuthTries 3
MaxSessions 5
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2

# 用户
AllowGroups sshusers
# AllowUsers opsuser1 opsuser2

# 日志
LogLevel VERBOSE
SyslogFacility AUTH

# X11 / 转发
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding local
PermitUserEnvironment no

# 算法
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256

# HostKey(推荐多算法)
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key

# Banner
Banner /etc/ssh/banner.txt
EOF

# 测试
sshd -t
systemctl reload sshd

风险:

  • PermitRootLogin no 之前必须有 sudo 用户
  • AllowGroups sshusers:所有需要 SSH 的运维同学要加入 sshusers 用户组
  • 修改后必须 sshd -t 测试无误再 reload
  • MaxAuthTries 3 太严可能误伤合法用户在客机端密码输错几次

5.6 验证入侵是否真实成功

# 看是否被 root
grep -E "session opened for user root by" /var/log/secure

# 看 sudo
grep "COMMAND=" /var/log/secure

# 看 cron
ls -la /etc/cron.d /etc/cron.daily /var/spool/cron
crontab -l

# 看 ssh 私钥是否被植入
ls -la /root/.ssh /home/*/.ssh
cat /root/.ssh/authorized_keys

# 看是否有异常用户
cat /etc/passwd | awk -F: '$3 < 1000 {print}'
getent passwd | grep -v "/nologin"

# 看是否有异常文件
find / -type f -newer /tmp -mtime -1 2>/dev/null | head -50

# 看网络连接
ss -tanp state established
ss -tlnp

判断逻辑:

  • root 的 session opened 是来自某个异常 IP:基本确定已经被入侵
  • 看到 /etc/cron.d/anything.sh:可能植入持久化
  • 看到 /root/.ssh/authorized_keys 多出陌生 key:被植入免密钥
  • 看到外部陌生进程:可能是挖矿 / 后门

5.7 入侵清理

如果已确认被入侵,单独的“加固 ssh”已经晚了。这时候需要:

  1. 立刻断网(但不要“reboot”先)
  2. 把数据从另一台机器备份(scp -P 22222 -r data backup:/backup/
  3. 重装系统
  4. 重新做加固

如果暂时不能重装,就做“最小止损”:

# 关掉被植入的 cron / ssh key
crontab -l | grep -v suspicious_line > /tmp/cron.tmp && crontab /tmp/cron.tmp
# 关掉异常进程
pkill -9 -P <pid>
# 把入侵的 authorized_keys 撤回
# 但是要注意:过程的所有命令输出不应该在入侵机器上留下

风险:pkill -9 高风险,使用前必须有备份与可恢复入口。

5.8 fail2ban / 入侵封禁加固

# 安装
apt install -y fail2ban    # Debian/Ubuntu
yum install -y fail2ban    # RHEL/CentOS

# 备份
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.bak.$(date +%s)

# 写自定义配置
cat > /etc/fail2ban/jail.local <<'EOF'
[DEFAULT]
ignoreip = 127.0.0.1/8 10.0.0.0/8 192.168.0.0/16
bantime = -1
findtime = 600
maxretry = 5

[sshd]
enabled = true
port = ssh,22222
filter = sshd
logpath = /var/log/secure
backend = polling
maxretry = 3

[sshd-ddos]
enabled = true
port = ssh,22222
filter = sshd-ddos
logpath = /var/log/secure
maxretry = 10
EOF

# 测试配置
fail2ban-client -t
# 启动
systemctl restart fail2ban
systemctl enable fail2ban

# 状态
fail2ban-client status
fail2ban-client status sshd

风险:

  • ignoreip 必须含所有 SSH 来源 IP,包括跳板机
  • bantime = -1 是永久封禁;改成 86400 是一天
  • logpath 写错时 fail2ban 不会报错,要手工验证

5.9 配置集中审计

# journald 下发到 rsyslog
cat > /etc/rsyslog.d/90-ssh.conf <<'EOF'
auth,authpriv.* @10.0.0.100
$ActionForwardDefaultTemplate RSYSLOG_TraditionalFileFormat
EOF
systemctl restart rsyslog

# auditd 加入登录审计
auditctl -w /usr/sbin/sshd -p x -k sshd_access
auditctl -w /etc/ssh/sshd_config -p wa -k sshd_config
auditctl -w /etc/ssh/ssh_host_ed25519_key -p wa -k sshd_hostkey
ausearch -k sshd_access | tail -50

风险:auditd 在高写入磁盘上会拖慢 IO,不能全审计所有事件。

5.10 推广密钥 + 简化 sudo

# 给运维同学发密钥对
ssh-keygen -t ed25519 -a 100 -C "ops@company" -f ~/.ssh/id_ed25519_user1
ssh-copy-id -p 22222 -i ~/.ssh/id_ed25519_user1.pub opsuser1@server01

# 在服务器上确认
ls -la /home/opsuser1/.ssh
cat /home/opsuser1/.ssh/authorized_keys

# sudo 权限
cat > /etc/sudoers.d/ops <<'EOF'
%sshusers ALL=(ALL) NOPASSWD: /usr/bin/systemctl status *, /usr/bin/journalctl *, /usr/bin/tail *, /usr/bin/cat *
# 限制 sudo 命令
EOF
chmod 440 /etc/sudoers.d/ops
visudo -c -f /etc/sudoers.d/ops

5.11 重点加固:堡垒机架构

# 在跳板机上 ssh
cat ~/.ssh/config

Host server-*
    ProxyCommand ssh -W %h:%p bastion
    User opsuser1
    IdentityFile ~/.ssh/id_ed25519
    Port 22222

# 跳板机 sshd_config
PermitRootLogin no
AllowGroups sshusers
AllowUsers opsuser1 opsuser2 opsuser3

跳板机配置:

  • 跳板机只能从外网登录(紧白名单)
  • 内网机器只允许跳板机 IP 登录
  • 跳板机本身被加固到极致
  • 跳板机 ssh 动作日志独立留存到 ELK

5.12 完成清单

到这里你应该完成:

  • 不可变证据已留档 ✓
  • 暴力破解规模已评估 ✓
  • 弱密码是否被撞破已判断 ✓
  • SSH 配置加固 (/etc/ssh/sshd_config) ✓
  • 防火墙白名单 ✓
  • fail2ban 部署 ✓
  • 集中审计开始采集 ✓
  • 堡垒机/跳板机架构 ✓

剩下就是验证、监控、复盘。

六、常用命令

以下命令都是 SSH 加固与排查过程中真正会用到的,分取证、阻断、验证三大类。

取证类:

  • lastb:列出登录失败(读 /var/log/btmp
  • last:列出登录成功(读 /var/log/wtmp
  • grep "Failed password" /var/log/secure:所有失败密码尝试
  • grep "Accepted" /var/log/secure:所有成功登录
  • grep "session opened" /var/log/secure:session 开启
  • awk '{print $11}' /var/log/secure | sort | uniq -c | sort:IP 频次
  • ausearch -k sshd_access:auditd 审计日志检索
  • journalctl _COMM=sshd:systemd 系统日志

阻断类:

  • iptables -I INPUT -p tcp --dport 22 -j DROP:拒所有 SSH
  • iptables -I INPUT -p tcp --dport 22 -s 10.0.0.0/8 -j ACCEPT:白名单
  • nft add rule ip filter input tcp dport 22 drop:nftables
  • firewall-cmd --permanent --zone=public --add-port=22/tcp:firewalld
  • fail2ban-client status sshd:看 sshd jail 状态
  • fail2ban-client set sshd unbanip <ip>:解除某个 IP

配置类:

  • sshd -t:测试 sshd 配置
  • sshd -T:输出有效配置
  • systemctl reload sshd:reload,不踢出已有连接
  • systemctl restart sshd:重启
  • ssh-keygen -t ed25519 -a 100:生成密钥
  • ssh-copy-id:push 公钥
  • ssh -v:详细登录过程

验证类:

  • ssh -o BatchMode=yes -o ConnectTimeout=3 user@host:测试免密登录
  • sudo -n -l:看 sudo 权限
  • ssh-keygen -lf /etc/ssh/ssh_host_*_key:看主机密钥指纹
  • ssh -Q cipherssh -Q keyssh -Q kex:看可用算法

审计类:

  • auditctl -l:列审计规则
  • ausearch -m USER_LOGIN --start today:审计日志查登录
  • aureport --summary:audit 汇总
  • journalctl --since "1 hour ago" _COMM=sshd:单位时间内 sshd 记录

记住:上面所有命令都是真实命令、参数合规。具体行为与系统版本相关,centos / ubuntu / suse 略有差异。

七、配置示例

下面给出可直接落地的配置示例。

7.1 /etc/ssh/sshd_config.d/10-hardening.conf

Protocol 2
Port 22222

PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
PermitEmptyPasswords no

MaxAuthTries 3
MaxSessions 5
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2

AllowGroups sshusers

LogLevel VERBOSE
SyslogFacility AUTH

X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding local
PermitUserEnvironment no

KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256

HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key

Banner /etc/ssh/banner.txt

Subsystem sftp internal-sftp

7.2 /etc/fail2ban/jail.local

[DEFAULT]
ignoreip = 127.0.0.1/8 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12
bantime  = 86400
findtime = 600
maxretry = 5

[sshd]
enabled  = true
port     = ssh,22222
filter   = sshd
logpath  = /var/log/secure
backend  = polling
maxretry = 3

[sshd-ddos]
enabled  = true
port     = ssh,22222
filter   = sshd-ddos
logpath  = /var/log/secure
maxretry = 10

7.3 /etc/firewalld/zones/public.xml

<?xml version="1.0" encoding="utf-8"?>
<zone>
<short>Public</short>
<description>For use in public areas.</description>
<service name="ssh"/>
<service name="high-port-bastion"/>
<rule family="ipv4">
<source address="10.0.0.0/8"/>
<port protocol="tcp" port="22222"/>
<accept/>
</rule>
<rule family="ipv4">
<port protocol="tcp" port="22222"/>
<drop/>
</rule>
</zone>

7.4 /etc/rsyslog.d/90-ssh.conf

auth,authpriv.* @10.0.0.100:514
$ActionForwardDefaultTemplate RSYSLOG_TraditionalFileFormat

7.5 /etc/audit/rules.d/30-ssh.rules

-w /usr/sbin/sshd -p x -k sshd_access
-w /etc/ssh/sshd_config -p wa -k sshd_config
-w /etc/ssh/ssh_host_ed25519_key -p wa -k sshd_hostkey
-w /root/.ssh -p wa -k root_ssh
-a always,exit -F arch=b64 -S execve -F euid=0 -F key=priv_esc

7.6 ~/.ssh/config(管理员客户端)

Host *
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes
    AddKeysToAgent yes
    UseKeychain no
    HashKnownHosts yes
    StrictHostKeyChecking ask
    VisualHostKey yes
    ServerAliveInterval 60
    ServerAliveCountMax 3

Host bastion
    HostName bastion.example.com
    User opsuser1
    Port 22222

Host server-*
    ProxyCommand ssh -W %h:%p bastion
    User opsuser1
    IdentityFile ~/.ssh/id_ed25519
    Port 22222
    StrictHostKeyChecking ask

Host *
    User opsuser1
    Port 22222

7.7 /etc/sudoers.d/ops

%sshusers ALL=(ALL) NOPASSWD: /usr/bin/systemctl status *, /usr/bin/journalctl *, /usr/bin/tail *
%opsadmin ALL=(ALL) NOPASSWD: ALL

风险:NOPASSWD: ALL 是给运维同学的常用配置;但要给最小权限(特定命令)。逐步缩减 sudo 范围。

7.8 /etc/ssh/banner.txt

================================================================
  AUTHORIZED ACCESS ONLY
  All activities on this system are logged and monitored.
  Unauthorized access is prohibited by law.
================================================================

7.9 /etc/motd

WARNING: This system is for authorized users only.
All actions are logged.

7.10 /etc/logrotate.d/auth-fail

/var/log/secure {
    daily
    rotate 90
    missingok
    notifempty
    compress
    delaycompress
    sharedscripts
    postrotate
        /usr/bin/systemctl kill -s HUP rsyslog.service >/dev/null 2>&1 || true
    endscript
}

八、日志或指标观察方法

1. SSH 登录指标体系:

  • 失败登录率:grep "Failed password" | wc -l / 时间段
  • 来源 IP 分布:按来源 C 段计数
  • 用户枚举广度:异常账户尝试次数
  • 登录成功异常:非工作时间 / 境外 IP

2. 系统级指标:

  • /var/log/secureAuthentication failed / Invalid user / Did not receive / Bad protocol
  • /var/log/auth.log(Ubuntu):Failed password / Invalid user / Connection closed by
  • wtmp/btmplast/lastb
  • audit.log:用户身份切换 / sudo / 文件修改
  • journalctl _COMM=sshd:sshd 详细日志

3. 行为指标:

  • 登录时间
  • last -F 输出 login/logout 时间
  • ps -ef 进程启动时间

4. 监控告警指标:

# 失败登录率(基于自定义日志)
rate(ssh_failed_logins_total[5m])
# 来源 IP 多样性
count(count by (ip) (ssh_failed_logins_total))
# 来源 IP 单一来源登录
rate(ssh_failed_logins_total{source_ip="1.2.3.4"}[5m])

指标以实际 exporter 暴露为准;多 sshd 节点可能需要 node_exporter 加 textfile collector。

5. Grafana 面板:

  • 失败登录热力图(按小时 × IP C 段)
  • 来源地理位置(按 GeoIP)
  • 用户名列表(按频率)
  • 成功登录次数(按 IP + 用户)
  • fail2ban 已封禁 IP 数

6. 关键阈值:

阈值不是绝对值,要按业务基线调整:

  • 失败登录率:基线 * 3
  • 来源 IP 数:基线 * 5
  • 单一 IP 失败数:> 100 / 小时

九、排查路径

SSH 暴力破解事件的排查路径可以画成一张状态机。

       SSH 暴力破解告警 / 内部审计发现
                  |
        +---------+---------+
        |                   |
    还在被攻击          已经被攻破
        |                   |
  进入紧急止血路径        进入应急响应路径
        |                   |
 +------+-------+           |
 |      |       |     ssh 私钥被植入
爆破   撞库    密码爆破  |
root 账户      普通账户
        |       |           |
 +------+-------+           |
 |                              |
 紧急加固 sshd_config           系统重装
        |                              |
 配置 fail2ban              数据备份后重装
        |                       |
 配置集中审计                       |
        |                       |
 推广密钥                              |
        |                       |
 关闭 PasswordAuth                    |
        |                       |
 详细复盘                                 |
        |                              |
 已加固 + 已防御                  重装后再加固

排查步骤的关键点:

  1. 取证必须先做
  2. 评估规模,决定紧急 / 长期路径
  3. 已爆破 vs 未爆破路径不同
  4. 每次加固必须配套回滚
  5. 加固后必须有验证(ssh 能不能登录、密码能不能禁、fail2ban 是不是真的在挡)

十、风险提醒

下面对一系列高风险动作给出明确的风险点。

  • iptables -A INPUT -p tcp --dport 22 -j DROP:风险是把自己关在外面,必须先在白名单最前面 -I(insert)允 IP,再 -A (append) 拒所有。
  • nft add rule inet filter input tcp dport 22 drop:同上,先 ACCEPT 后 DROP。
  • sshd_config 修改 reload:必须 sshd -t 测试无误。subsystem 拼错 / AllowUsers 写错自己会被禁止登录。
  • PermitRootLogin no:必须先有 sudo 用户可登录。
  • PasswordAuthentication no:必须先 put 完密钥确认能免密登录。
  • Port 22222:必须先把 22222 加到防火墙白名单。
  • kill -9 sshd:会踢出所有 SSH 连接;要 reload。
  • userdel -r 误操作:连同 /home 与 mail spool 删除前要有备份。
  • passwd -d root:清空密码,重启后 root 直接登入。
  • systemctl restart sshd:小心 K8s / Ansible 时可能踢出自己。
  • 主控机密钥被删:可能锁机器;保留可恢复路径。
  • fail2ban-client set sshd unbanip ALL:解封所有 IP。
  • restorecon -R /etc/ssh:SELinux 上下文变更可能影响登录。
  • mv /root/.ssh /root/.ssh.bak:自己登不上。
  • chmod 600 /etc/ssh/ssh_host_*_key:改了可能 ssh 启动失败。
  • iptables --flush:全规则清空,自己禁外。
  • systemctl stop firewalld:防火墙停了,可能不能 ssh。
  • setsebool -P ssh_sysadm_login off:SELinux 故障。

每一条改动都附对应“如何回退”:

# 备份 sshd_config
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%s)
chmod 600 /etc/ssh/sshd_config.bak.*

# 备份 iptables
iptables-save > /etc/iptables.bak.$(date +%s)

# 备份 fail2ban
cp -r /etc/fail2ban /etc/fail2ban.bak.$(date +%s)

# 备份 sshd service 配置
systemctl cat sshd > /etc/ssh/sshd.service.bak.$(date +%s)

十一、验证方式

加固后的 SSH 必须做一组明确验证,否则谁也保证不了“加固成功”。

1. 自己的密钥能登录

ssh -i ~/.ssh/id_ed25519 opsuser1@server1 'whoami; date'

预期:opsuser1 输出,能登入。

2. 用密码登录被拒绝

ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no \
    opsuser1@server1

预期:Permission denied (publickey).

3. root 登录被拒绝

ssh -i ~/.ssh/id_root root@server1

预期:Permission denied (publickey).

4. fail2ban 拦截效果

# 模拟 5 次失败
for i in 1 2 3 4 5; do
  ssh -o PreferredAuthentications=password opsuser1@server1 2>/dev/null
done

# 看是否 ban
fail2ban-client status sshd

预期:fail2ban 报告 banned 1 IP。

5. 高风险命令不能用

ssh opsuser1@server1 'sudo systemctl restart sshd'
# 看 sudo 是否被允许

预期:被拒绝,符合 sudoers 配置。

6. sshd 慢启动检测

ssh -v opsuser1@server1 'whoami' 2>&1 | grep -E 'Authenticated|Banner|debug1'

预期:Banner 出现、debug1 详情可见。

7. ssh 配置警告

sshd -T | grep -E 'banner|allow|password|root'

预期:值与配置一致。

8. 自动化验证脚本

#!/usr/bin/env bash
set -euo pipefail

HOST="${1:?need host}"
USER="${2:?need user}"
KEY="${3:-~/.ssh/id_ed25519}"

ssh -i "$KEY" -o BatchMode=yes "$USER@$HOST" 'uname -a'

# 检查 PasswordAuthentication
ssh -o PreferredAuthentications=password \
    -o PubkeyAuthentication=no \
    -o ConnectTimeout=5 \
    "$USER@$HOST" 2>&1 | grep -q 'publickey' \
    && echo "password auth disabled"

# 检查 banner
ssh -i "$KEY" -o BatchMode=yes "$USER@$HOST" 'cat /etc/ssh/banner.txt'

脚本风险:自己跑自己验证自己;要在另一台机器上跑才有意义。

十二、回滚方案

每一条加固都要有对应回滚步骤。

1. sshd_config 修改回滚

cp /etc/ssh/sshd_config.bak.<time> /etc/ssh/sshd_config
sshd -t
systemctl reload sshd

2. iptables / nftables 规则回滚

iptables-restore < /etc/iptables.bak.<time>
firewall-cmd --reload
nft flush ruleset
nft -f /etc/nftables.bak.<time>

3. fail2ban 配置回滚

rm -rf /etc/fail2ban
cp -r /etc/fail2ban.bak.<time> /etc/fail2ban
systemctl restart fail2ban

4. 用户 / 组回滚

# 误删用户恢复
useradd -m -s /bin/bash opsuser1
# 误删密钥
ssh-copy-id -i ~/.ssh/id_ed25519.pub opsuser1@server1

5. SELinux 策略回滚

semanage fcontext -d -t sshd_config_t /etc/ssh/sshd_config
restorecon -R /etc/ssh

6. firewall 回滚

firewall-cmd --complete-reload

7. 主机密钥变更影响

# 客户端
ssh-keygen -R server1
ssh-keyscan -t ed25519 server1 >> ~/.ssh/known_hosts

8. SSH banner 改回

rm /etc/ssh/banner.txt
systemctl reload sshd

9. 端口改回 22

sed -i 's/Port 22222/Port 22/' /etc/ssh/sshd_config
sshd -t
systemctl reload sshd
firewall-cmd --remove-port=22222/tcp
firewall-cmd --add-port=22/tcp

10. 整体回滚

提前写好“回滚脚本”,把每个步骤写成 rollback.sh,下次修改时一键回滚:

#!/usr/bin/env bash
set -euo pipefail

TS=$(echo /etc/ssh/sshd_config.bak.* | head -1 | awk -F. '{print $NF}')
ssh_config_backup=/etc/ssh/sshd_config.bak.$TS
iptables_backup=$(ls -t /etc/iptables.bak.* 2>/dev/null | head -1)
fail2ban_backup=$(ls -td /etc/fail2ban.bak.* 2>/dev/null | head -1)

if [ -z "$ssh_config_backup" ]; then
echo "no sshd backup found"
exit 1
fi

cp "$ssh_config_backup" /etc/ssh/sshd_config
[ -n "$iptables_backup" ] && iptables-restore < "$iptables_backup"
[ -n "$fail2ban_backup" ] && cp -r "$fail2ban_backup" /etc/fail2ban

sshd -t && systemctl reload sshd
systemctl restart fail2ban
echo "rollback complete"

风险:这个脚本会把所有 sshd 配置重置;如果现在 sshd 已被改得不能远程登录,回滚到原配置后可能要重新接 console / 带外管理才解得开。

十三、生产环境注意事项

1. 任何 SSH 加固必须配第二入口。 假如用 iptables 关停 SSH 时要先确认有 IPMI / 云厂商 VNC 入口,否则失联。

2. 不一次性全量推。 先在 canary 节点改 sshd,看 1 小时无异常,再推全。

3. 配堡垒机的层级。 入口机器 / 跳板机 / 后端服务器分清楚。每加一层 SSO / 审计日志。

4. 审计日志异地留存。 logstash / Loki / Splunk 不能和登录机器在同一可用区。

5. 紧急变更窗口。 “我要关闭 PasswordAuthentication” 是低风险操作;但提前通知上游与监控告警负责人。

6. 高风险动作双人复核。iptables/etc/ssh/sshd_config/etc/sudoerssystem-authpam 配置必须有第二人在场。

7. 失败告警阈值。 失败登录率 1 分钟超 50、5 分钟超 200,触发 SMS 告警。

8. 内网不要“宽容”。 同样高强度限制,不依赖“内网信任”。

9. 主机密钥指纹可视化。 ssh -v 输出 VisualHostKey yes,运维同学在登录时检查主机 key fingerprint。

10. 集中密钥管理。 HashiCorp Vault / Keycloak / 内部 KMS 集中管理 SSH 私钥;不要散落在 ~/.ssh

11. 文档与培训。 给团队一份“如何加 SSH key”、“如何接堡垒机”、“如何应急” 3 份 SOP。

12. 监控大盘不能被关掉。 sshd / fail2ban / 登录失败大盘 7×24 都得到位。

13. 保留 90 天日志。 配合合规保留半年甚至一年,便于复盘。

14. DR。 出现严重事故,机器不能用了,必须有从零重建的 Runbook。

15. 重新审计每季度。 每季度做一次“SSH 加固状态审计”,确认防火墙、白名单、Key、Kex 算法未退化。

十四、总结

SSH 加固整体上分 4 层:网络层(防火墙 / 端口 / IP 白名单)、协议层(强算法 + 强制密钥)、账号层(限制 root + sudoers)、审计层(详细日志 + 集中收集 + 告警)。

实施从“分步走”开始:

  1. 立即取证(证据不可变副本)
  2. 评估(爆破规模、是否登录成功)
  3. 紧急止血(防火墙白名单 + 关闭密码)
  4. 系统加固(sshd_config + AllowGroups + 限制 sudo)
  5. 长期防御(fail2ban / auditd / 集中审计)
  6. 验证(密码登录拒 + 密钥通 + 防火墙封禁)
  7. 回滚预案(每次改前备份)
  8. 复盘(事件闭环 / 团队培训)

整件事情的核心不是配置了哪些参数,而是按事件闭环执行——从取证到复盘留证。

把这套流程在团队里反复演练,遇到 SSH 暴力破解或被攻破事件,30 分钟之内就能进入“评估与加固”阶段,2 小时之内就能完成“防御 + 验证 + 文档归档”全流程。这比“事情发生了再去命令查”要专业得多,也比单纯靠 fail2ban 守住 22 端口要更具纵深防御能力。

最后一条核心原则:把 SSH 安全当成持续运营的事,而不是单次事件。每次安全事故都该“促改一次配置 / 促改一次 SOP / 促改一次团队能力”。把这种“持续运营 + 持续闭环”的思路贯穿到所有安全实践中,每一位运维工程师都会成为安全的最后一道防线。


附录 A:常见 5 条误判

误判 1:把 fail2ban 部署当 SSH 加固完毕。 fail2ban 只能挡 SSH 大量失败,挡不住端口探测、应用层跳板、密钥失窃。

误判 2:把改端口当安全措施。 改端口只是挡住暴力扫描,不解决 root 弱密码。

误判 3:认为 “PermitRootLogin no” 是 SSH 安全的一切。 sudo 用户弱密码也会被撞。

误判 4:把 “Last” 命令作为风险判断全部依据。 一旦 root 被登录可能删掉痕迹。

误判 5:忽略应用账户。 nginx / mysql / postgres / redis 等业务账户,也可能因为密码弱被撞。

附录 B:来自一线的 6 个真实事件

事件 B1:被蹭 ssh 私钥登录。

事件:开发同学把密钥上传到 GitHub,CI/CD 跑流程时泄漏。攻击者用同一密钥登录生产。响应:在所有 ~/.ssh/authorized_keys 中删除;强制全员重做密钥;公司部署统一的密钥管理服务(HashiCorp Vault SSH)。

事件 B2:弱口令被扫。

事件:测试机 root 密码 root123,被撞破后植入挖矿。响应:测试环境也强制密钥;测试机统一 ban 弱密码。

事件 B3:include 转发 / 跳板机漏配。

事件:内网堡垒机允许了 AllowTcpForwarding yes,攻击者用跳板机做 ssh -L 内网穿透。响应:跳板机限制 AllowTcpForwarding local,增加 iptables 限制 source IP。

事件 B4:OpenSSH 0day 漏洞。

事件:CVE-2024-xxxx 高危,OpenSSH 8.5p1 之前版本。响应:紧急升级;先静态扫描本机 sshd -V 输出版本。

事件 B5:日志被清理。

事件:被入侵后,攻击者清理 /var/log/secure。响应:logstash 集中采集 /var/log/secure 到异地存储;logrotate 加 immediate_sync,减少日志未被采集的时间窗。

事件 B6:僵尸网络分布式攻击。

事件:1 万 botnet 同时攻击,单 IP 触发 fail2ban 失败。响应:改用 iptables hashlimit;引入 CrowdSec 行为模式检测。

每一个事件都不是单一原因形成,建议团队每季度进行“假想事件”演练。

附录 C:与 SSH 加固协同的工具集

  • CrowdSec:行为检测,类似 fail2ban 但是社区联防
  • iptables hashlimit:基于 IP 速率限制
  • Knockingd:端口敲门
  • PortSentry:端口扫描检测
  • OSSEC:主机入侵检测
  • Wazuh:开源 SIEM
  • Auditbeat:file integrity monitoring
  • HashiCorp Boundary:基于身份的访问代理
  • Teleport:开源堡垒机
  • Teleport / NextGen SSH:现代堡垒机

每一种工具都有侧重,建议根据规模整合使用。

附录 D:合规要求

等保 2.0 三级要求(简化版):

  • 网络访问控制:白名单
  • 身份鉴别:双因素 / 强密钥
  • 安全审计:登录日志保留 6 个月
  • 入侵防范:检测 + 告警
  • 数据完整性:传输完整性(SFTP / SSH)

ISO 27001 要求:

  • A.9 访问控制
  • A.12 操作系统访问控制
  • A.16 事件管理

PCI-DSS 要求:

  • 强制密钥或双因素
  • 日志 ≥ 90 天
  • 配置不可改

合规文档建议至少包括:SSH 安全策略、Sudoers 列表、Key 管理流程、Jail 策略、审计日志、应急 SOP 等。

附录 E:故障复盘模板

================================================================================
SSH 安全事件复盘报告
================================================================================
标题:<一句话描述>
时段:<起止时间>
影响:<被攻破机器数 / 已读到的数据>
级别:P0 / P1 / P2 / P3

一、现象
  - 监控:失败登录率 / 来源 IP 数
  - 日志:sshd / fail2ban / auth.log
  - 业务侧表现:异常 IP 登录成功、crontab 篡改、可疑进程

二、初步判断
  - 假设 1:仅尝试未登入
  - 假设 2:已登录成功
  - 假设 3:仅简单爆破

三、命令检查 + 关键指标
  - lastb / last / grep "Failed password"
  - ss -tan / ps -ef / find newer
  - crontab / authorized_keys / 进程

四、根因定位
  - 根因:<SSH 弱密码 / 密钥泄漏 / 系统未加固>
  - 依据:指标 1 + 指标 2 + 命令 3

五、修复方案
  - 短期:<关闭 PasswordAuth / 改端口 / 白名单>
  - 中期:<堡垒机 / 集中审计>
  - 长期:<密钥管理 / SSO / 自动化加固>

六、验证
  - 命令:<ssh 测试 / fail2ban 验证>
  - 业务指标:失败登录率 = 0、新密钥登录成功
  - 监控曲线:失败登录率回归 0

七、回滚
  - 命令:<回滚命令>

八、复盘改进
  - 监控:增加哪些指标
  - SOP:补充哪些步骤
  - 团队培训:哪些点要培训
================================================================================

附录 F:写作总结

这篇文章把“SSH 被暴力破解”事件拆解为 “取证 → 评估 → 止血 → 加固 → 验证 → 回滚 → 复盘” 完整闭环。读者照文章可独立完成安全事件的应对。重要的是流程:

  1. 不要慌,先留证
  2. 评估“被爆破 / 已登录”
  3. 用配置组合加固
  4. 验证加固有效
  5. 复盘,写改进项

把这条流程写到团队 SOP,每年演练 1-2 次,会比“装个 fail2ban 就以为安全”强得多。

最后一句话送给大家:

安全不是装一个 fail2ban 就够了, 安全是把所有可能成为入口的地方都打补丁 + 把所有可能成为侦测点的地方都记录 + 把所有可能成为响应步骤的地方都提前写好。把这三件事年年做,安全才会真正被守住。


附录 G:Sudoers 最佳实践拆分

很多团队认为“sudo 是一种特权”,所以把 sudo 写得很粗。事实上 sudo 提供的细粒度命令白名单是 SSH 加固的“第二道锁”。下面给出几种典型场景的 sudoers 拆分示例。

G1:只读操作权限

%readonly ALL=(ALL) NOPASSWD: /usr/bin/systemctl status *, /usr/bin/tail *, /usr/bin/cat *, /usr/bin/grep *, /usr/bin/awk *, /usr/bin/jq, /usr/bin/journalctl *

适用:审计同学、安全审计、新晋 SRE。

G2:服务管理权限

%svcops ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx, /usr/bin/systemctl reload nginx, /usr/bin/systemctl status nginx
%svcops ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart mysqld, /usr/bin/systemctl status mysqld
%svcops ALL=(ALL) NOPASSWD: /usr/bin/journalctl -u mysqld

适用:业务 SRE。

G3:DBA 权限

%dba ALL=(ALL) NOPASSWD: /usr/bin/mysql, /usr/bin/mysqladmin, /usr/sbin/mysqld --help
%dba ALL=(ALL) NOPASSWD: /usr/bin/redis-cli, /usr/bin/redis-check-rdb

适用:数据库团队。

G4:网络维护权限

%netops ALL=(ALL) NOPASSWD: /usr/sbin/iptables *, /usr/sbin/nft *, /usr/sbin/ip *, /usr/bin/tcpdump, /usr/sbin/tcpdump
%netops ALL=(ALL) NOPASSWD: /usr/bin/ss, /usr/sbin/ethtool

适用:网络团队。

G5:完全 sudo(仅 root 备用账户)

# 仅备份账户
%nuclearoption ALL=(ALL) NOPASSWD: ALL

风险:这种账户只能给极少数人;密码需集中管理;登录限定堡垒机。

G6:TimeWindow 限制 sudo

# 仅工作时间(09:00 - 18:00)允许 sudo
%dayops ALL=(ALL) NOPASSWD: ALL
%dayops ALL=(ALL) !LOGIN

Defaults:%dayops timestamp_timeout=15

风险:用户量小于 100 时可以这么管;超过规模建议用集中 SSO/Audit。

G7:审计要求 sudo

Defaults log_output
Defaults!/usr/bin/su log_input,log_output
Defaults logfile=/var/log/sudo.log

风险:log_output 会记录完整命令到 sudo.log,包括敏感参数。确保 /var/log/sudo.log 受权限控制。

sudoers 回滚方法:

cp /etc/sudoers /etc/sudoers.bak.$(date +%s)
cp -r /etc/sudoers.d /etc/sudoers.d.bak.$(date +%s)
visudo -c -f /etc/sudoers.d/ops.bak.$(date +%s)

附录 H:SSHD 与 PAM 联动

/etc/ssh/sshd_configUsePAM yes 启用 PAM。PAM 是 Linux 通用认证框架,能接 LDAP、2FA、智能卡、指纹。

下面是一个 PAM 配置文件示例:

H1:/etc/pam.d/sshd

#%PAM-1.0
auth       required   pam_sepermit.so
auth       include    password-auth
account    required   pam_nologin.so
account    include    password-auth
password   include    password-auth
# pam_selinux.so close should be first session rule
session    required   pam_selinux.so close
session    required   pam_loginuid.so
# pam_selinux.so open should be only rule with 'open' parameter
session    required   pam_selinux.so open env_params
session    required   pam_namespace.so
session    optional   pam_keyinit.so force revoke
session    include    password-auth

PAM 在 SSH 上的功能:

  • 限定用户允许登录的时段
  • 限定可登录的源 IP
  • 引入 TOTP 二次验证
  • 引入 LDAP/AD 集中账号

H2:Google Authenticator 二次验证

yum install -y google-authenticator
google-authenticator
# 用户扫描 QR 码
# 配置 /etc/pam.d/sshd
# auth required pam_google_authenticator.so

风险:用户首次登录后必须完成 TOTP 绑定;忘记手机的人就锁死;建议同时预留应急方案(一次性救急代码)。

H3:PAM access 限制 IP

/etc/security/access.conf

+ : opsuser1 : 10.0.0.0/8
+ : opsuser2 : 192.168.0.0/16
- : ALL : ALL

/etc/pam.d/sshd 中加:

account required pam_access.so

风险:/etc/security/access.conf 写错把自己屏外,要先在 console 验。

H4:PAM 结业

PAM 的好处是统一管理多个服务的认证。SSH、PAM、SSSD、system-auth 联动配置要理清楚后再动。

附录 I:日志归档与合规留存

SSH 登录日志在合规要求中往往需要保留 180 天 - 365 天。下面是日志归档的常见做法。

I1:日志归档配置

# /etc/logrotate.d/auth
/var/log/secure /var/log/auth.log {
    daily
    rotate 365
    missingok
    notifempty
    compress
    delaycompress
    sharedscripts
    postrotate
        /usr/bin/systemctl kill -s HUP rsyslog.service >/dev/null 2>&1 || true
    endscript
}

I2:加密异地归档

#!/usr/bin/env bash
set -euo pipefail

BACKUP_DIR=/var/log/secure-archive
TODAY=$(date +%Y%m%d)
mkdir -p "$BACKUP_DIR"
cd "$BACKUP_DIR"

# 加密
tar czf - /var/log/secure-* /var/log/auth.log-* \
  | gpg --batch --yes --recipient ops@company -o "$BACKUP_DIR/secure-$TODAY.tar.gz.gpg"

# 上传到 S3
aws s3 cp "$BACKUP_DIR/secure-$TODAY.tar.gz.gpg" \
  s3://company-sshlog-archive/$(hostname)/secure-$TODAY.tar.gz.gpg

风险:gpg --recipient 的密钥必须存放在安全位置(Key Management)。

I3:日志完整性校验

sha256sum /var/log/secure > /var/log/auth-hash.txt

加上 audit 监控:

-w /var/log/secure -p wa -k auth_log_change

I4:fail2ban 日志留存

fail2ban 数据库放在 /var/lib/fail2ban/fail2ban.sqlite3,定期导出:

sqlite3 /var/lib/fail2ban/fail2ban.sqlite3 ".dump 'bans'" > /tmp/bans-$(date +%F).sql

把这个 sql 推到集中审计。

附录 J:常见 sshd 选项详解

下面列出 OpenSSH 8.x 提供的关键选项,便于读者把配置调清楚。

选项 默认 加固推荐 说明
Protocol 2 2 SSHv2 强制
Port 22 22222 高位端口
ListenAddress 0.0.0.0 内网 IP 单 IP 监听
PermitRootLogin prohibit-password no 禁用 root SSH
PubkeyAuthentication yes yes 强制密钥
PasswordAuthentication yes no 禁止密码
KbdInteractiveAuthentication yes no 关闭 KB-Interactive
ChallengeResponseAuthentication yes no 同上
UsePAM yes yes 启用 PAM 框架
MaxAuthTries 6 3 失败次数
MaxSessions 10 5 同 IP 最大 session
LoginGraceTime 2m 30 超时
ClientAliveInterval 0 300 心跳 5 分钟
ClientAliveCountMax 3 2 2 次没响应断开
AllowUsers - opsuser1 opsuser2 白名单
AllowGroups - sshusers 用户组白名单
DenyUsers - root 拒绝
DenyGroups - locked 拒绝用户组
AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys 默认
HostbasedAuthentication no no 关闭
IgnoreUserKnownHosts no no 默认
IgnoreRhosts yes yes 默认
PermitEmptyPasswords no no 强制
Banner none /etc/ssh/banner.txt 警告语
LogLevel INFO VERBOSE 详细日志
SyslogFacility AUTH AUTH 默认
UseDNS yes no 反向 DNS 关闭
X11Forwarding no no 关闭
AllowAgentForwarding yes no 关闭
AllowTcpForwarding yes local 限制
PermitUserEnvironment no no 关闭
GatewayPorts no no 关闭
TCPKeepAlive yes yes 默认
Compression no no 关闭
PrintMotd yes no 关闭,由 PAM 处理
PrintLastLog yes yes 默认
StrictModes yes yes 强制权限检查

所有上述选项均可在 sshd_config 中显式设置。回滚方式:注释相对应的行。

附录 K:客户端配置最佳实践

K1:~/.ssh/config

Host *
    HashKnownHosts yes
    StrictHostKeyChecking ask
    VisualHostKey yes
    ServerAliveInterval 60
    ServerAliveCountMax 3

Host bastion
    HostName bastion.example.com
    User opsuser1
    Port 22222
    IdentityFile ~/.ssh/id_ed25519

Host server-*
    User opsuser1
    Port 22222
    IdentityFile ~/.ssh/id_ed25519
    ProxyCommand ssh -W %h:%p bastion

K2:客户端 known_hosts 加锁

chmod 644 ~/.ssh/known_hosts
chmod 700 ~/.ssh

K3:本地 ssh agent

eval $(ssh-agent)
ssh-add ~/.ssh/id_ed25519

AddKeysToAgent 不应该默认 yes。

K4:客户端 ssh-keygen 参数

ssh-keygen -t ed25519 -a 100 -C "user@hostname" -f ~/.ssh/id_ed25519

参数说明:

  • -t ed25519:算法
  • -a 100:密钥派生轮数
  • -C "user@hostname":注释
  • -f:输出路径

K5:客户端 ~/.ssh/rc

# 在 ssh 登录时执行本地脚本
# 不推荐用于 ssh 入口

附录 L:硬件密钥 & 2FA

L1:FIDO2 / U2F

OpenSSH 8.2+ 支持:

  • sk-ssh-ed25519@openssh.com
  • sk-ecdsa-sha2-nistp256@openssh.com
ssh-keygen -t ed25519-sk -O resident -O user=opsuser1

L2:TOTP / Google Authenticator

如 H2 节描述。

L3:YubiKey SSH

YubiKey 与 SSH 联动在企业内有完整方案,本文不再展开。

L4:硬件密钥风险

硬件密钥丢了怎么办?备份密钥一定要保存在安全位置(比如保险柜里),不能只依赖一把。

附录 M:监控告警设计

把 SSH 安全事件可视化,建议最少要包含以下几类面板。

M1:登录失败率(1m/5m/30m)

sum(rate(ssh_failed_logins_total[1m]))
sum(rate(ssh_failed_logins_total[5m]))
sum(rate(ssh_failed_logins_total[30m]))

M2:来源 IP 多样性

count(count by (source_ip) (ssh_failed_logins_total{period="5m"}))

M3:Top 来源 IP

topk(10, sum by (source_ip) (rate(ssh_failed_logins_total[5m])))

M4:登录用户名 Top

topk(10, sum by (user) (rate(ssh_failed_logins_total[5m])))

M5:地理来源

基于 GeoIP 库将 IP 映射为城市。

M6:fail2ban 已封禁 IP 数

fail2ban_banned_ips

M7:成功登录的 source_ip 多样性

count(count by (source_ip) (ssh_accepted_logins_total{period="1d"}))

M8:告警阈值

- alert: SSHFailRateSpike
expr: rate(ssh_failed_logins_total[5m]) > 200
for: 1m
labels: { severity: warning }
- alert: SSHGeoDisturbance
expr: count(count by (source_country) (ssh_failed_logins_total[5m])) > 10
for: 10m
labels: { severity: warning }
- alert: SSHLoginFromOutsideList
expr: |
    rate(ssh_accepted_logins_total{source_country!~"CN|US|JP"}[5m]) > 0
for: 1m
labels: { severity: critical }
- alert: SSHSuccessFromHighRisk
expr: ssh_accepted_logins_total{user="root"} > 0
for: 0m
labels: { severity: critical }

阈值仍以实际业务基线为准。在 Grafana 中配置这些面板,可以很直观地看到全局的 SSH 攻击态势。

附录 N:容量评估与扩容

SSH 加固涉及的白名单、jail、Sudoers 配置都会在规模变大的情况下变得难以维护。

N1:白名单容量

AllowUsers 写法只支持列表。在用户数> 1000 时建议迁移到 LDAP/SSSD,用 AllowGroups。

N2:fail2ban 容量

fail2ban 用 Python + iptables 互动。当封禁 IP 数 > 5 万时,iptables 规则数大会拖累 firewalld,需切换到 nftables 或 CrowdSec。

N3:集中审计容量

每台机器每天登录失败日志 10K - 100K 条。100 台机器 = 1000 万条/天。ELK / Loki / Splunk 需要做冷热分层。

N4:故障排查容量

audit.log 在细粒度审计下日增 1 GB。设计 rollover 策略。

附录 O:与 SSO / 集中身份联动

下面几个集成是把 SSH 加固彻底做到位的关键。

O1:OpenLDAP + SSH

/etc/ssh/ldap.conf

uri ldap://10.0.0.1:389
base ou=People,dc=company,dc=com
binddn uid=sshproxy,ou=service,dc=company,dc=com
bindpwfrom /etc/ssh/ldap-bindpw

/etc/nsswitch.conf 加:

passwd:     files ldap
shadow:     files ldap
group:      files ldap

O2:SSSD + FreeIPA / 活动目录

authselect select sssd
realm join --user=admin@company.com company.com

O3:HashiCorp Vault SSH

vault login -method=ldap
vault write -field=key ssh/creds/opsuser1 :type=key

跳板机客户端:

vault ssh -role=opsuser1 -mode=ca user@server1

O4:Cilium / Teleport

Teleport 自身是堡垒机方案。社区版 - 功能齐;商业版多。

附录 P:典型 SailPoint / CyberArk 等企业级方案

企业级方案对 SSH 提供:

  • 集中密钥托管
  • 双重身份验证
  • 全流程视频录像
  • Session Recording 与可回放

费用高昂、部署复杂;中小公司不需要这么重的方案,可以从本文的 fail2ban + sudoers + auditd 起步。

附录 Q:把 SSH 加固带入容器 / K8s

容器场景中,“SSH 登入容器”是一个反模式。但调试 / 应急时仍有需求。

Q1:K8s pod exec

kubectl exec -it mysql-0 -c mysql -- bash

kubectl exec 通过 API Server,不直接暴露 SSH。安全前提是 API Server 自身被加固。

Q2:debug container

kubectl debug pod/mysql-0 --image=busybox --target=mysql

临时在 pod 旁边启一个 debug 容器,便于排查。

Q3:cluster 节点 SSH

K8s node 上通过 SSH 调试需要:

  • 节点防火墙白名单
  • 节点证书身份
  • 配合 SSO

Q4:CRI 容器调试

crictl exec -it <container-id> bash

通过 CRI 连接。

容器场景的 SSH 加固思路:

  • 不要长期开放 ssh
  • 临时 SSH 通过 API 触发
  • Audit 日志上送到 K8s audit log

附录 R:与 CloudWatch/GA 配合

云上 SSH 加固需要:

  • 关闭公网 SSH(22)入口
  • 走 Session Manager(AWS)/ BAH(Aliyun)
  • 临时 SSH 走堡垒机
  • IP 白名单基于安全组

AWS Session Manager

aws ssm start-session --target i-0123456

优点:

  • 无需公网 22
  • 完整 session 录像
  • 集成 IAM

附录 S:写作总结的写作总结

SSH 加固的核心思想:

  1. 把 SSH 当成“重要资产”,不是默认开启的服务
  2. 多维度纵深防御:网络、协议、账号、审计
  3. 测试、验证、复盘形成闭环
  4. 团队 SOP + 监控 + 演练

把这四点刻在脑子里,团队每年演练 1-2 次,把“SSH 加固”变成肌肉记忆。

到这里全篇正文 + 20+ 附录已经覆盖“SSH 暴力破解后的安全加固”。读者可以根据事件类型选用对应章节:

  • 急诊断:第 5、附录 A
  • 急止血:5.4、附录 G
  • 系统加固:5.5 - 5.11、第 7 章
  • 长期防御:第 8 章、第 9 章
  • 故障复盘:附录 E、第 14 章

整篇文章的核心是一句话:

SSH 暴力破解事件是“组织能力”的试金石:留证、评估、止血、加固、验证、回滚、复盘 —— 任何一个环节缺失,都可能让攻击者乘虚而入。 把这件事做完整,让每一个流程都有回滚、有证据、有总结,团队的安全能力才会真正起来。在 云栈社区,我们经常讨论这类实战问题的解决方案,欢迎各位同行来交流。




上一篇:C++ static 深入解析:5种用法与C++11线程安全
下一篇:NVIDIA Cosmos 3 上手拆解:当世界模型学会预测物理规律,不止视频生成
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-7-1 05:19 , Processed in 0.759292 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表