如何在香港服务器的CentOS 7中通过优化SSH配置和硬化密码策略,防止暴力破解攻击?
那天是个潮湿的上午,我在香港机房走道里一边吸着冷气一边盯着值班大屏。香港这台对外节点的告警刚刚又跳了一轮:sshd 认证失败尖刺直冲 10k/min。运营同事说晚上就要跑客户演示,我一边把咖啡放到机柜的托盘上,一边通过 iDRAC 进到这台机器,心里就一个念头——先把门(SSH)加固,再谈别的。
1. 资产与环境清单(硬件/系统参数)
| 项目 | 参数 |
|---|---|
| 机房位置 | HK(将军澳 TKO) |
| 服务器 | Dell R730(1×Xeon E5-2680 v4,128GB RAM) |
| 存储 | 2×960GB NVMe(RAID1,用于系统与日志分区) |
| 网卡 | 2×10GbE(bond0) |
| 外网 | /29 公网段,独立 IP |
| OS | CentOS 7.9(3.10 内核) |
| OpenSSH | 7.x(CentOS 7 默认系,后续随补丁) |
| SELinux | Enforcing |
| 防火墙 | firewalld + ipset |
| OOB | iDRAC Enterprise(远程 KVM/虚拟介质) |
注:确保你有 OOB(带外)入口。任何 SSH 改动前,我都会先验证 iDRAC 控制台可用,避免“锁死在门外”。
2. 攻击态势基线(改造前)
先用几条命令刻下“问题有多糟”:
# 近 10 分钟失败登录 TOP10 IP
awk '/Failed password/ {print $(NF-3)}' /var/log/secure \
| sort | uniq -c | sort -nr | head
# 每分钟失败次数
grep "Failed password" /var/log/secure \
| awk '{print $1" "$2" "$3}' \
| uniq -c | tail
# 当前 sshd 并发
ss -tnp | grep ':22 ' | wc -l
基线数据(摘录)
| 指标 | 数值(改造前) |
|---|---|
| 失败认证峰值 | ~10,500 次/分钟 |
| 高危源段 | 若干海外云段 & 代理 |
| 22 端口并发 | 2,000+ 半开/短连 |
| CPU 抖动 | sshd 占用瞬时 80%+(总量) |
3. 总体防护思路(先“关门”,再“设门槛”,再“布捕网”)
- 最小暴露面:改端口 + 只监听需要的地址 + 允许列表优先。
- 强身份:禁用密码登录或把密码策略“硬化到痛”,优先用密钥 + 2FA。
- 限速与惩罚:firewalld / ipset / fail2ban 联动,打+封+记。
- 日志与回滚:每步变更前验证、sshd -t 语法检查、OOB 待命、每步留痕。
4. 实操步骤(逐步加固)
4.1 新建非 root 维护账号 + 密钥登录
# 1) 创建维护组与账号
groupadd devops
useradd -m -G devops,wheel -s /bin/bash ops
# 2) 配置 sudo 最小化
echo '%wheel ALL=(ALL) ALL' > /etc/sudoers.d/90-wheel
chmod 440 /etc/sudoers.d/90-wheel
# 3) 生成并部署密钥(在跳板机)
ssh-keygen -t ed25519 -a 100 -f ~/.ssh/hk_ops_ed25519
ssh-copy-id -i ~/.ssh/hk_ops_ed25519.pub ops@<当前服务器IP>
经验:先验证密钥可用,再去改 SSH 配置,避免切断最后一根救命绳。
4.2 变更 SSH 端口 + 仅监听需要的地址(含 SELinux 与防火墙)
SELinux 打开新端口类型
yum install -y policycoreutils-python
semanage port -a -t ssh_port_t -p tcp 2222
semanage port -l | grep ssh_port_t # 验证
firewalld 允许 2222,保留短期 22 做迁移过渡
# 创建 allowlist
firewall-cmd --permanent --new-ipset=ssh_allowlist --type=hash:ip
firewall-cmd --permanent --ipset=ssh_allowlist --add-entry=<你的跳板IP>
firewall-cmd --permanent --add-rich-rule='rule source ipset=ssh_allowlist port protocol="tcp" port="2222" accept'
# 临时开放公网段(只为切换窗口,后续删除)
firewall-cmd --permanent --add-port=2222/tcp
firewall-cmd --reload
编辑 /etc/ssh/sshd_config(关键项)
Port 2222
# Port 22 # 迁移期保留,确认后删除
AddressFamily inet # 无 IPv6 需求时减少面暴
ListenAddress <此机公网IPv4>
Protocol 2
# 身份与权限
PermitRootLogin no
PasswordAuthentication no # 强推密钥;如需临时保留,最后两节再硬化密码策略
PubkeyAuthentication yes
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
UsePAM yes
# 加固算法(CentOS 7/OpenSSH 7.x 兼容)
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes128-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256
# 细节
ClientAliveInterval 300
ClientAliveCountMax 2
LoginGraceTime 20
MaxAuthTries 3
MaxStartups 10:30:60 # 限并发握手
UseDNS no # 避免反查卡顿
# 访问白名单(可选:更细颗粒)
# AllowUsers ops
语法检查 & 平滑重载
sshd -t
systemctl reload sshd
在新端口验证登录,确认无误后移除 22
ssh -i ~/.ssh/hk_ops_ed25519 -p 2222 ops@<IP>
# 验证稳定后:
firewall-cmd --permanent --remove-port=22/tcp
firewall-cmd --reload
# 同步删掉 sshd_config 中的 Port 22
坑 1:改了 Port,忘了 semanage 或忘了 firewalld。症状:connection refused 或 No route to host。
解决:OOB 登录,核对 semanage port -l | grep ssh 与 firewall-cmd --list-all。
4.3 失败尝试自动封禁:fail2ban + firewalld/ipset
安装与启用
yum install -y epel-release
yum install -y fail2ban fail2ban-systemd
systemctl enable --now fail2ban
jail.local(/etc/fail2ban/jail.local)
[DEFAULT]
banaction = firewallcmd-ipset
backend = systemd
[sshd]
enabled = true
port = 2222
findtime = 10m
maxretry = 5
bantime = 1h
检查状态
fail2ban-client status sshd
坑 2:后端日志选择不当。CentOS 7 建议 backend=systemd,否则有时抓不全拒绝事件。
4.4 进一步缩口:只允许特定源段(生产强烈建议)
# 扩充允许列表
firewall-cmd --permanent --ipset=ssh_allowlist --add-entry=<你的办公出口IP>
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="<临时远程IP/32>">
port protocol="tcp" port="2222" accept'
firewall-cmd --reload
策略:先白名单,后黑名单。公网节点尽量不对全网暴露管理口。
4.5 若必须保留密码登录:把密码政策“硬到吓退脚本”
如果业务流程暂时离不开密码(例如跳板未全覆盖),务必开启以下策略;一旦条件允许,还是推荐彻底 PasswordAuthentication no。
pwquality 强度策略(/etc/security/pwquality.conf)
minlen = 14
dcredit = 0
ucredit = 0
lcredit = 0
ocredit = 0
minclass = 4
maxrepeat = 2
dictcheck = 1
usercheck = 1
gecoscheck = 1
PAM 引用(/etc/pam.d/system-auth 与 /etc/pam.d/password-auth)
在 password 段增加(或强化):
password requisite pam_pwquality.so retry=3 enforce_for_root local_users_only
登录失败锁定(pam_faillock)
在 auth 段添加:
auth required pam_faillock.so preauth silent audit deny=5 unlock_time=900
auth [default=die] pam_faillock.so authfail audit deny=5 unlock_time=900
account required pam_faillock.so
密码轮换与过期(/etc/login.defs)
PASS_MAX_DAYS 90
PASS_MIN_DAYS 7
PASS_WARN_AGE 14
对已有用户执行:
chage -M 90 -m 7 -W 14 ops
4.6 选配:SSH + TOTP 二次验证(Google Authenticator)
yum install -y google-authenticator
su - ops -c 'google-authenticator -t -d -f -r 3 -R 30 -w 3'
# 扫码保存应急码
# PAM:/etc/pam.d/sshd
auth required pam_google_authenticator.so nullok
# sshd_config
ChallengeResponseAuthentication yes
AuthenticationMethods publickey,keyboard-interactive
systemctl reload sshd
小贴士:TOTP 最好只对运维跳板或关键账号启用,通过 Match User ops 分段配置可控范围。
4.7 登录横幅(法律告知)
echo "Authorized access only. All activities may be monitored and recorded." > /etc/issue.net
sed -i 's/^#\?Banner.*/Banner \/etc\/issue.net/' /etc/ssh/sshd_config
systemctl reload sshd
5. 防火墙规则结构(示意)
| 优先级 | 规则 | 说明 |
|---|---|---|
| 1 | 允许 ssh_allowlist → TCP/2222 |
固定办公/跳板源 |
| 2 | fail2ban 动态封禁(ipset) | 惩罚阈值命中即封 |
| 3 | 临时例外(短期 IP/32) | 运维紧急通道 |
| 4 | 其余拒绝(不暴露 22) | 收口 |
6. 审计与监控要点
配置快照:
sshd -T > /root/sshd.effective.$(date +%F).txt 留档。
日志切割与留存:
确保 /var/log/secure 与 fail2ban 日志落在 NVMe,容量充足。
指标:
- 每分钟失败登录数
- fail2ban 封禁数
- MaxStartups 命中次数
- 登录成功地域分布(反常告警)
例行检查脚本(摘录):
#!/bin/bash
echo "[Failed/min]"
grep "Failed password" /var/log/secure \
| awk '{print $1" "$2" "$3}' \
| uniq -c | tail -n 5
echo "[Fail2ban bans]"
fail2ban-client status sshd | sed -n '1,20p'
7. 实施“前后对比”(实际观测)
| 指标 | 改造前 | 改造后(48h) |
|---|---|---|
| 失败认证峰值(次/分钟) | ~10,500 | < 30 |
| 22 端口并发 | 2,000+ | 0(已下线) |
| 被封禁 IP(累计) | N/A | 2,183 |
| 有效登录失败率 | 12% | < 1% |
| SSH 握手耗时 P95 | 480ms | 140ms(禁用反查+白名单) |
8. 回滚与应急预案
永远保留 OOB 通道(iDRAC/iLO)。
变更窗口:先开放新端口 2222 → 成功验证 → 再关闭 22。
一键回滚:保留 /etc/ssh/sshd_config.bak,失败时:
cp /etc/ssh/sshd_config.bak /etc/ssh/sshd_config
semanage port -d -t ssh_port_t -p tcp 2222 || true
firewall-cmd --permanent --remove-port=2222/tcp
firewall-cmd --reload
systemctl restart sshd
9. 常见坑与现场解法(血泪总结)
忘开 SELinux 端口:Permission denied/context not allowed
→ semanage port -a -t ssh_port_t -p tcp 2222
firewalld Zone 搞错:规则加到 public,网卡却在 drop
→ nmcli -f GENERAL.CONNECTION,GENERAL.ZONE dev show <nic>;firewall-cmd --get-active-zones
密钥权限不对:~/.ssh 权限 700、authorized_keys 600;chcon -t ssh_home_t ~/.ssh -R(SELinux)
DNS 反查拖慢登录:UseDNS no、同时 /etc/hosts 固定跳板名
MaxStartups 过严:登录高峰被挤掉
→ MaxStartups 建议 10:30:60 起步,按压测微调
10. 一键化脚本(可读可改)
在 OOB 保驾护航前提下执行,变量按需替换。
#!/bin/bash
set -euo pipefail
NEW_PORT=2222
JUMPHOST_IP="<你的跳板IP>"
yum install -y epel-release policycoreutils-python fail2ban fail2ban-systemd
# SELinux & firewalld
semanage port -a -t ssh_port_t -p tcp ${NEW_PORT} || true
firewall-cmd --permanent --new-ipset=ssh_allowlist --type=hash:ip || true
firewall-cmd --permanent --ipset=ssh_allowlist --add-entry=${JUMPHOST_IP}
firewall-cmd --permanent --add-rich-rule="rule source ipset=ssh_allowlist port protocol=\"tcp\" port=\"${NEW_PORT}\" accept"
firewall-cmd --permanent --add-port=${NEW_PORT}/tcp
firewall-cmd --reload
# 备份 & 修改 sshd_config
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%F_%H%M%S)
cat >/etc/ssh/sshd_config <<'CONF'
Port 2222
AddressFamily inet
Protocol 2
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
UsePAM yes
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes128-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256
ClientAliveInterval 300
ClientAliveCountMax 2
LoginGraceTime 20
MaxAuthTries 3
MaxStartups 10:30:60
UseDNS no
CONF
sshd -t && systemctl reload sshd
# fail2ban
cat >/etc/fail2ban/jail.local <<'JAIL'
[DEFAULT]
banaction = firewallcmd-ipset
backend = systemd
[sshd]
enabled = true
port = 2222
findtime = 10m
maxretry = 5
bantime = 1h
JAIL
systemctl enable --now fail2ban
fail2ban-client status sshd || true
echo "Done. Test SSH: ssh -p 2222 ops@<IP>"
11. 最终清单(Checklist)
- 已验证跳板/白名单能通 2222
- PasswordAuthentication no(或已启用强密码+faillock)
- PermitRootLogin no
- SELinux 端口已登记(ssh_port_t)
- firewalld 仅放行白名单与 2222
- fail2ban 生效,banaction=firewallcmd-ipset
- UseDNS no,算法/握手参数已收敛
- 登录横幅与审计策略到位
- 22 已下线并验证无依赖
12. 从“海啸”到“细雨”
改造完成的第二天夜里,我回到机房,又把咖啡放在了那块托盘上。大屏上 sshd 的日志曲线从锯齿海啸变成了几条细线;fail2ban 偶尔跳一下封禁计数,但很快归于平静。
我知道这不是终点——安全是个过程,但至少今晚,门锁结实、警铃在线、巡逻规律。你得先让门配得上这座机房,剩下的就交给日常的监控和纪律。
FAQ · 我在现场经常被问到的 3 个问题
只改端口有用吗?
仅改端口不是安全策略,但能有效降噪,让惩罚/监控更聚焦;与白名单、密钥、fail2ban 叠加才是闭环。
必须关掉密码登录吗?
能关就关;不能关就把密码策略“硬化到痛”,并配合 pam_faillock 与 2FA。
怕锁自己在外面?
准备 OOB、逐步切换、每步sshd -t、保留回滚脚本。流程比命令更重要。
如果你也在香港维护类似的外网节点,上面的步骤照抄 80% 就能落地。剩下 20%,请结合你的网络边界(云防火墙/硬件 ACL)与团队运维流程去“定制化”。安全不是模板,而是纪律 + 细节。