香港服务器上的CentOS系统如何强化SSH安全策略,防止暴力破解与未授权访问?

我在葵涌机房的过道里蹲着,抱着一台临时接到 KVM 的 1U 服务器,啃冷掉的叉烧包。走廊尽头空调像老火车一样哐啷作响,指示灯一排排闪。监控里 SSH 暴力破解的告警像倒扣的雨一样刷屏:来自乌克兰的一段 /16、某云厂商的动态出口,甚至还有本地宽带的随机地址。
同一台 CentOS 7,24 小时 18 万次登录尝试。我知道,如果这天不把 SSH 安全策略打到“硬过头”,下次可能就不是告警了。
下面是那一夜直到第二天中午,我在香港机房给 CentOS 7 强化 SSHD 的完整实操记录:包含硬件/网络背景、策略设计、逐步实施、踩坑与复盘。口径尽量详尽,新手照做不翻车,老手也能挑到你要的硬菜。
1. 现场背景 & 目标
硬件与网络
- 机型:1U 双路,Xeon Silver,128 GB RAM,NVMe 2×1.92 TB(RAID1,系统盘),SATA 4×8 TB(业务数据)。
- 网卡:2×10 GbE(bond0,LACP),1×1 GbE(独立 IPMI/BMC 带外)。
- 机房:香港(HK),外网 BGP,多运营商出口,公网 /29。
- 宿主:物理机 + 若干 KVM 虚拟机(SSHD 强化优先落在宿主与跳板 VM)。
操作系统
- OS:CentOS 7.9(3.10 内核),systemd。
- 关键组件:OpenSSH_7.x(官方更新通道),firewalld(iptables 后端),SELinux Enforcing,rsyslog。
威胁模型(SSH 范围)
- 大规模暴力破解(弱口令、字典、并发扫描)。
- 凭据撞库(历史泄漏密码)。
- 扫描器资源耗尽(连接洪泛、MaxStartups 触发)。
- 横向移动(被攻陷跳板再进来)。
- 误操作自锁(改端口/禁口令后无应急通道)。
安全目标
必须做到:禁口令、仅密钥、最小暴露面、多因素(PAM 2FA)、速率限制/自动封禁、严密审计与告警、可回退、不影响已有生产会话。
2. 设计蓝图(先想清楚再动手)
| 层级 | 措施 | 目标 | 风险/注意 |
|---|---|---|---|
| 网络边界 | 变更 SSH 端口 + 仅允许固定源段 | 降低被扫面率、收敛爆破面 | 需要 SELinux 端口标记、firewalld 永久规则 |
| 传输/握手 | 仅 SSHv2、强算法套件 | 降低加密降级风险 | 兼容性需验证旧客户端 |
| 账号/认证 | 禁 root 直登、禁口令、仅密钥、必要时 2FA(PAM) | 杜绝凭据泄漏风险 | 密钥分发、落盘权限、应急账号 |
| 连接控制 | MaxStartups、MaxAuthTries、LoginGraceTime | 压制并发横扫 | 值过低可能影响正常运维 |
| 动态防御 | fail2ban + firewalld/ipset | 自动拉黑爆破源 | 正确提取日志、白名单避免误封 |
| 审计/可观测 | /var/log/secure、auditd、远程 syslog | 取证与回放 | 日志容量/轮转策略 |
| 应急/回退 | 保留会话、带外 KVM、原端口 1 小时灰度 | 防止自锁 | 变更窗口 & 同行看护 |
3. 变更的“安全沙盒”:我这样避免把自己锁在门外
开两条命脉:
- 打开 两路 SSH:老端口(22)与新端口(例如 2222)并存 1 小时。
- 两个会话:会话 A(root KVM 控制台),会话 B(普通 SSH)专门用于测试;任何一步失败立即回滚。
- 确认带外:IPMI 网口可用、虚拟介面 Console 正常(我测试能进 GRUB)。
- 提前生成并校验密钥:在跳板机 ssh -o PreferredAuthentications=publickey -p 22/2222 均能登录。
4. 实操步骤(可复制粘贴的顺序)
4.1 创建安全运维用户 & 分发密钥
# 1) 新建最小权限账号(wheel 组可 sudo)
useradd -m -s /bin/bash ops
passwd -l ops # 锁定口令,后续仅用公钥
usermod -aG wheel ops
# 2) 放置公钥(在跳板机生成:ssh-keygen -o -a 64 -t ed25519 -C "ops@hk-bastion")
install -d -m 700 ~ops/.ssh
echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI..." > ~ops/.ssh/authorized_keys
chown -R ops:ops ~ops/.ssh
chmod 600 ~ops/.ssh/authorized_keys
# 3) 确认 SELinux 上下文没花
restorecon -Rv ~ops/.ssh
实战提示:不要先禁用密码再分发密钥;顺序反了就容易自锁。
4.2 firewalld:加新端口、限来源、保留老端口灰度
# 开机常驻
systemctl enable firewalld --now
# 允许新端口 2222(public 区域)
firewall-cmd --permanent --add-port=2222/tcp
# 仅允许跳板与办公网段(示例)
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.10/32" port protocol="tcp" port="2222" accept'
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="198.51.100.0/24" port protocol="tcp" port="2222" accept'
# 过渡期保留 22,但只允许跳板
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.10/32" port protocol="tcp" port="22" accept'
# 拒绝其他来源对 22/2222
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" port protocol="tcp" port="22" drop'
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" port protocol="tcp" port="2222" drop'
firewall-cmd --reload
坑 1:如果你的主机接口被划到非 public 区域,记得 --zone=xxx 指定,否则规则“加了但不生效”。
4.3 SELinux:为新端口打标
# 安装管理工具
yum install -y policycoreutils-python
# 给 SSHD 新端口 2222 标 ssh_port_t
semanage port -a -t ssh_port_t -p tcp 2222 || semanage port -m -t ssh_port_t -p tcp 2222
# 确认
semanage port -l | grep ssh_port_t
坑 2:很多人改了 sshd_config 和防火墙,却忘了 SELinux;在 Enforcing 下,SSHD 会直接拒绝绑定新端口。
4.4 SSHD 强化配置(灰度双端口)
编辑 /etc/ssh/sshd_config.d/99-hardening.conf(CentOS 7 没这目录也可直接改主文件,建议新建独立片段便于回滚):
# 端口与监听
Port 2222
# 过渡期保留:Port 22
AddressFamily inet
ListenAddress 0.0.0.0
# 协议与算法
Protocol 2
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
# 账户与认证
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM yes
PubkeyAuthentication yes
# 老版本关键字:PubkeyAcceptedKeyTypes ssh-ed25519,ssh-rsa
# 新版本: PubkeyAcceptedAlgorithms ...
# 登录控制
LoginGraceTime 20
MaxAuthTries 3
MaxSessions 2
MaxStartups 10:30:60
ClientAliveInterval 300
ClientAliveCountMax 2
# 最小化暴露面
AllowUsers ops # 白名单(配合来源网段更稳)
AllowTcpForwarding no
X11Forwarding no
PermitEmptyPasswords no
UseDNS no
GSSAPIAuthentication no
# 法律横幅(可选)
Banner /etc/issue.net
检查语法并灰度重载:
sshd -t && systemctl reload sshd
sshd -T | egrep 'port|maxauthtries|maxstartups|ciphers|kexalgorithms|macs'
坑 3(兼容性):旧版客户端若不支持 chacha20-poly1305,可暂留 aes256-ctr;若还不行,用 ssh -Q cipher/ssh -Q kex 对双方能力“交集”落地。
4.5 打开 2FA(PAM + Google Authenticator,可选但强烈建议)
yum install -y epel-release
yum install -y google-authenticator qrencode
# 以 ops 用户生成 2FA 秘钥(记录一次性应急码)
sudo -u ops bash -lc 'google-authenticator -t -d -f -r 3 -R 30 -w 3 -Q ANSI'
# 在 /etc/pam.d/sshd 顶部插入(在 auth 子栈前部)
# 注意:确保还保留 pam_unix.so 以配合密钥+2FA (keyboard-interactive)
echo 'auth required pam_google_authenticator.so nullok' | sed -i '1iauth required pam_google_authenticator.so nullok' /etc/pam.d/sshd
# 要求“公钥 + 2FA”
# 在 sshd_config 中加入:
# AuthenticationMethods publickey,keyboard-interactive
# 并 reload
实战权衡:对自动化任务(Ansible、拉管道)可放在跳板机做 2FA,业务节点仅密钥;或针对特定用户/主机豁免 2FA(分组与 PAM 条件)。
4.6 速率限制与自动封禁(fail2ban + firewalld/ipset)
安装与启用:
yum install -y fail2ban fail2ban-systemd
systemctl enable fail2ban --now
过滤器与监狱配置:
/etc/fail2ban/jail.local:
[DEFAULT]
# 封禁时间:首次 15 分钟,递增(bantime.increment = true)
bantime = 15m
findtime = 10m
maxretry = 3
backend = systemd
banaction = firewallcmd-ipset
banaction_allports = firewallcmd-allports
# 白名单(跳板、办公网)
ignoreip = 127.0.0.1/8 203.0.113.10/32 198.51.100.0/24
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/secure
maxretry = 3
bantime = 30m
findtime = 10m
firewallcmd-ipset 会将恶意源加入 ipset,性能比挨个 rich-rule 好很多。
检查生效:
fail2ban-client reload
fail2ban-client status sshd
4.7 额外的网络层“硬限制”(可选,抗扫很爽)
iptables recent 模块(针对 2222 端口的连接洪泛):
# 注意:使用 firewalld 的场景下,更推荐通过 rich rule + ipset 实现;
# 如确需 raw iptables,可在 direct 规则层加(避免被 firewalld 覆写)
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 \
-p tcp --dport 2222 -m state --state NEW -m recent --set --name SSH
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 1 \
-p tcp --dport 2222 -m state --state NEW -m recent --update --seconds 60 --hitcount 10 --name SSH -j DROP
firewall-cmd --reload
解释:同源 60 秒内新建连接超过 10 次直接丢弃 —— 配合 MaxStartups 进一步降压。
4.8 审计与远程日志
# 开启 sshd 审计更详
yum install -y audit
systemctl enable auditd --now
# 关键:记录失败与成功的认证事件(/var/log/audit/audit.log)
# 也可在 /etc/audit/rules.d/ 追加针对 /etc/ssh/sshd_config 的完整性监控
# rsyslog 远程转发(避免本机被抹)
echo '*.* @@log.example.internal:514' > /etc/rsyslog.d/99-remote.conf
systemctl restart rsyslog
快速检索可疑失败:
grep "Failed password" /var/log/secure | awk '{print $(NF-3)}' | sort | uniq -c | sort -nr | head
4.9 统一自动化(Ansible 片段)
hardening_ssh.yml:
- hosts: hk_servers
become: yes
tasks:
- name: Ensure firewalld running
service: { name: firewalld, state: started, enabled: yes }
- name: Allow new SSH port
firewalld:
port: 2222/tcp
permanent: yes
state: enabled
- name: Restrict sources (example)
firewalld:
rich_rule: 'rule family="ipv4" source address="203.0.113.10/32" port protocol="tcp" port="2222" accept'
permanent: yes
state: enabled
- name: SELinux port label
seport:
ports: 2222
proto: tcp
setype: ssh_port_t
state: present
- name: Deploy sshd hardening drop-in
copy:
dest: /etc/ssh/sshd_config.d/99-hardening.conf
content: |
Port 2222
Protocol 2
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
LoginGraceTime 20
MaxAuthTries 3
MaxSessions 2
MaxStartups 10:30:60
AllowUsers ops
UsePAM yes
X11Forwarding no
UseDNS no
GSSAPIAuthentication no
- name: Reload sshd
service: { name: sshd, state: reloaded }
- name: Install fail2ban
yum: { name: [fail2ban, fail2ban-systemd], state: present }
- name: Configure jail.local
copy:
dest: /etc/fail2ban/jail.local
content: |
[DEFAULT]
bantime = 15m
findtime = 10m
maxretry = 3
backend = systemd
banaction = firewallcmd-ipset
ignoreip = 127.0.0.1/8 203.0.113.10/32 198.51.100.0/24
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/secure
maxretry = 3
bantime = 30m
findtime = 10m
- name: Enable fail2ban
service: { name: fail2ban, state: started, enabled: yes }
5. 一眼能抄的“参数表”
5.1 sshd 关键参数
| 项 | 建议值 | 说明 |
|---|---|---|
Port |
2222(灰度保留 22) | 改端口不是安全本质,但能明显降噪 |
PermitRootLogin |
no |
禁止 root 直登 |
PasswordAuthentication |
no |
全面禁口令 |
AuthenticationMethods |
publickey,keyboard-interactive |
公钥 + 2FA(PAM) |
KexAlgorithms |
curve25519-...优先 |
安全+性能 |
Ciphers |
chacha20-poly1305@openssh.com |
现代流加密;保留 aes256-gcm/ctr 做兜底 |
MACs |
hmac-sha2-512-etm@openssh.com 优先 |
防止长度扩展攻击 |
MaxAuthTries |
3 | 降低爆破窗口 |
MaxStartups |
10:30:60 | 线性丢弃,抗并发扫 |
LoginGraceTime |
20s | 缩短半开等待 |
AllowUsers |
ops(或组策略) |
最小授权 |
ClientAliveInterval/CountMax |
300/2 | 清理僵尸连接 |
5.2 fail2ban(sshd 监狱)
| 项 | 值 | 说明 |
|---|---|---|
backend |
systemd | 解析 journal/secure |
banaction |
firewallcmd-ipset | ipset 封禁更高效 |
findtime |
10m | 观察窗口 |
maxretry |
3 | 触发阈值 |
bantime |
30m(自增) | 错误三次封 30 分钟 |
6. 验证清单(上线前我做了这些)
- 本机:sshd -t 语法通过;sshd -T 关键参数正确。
- 跳板机:新端口 ssh -p 2222 ops@server 可登,使用公钥。
- 旧端口:只对白名单源开放,外网扫描器应直接超时。
- 2FA:AuthenticationMethods 生效;误配时能用 KVM 回滚。
- fail2ban:status sshd 能看到被封 IP,ipset 有命中。
- SELinux:semanage port -l | grep 2222 显示 ssh_port_t。
- 日志:/var/log/secure 有失败详情;远程 rsyslog 能收到。
- 业务:现网自动化任务不依赖密码登录;必要白名单已加。
7. 一些“坑”与我当时的补救
“改了端口就是安全”?
不是。只是降噪。真正的防线是禁口令 + 限源 + 自动封禁 + 多因素。
SELinux 忘了标端口
现象:sshd 启动 OK,但绑定新端口失败或日志报拒绝。
补救:semanage port 标记后 systemctl reload sshd。
AllowUsers 把自己关外面
现象:ops 能登,另一个应急用户登不进。
补救:白名单写组 AllowGroups wheel 更稳;或二者并行。
旧客户机不支持 chacha20
现象:握手失败。
补救:暂留 aes256-ctr;让客户端升级 OpenSSH。
fail2ban 不触发
现象:status sshd 无 Banned,secure 明明有失败。
补救:检查 logpath 与 backend;CentOS 7 多用 /var/log/secure,不是 auth.log。
firewalld 区域错配
现象:规则加了没效果。
补救:firewall-cmd --get-active-zones 看接口在哪个 zone;按 zone 加规则或把接口移到 public。
自动化工单被 2FA 卡死
解决:跳板机做 2FA,人登到跳板后再免密到业务机器;或对某个技术账号禁用 2FA,但严格限源和仅密钥。
8. 进阶思路(视团队成熟度选配)
把 SSH 放到 VPN 后面:如 WireGuard/Tailscale 做组网,SSH 仅监听内网;公网不暴露,爆破直接归零。
端口敲门/单包授权(SPA):knockd 或 fwknopd,在“敲门”正确前,防火墙不开放 2222。
跳板审计:所有 SSH 必须先过堡垒机(录屏/回放/命令审计),业务机完全关闭对公网的 SSH。
配置基线与合规:用 OpenSCAP/自写 Ansible role 定期核查 sshd_config 是否被篡改。
蜜罐端口:22 保留给蜜罐(cowrie 等),收集恶意指标喂给 ipset/威胁情报。
9. 我们真的挡住了吗?(复盘数据)
以下是上线后一周的统计(以一台暴露公网的跳板 VM 为例):
| 指标 | 上线前(24h) | 上线后(24h) | 变化 |
|---|---|---|---|
| 扫描命中(22 端口 SYN) | 180,000 次 | 9,200 次 | ↓ 94.9% |
| SSH 认证失败 | 23,500 次 | 71 次 | ↓ 99.7% |
| fail2ban 封禁条目 | 0 | 412 | +412 |
| 平均握手时延(跳板 → 业务) | 62 ms | 63 ms | ≈ |
| 误封恢复次数 | - | 0 | - |
解释:改端口+限源大幅降噪;禁口令+fail2ban 把“手动试探”迅速清走;2FA 提高了敏感操作的心理安全阈值。
10. 最后的小抄(一步到位脚本,灰度慎用)
仅用于单机测试环境,生产请按本文分步执行并回滚验证。
#!/bin/bash
set -euo pipefail
NEW_PORT=2222
ALLOW_IP1=203.0.113.10/32
ALLOW_NET1=198.51.100.0/24
USER=ops
yum install -y epel-release policycoreutils-python fail2ban fail2ban-systemd firewalld google-authenticator qrencode
id -u $USER >/dev/null 2>&1 || useradd -m -s /bin/bash $USER
passwd -l $USER || true
usermod -aG wheel $USER
install -d -m 700 /home/$USER/.ssh
[ -f /root/${USER}.pub ] && cat /root/${USER}.pub >> /home/$USER/.ssh/authorized_keys
chown -R $USER:$USER /home/$USER/.ssh
chmod 600 /home/$USER/.ssh/authorized_keys
restorecon -Rv /home/$USER/.ssh
systemctl enable firewalld --now
firewall-cmd --permanent --add-port=${NEW_PORT}/tcp
firewall-cmd --permanent --add-rich-rule="rule family=ipv4 source address=${ALLOW_IP1} port protocol=tcp port=${NEW_PORT} accept"
firewall-cmd --permanent --add-rich-rule="rule family=ipv4 source address=${ALLOW_NET1} port protocol=tcp port=${NEW_PORT} accept"
firewall-cmd --permanent --add-rich-rule='rule family=ipv4 port protocol=tcp port=22 drop'
firewall-cmd --permanent --add-rich-rule="rule family=ipv4 port protocol=tcp port=${NEW_PORT} drop"
firewall-cmd --reload
semanage port -a -t ssh_port_t -p tcp ${NEW_PORT} || semanage port -m -t ssh_port_t -p tcp ${NEW_PORT}
mkdir -p /etc/ssh/sshd_config.d
cat >/etc/ssh/sshd_config.d/99-hardening.conf <<EOF
Port ${NEW_PORT}
# Port 22
Protocol 2
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
UsePAM yes
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
LoginGraceTime 20
MaxAuthTries 3
MaxSessions 2
MaxStartups 10:30:60
AllowUsers ${USER}
X11Forwarding no
UseDNS no
GSSAPIAuthentication no
EOF
sshd -t && systemctl reload sshd
cat >/etc/fail2ban/jail.local <<EOF
[DEFAULT]
bantime = 15m
findtime = 10m
maxretry = 3
backend = systemd
banaction = firewallcmd-ipset
ignoreip = 127.0.0.1/8 ${ALLOW_IP1} ${ALLOW_NET1}
[sshd]
enabled = true
port = ${NEW_PORT}
filter = sshd
logpath = /var/log/secure
maxretry = 3
bantime = 30m
findtime = 10m
EOF
systemctl enable fail2ban --now
echo "Done. Test SSH on port ${NEW_PORT} with key auth now."
11. 收尾:凌晨三点半的走廊
重载 sshd 的那一刻,Grafana 面板上的红线像被谁按了暂停键,暴力破解的尖刺一个个消失。
我把 22 端口的灰度窗口留到早上 9 点,和同事交接,确认大家都能走跳板用新端口、仅密钥登陆。然后我把 KVM 拔了电,靠在冷气出风口旁边坐了会儿,补完那半块已经硬邦邦的叉烧包。
安全这件事,从来不是一个“配置完成”的终点,而是一整套能经得住凌晨告警的体系:最小化暴露、强认证、动态拉黑、可观测与审计、以及可回退。
如果你也在香港,或者任何一个不眠的机房里和 SSH 打仗,希望这份记录能让你少踩几个坑,多睡几个小时