服务器怎么防止暴力破解

结论: 防止暴力破解最有效的方式不是"把密码设复杂",而是让密码本身失去作用——改用 SSH 密钥登录并彻底禁用密码认证;在此基础上叠加 fail2ban 自动封禁、防火墙限速、端口收敛三层防护,把猜密码的成功概率降到趋近于零。

暴力破解(Brute Force Attack)是指攻击者用字典或穷举方式反复尝试用户名密码,直到撞对一个弱口令。它不需要任何技术含量,一台云主机放到公网上,几小时内 /var/log/secure 或 /var/log/auth.log 里就会堆满 Failed password for root from ... 的记录。更麻烦的是,这类扫描带来的不只是入侵风险:持续的连接尝试会消耗 SSH 的连接槽与 CPU,放任不管还可能让日志在几天内涨满磁盘。

为什么说"只靠复杂密码"挡不住暴力破解?

暴力破解的本质是把"一次猜对"变成"百万次里有一次对"。只要系统允许无限次尝试,任何密码在统计意义上最终都会被撞中。因此防御思路不是让密码更长,而是降低尝试次数上限——密钥登录让猜测无意义,fail2ban 让尝试次数封顶,防火墙让扫描者连握手都完不成。

下面这张表对比了几种常见防御手段的实际效果,建议按顺序自上而下叠加部署:

防御手段位置挡住什么副作用推荐度
强密码策略应用层弱口令字典用户记不住,会写便签基础,必做但不够
SSH 密钥登录 + 禁用密码SSH 服务99% 的密码爆破私钥丢了就登不上强烈推荐
fail2ban 自动封禁日志 + 防火墙反复失败的来源自己输错也会被封强烈推荐
修改 SSH 端口SSH 服务只扫 22 的低级扫描器属于隐蔽而非安全,需同步改防火墙可选
firewalld/iptables 限速网络层高频连接洪水极端时影响正常批量登录推荐
仅允许白名单 IP 访问网络层一切非授权来源出差/家里就登不上有固定出口时推荐
单包授权 / VPN 接入边界端口完全不暴露架构复杂度上升高安全场景推荐

第一步:用 SSH 密钥登录替代密码认证

密钥登录是防爆破的第一道也是决定性的一道防线。它的原理是:服务端保存公钥,客户端持有私钥,登录时用数学运算证明你持有私钥,全程不传输任何可被猜测的秘密。

bash
# —— 在【本地电脑】上生成密钥对(Windows 用 PowerShell / macOS Linux 用终端)——
ssh-keygen -t ed25519 -C "ops@company" -f ~/.ssh/company_ed25519
# 连按两次回车可跳过密码短语;生产私钥建议设置 passphrase

# 把公钥推送到服务器(会要求输入一次服务器密码)
ssh-copy-id -i ~/.ssh/company_ed25519.pub opsadmin@203.0.113.10

密钥推送完成后,务必先新开一个终端窗口验证密钥可用,确认无误再关闭密码登录:

bash
# —— 在【服务器】上修改 SSH 配置 ——
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%F)
cat > /etc/ssh/sshd_config.d/99-no-password.conf <<'EOF'
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
UsePAM no
PubkeyAuthentication yes
PermitEmptyPasswords no
MaxAuthTries 3
EOF
# 校验语法,无输出即通过
sshd -t
systemctl restart sshd

改完之后用一台新机器验证:ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no opsadmin@IP,应当立即被拒绝而不是提示输入密码。完整的 SSH 加固项见 服务器SSH安全怎么配置。

第二步:安装配置 fail2ban

fail2ban 的工作方式是"读日志 → 匹配失败规则 → 触发防火墙封禁"。它通过 jail(监狱)定义监控对象,通过 filter(过滤器)定义失败特征,通过 action(动作)定义封禁手段。

bash
# Ubuntu 24.04
apt update && apt install -y fail2ban
# RHEL / Rocky / Alma 9 需先装 EPEL
# dnf install -y epel-release && dnf install -y fail2ban fail2ban-firewalld

systemctl enable --now fail2ban
fail2ban-client version

不要直接修改 /etc/fail2ban/jail.conf,升级时会被覆盖。正确做法是新建 .local 文件:

bash
cat > /etc/fail2ban/jail.local <<'EOF'
[DEFAULT]
# 白名单:把自己的固定出口 IP 和内网段加进来
ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 203.0.113.5
# 封禁时长:600 秒;改为 -1 表示永久封禁
bantime  = 600
# 统计窗口:10 分钟内
findtime = 600
# 失败几次触发封禁
maxretry = 5
# 递增封禁:重复违规者封禁时长逐级翻倍(1h → 12h → 1d → 8d ...)
bantime.increment = true
bantime.factor = 1
bantime.formula = ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * 1.0
bantime.multipliers = 1 6 12 24 72 168
backend = systemd

[sshd]
enabled  = true
port     = ssh
filter   = sshd
logpath  = /var/log/auth.log      # Debian/Ubuntu
# logpath = /var/log/secure       # RHEL/CentOS 系二选一
maxretry = 3
bantime  = 3600
action   = iptables[name=SSH, port=ssh, protocol=tcp]

[nginx-http-auth]
enabled  = true
filter   = nginx-http-auth
logpath  = /var/log/nginx/error.log
maxretry = 5
EOF

systemctl restart fail2ban

验证与运维常用命令:

bash
# 查看所有启用的 jail 及当前被封 IP 数
fail2ban-client status
# 查看 sshd 监狱详情与封禁列表
fail2ban-client status sshd

# 手动解封误伤的 IP
fail2ban-client set sshd unbanip 198.51.100.23

# 手动永久拉黑某个 IP
fail2ban-client set sshd banip 198.51.100.23

# 直接看内核层面生效的 iptables 规则
iptables -L f2b-SSH -n -v --line-numbers

除了 SSH,fail2ban 还内置了 nginx、Apache、Postfix、Dovecot、MySQL、WordPress(wordpress-hard.conf)等数十种过滤器,用于防御后台弱口令扫描同样有效。更完整的主机防护工具清单见 服务器安全软件推荐。

第三步:修改 SSH 端口到底有多大用?

改端口是争议最大的一条措施。结论是:它是"降噪"手段而不是"安全"手段,能挡住只扫 22 端口的批量脚本,但挡不住针对你的定向扫描——用 nmap -p- 全端口扫一遍就能找到新端口。

如果要改,请按以下流程走,避免把自己关在门外:

bash
# 1. 先选一个未被占用的高位端口(1024~65535,避开常见服务)
ss -lntup | grep -w 23456 || echo "端口 23456 可用"

# 2. 防火墙先放行新端口,再改 SSH
ufw allow 23456/tcp comment 'ssh custom'      # Ubuntu
# firewall-cmd --permanent --add-port=23456/tcp && firewall-cmd --reload   # RHEL 系

# 3. 修改配置(注意 SELinux 环境需额外放行标签)
cat > /etc/ssh/sshd_config.d/10-port.conf <<'EOF'
Port 23456
EOF
# RHEL 系 SELinux 需执行:semanage port -a -t ssh_port_t -p tcp 23456

# 4. 校验并重启,然后用【新端口】开窗口验证,验证成功前不要关旧连接
sshd -t && systemctl restart sshd

确认新端口可用后再关闭 22:先在云厂商安全组里移除 22 的放行规则,再在系统防火墙里删除,最后才把 sshd_config 里的 Port 22 注释掉。顺序反了容易踩坑。端口治理的整体思路见 服务器端口安全怎么设置。

第四步:网络层限速与白名单

这一层负责在连接建立前挡住洪水式尝试,常见做法是限制单位时间内的新建连接数。

bash
# —— iptables recent 模块:60 秒内同一 IP 新建 SSH 连接超过 4 次则丢弃 ——
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set --name SSHSCAN
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update \
         --seconds 60 --hitcount 4 --rttl --name SSHSCAN -j DROP

# —— firewalld rich rule 等价写法(RHEL 系推荐)——
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" service name="ssh" \
  source ipset="trusted" accept'
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" service name="ssh" \
  limit value="3/m" accept'
firewall-cmd --reload

# —— 最严格:只允许办公室固定出口 IP 访问 SSH ——
firewall-cmd --permanent --remove-service=ssh
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.5/32" \
  service name="ssh" accept'
firewall-cmd --reload

对于 MySQL 3306、Redis 6379、MongoDB 27017 等永不应对公网开放的端口,正确做法是在云控制台安全组中直接不放行,并在系统里把服务绑定到内网网卡地址,而不是靠 fail2ban 兜底。

第五步:如何核验防爆破措施真的生效?

没有验证的防护等于没有防护。以下是三条可以立刻执行的核验手段。

bash
# 1. 统计今日失败登录的来源 TOP 10
grep "Failed password" /var/log/auth.log | awk '{print $(NF-3)}' | sort | uniq -c | sort -nr | head -10
# RHEL 系:grep "Failed password" /var/log/secure | ...

# 2. 统计已被 fail2ban 封禁过的 IP 总数
zgrep -h "Ban " /var/log/fail2ban.log* | wc -l

# 3. 观察实时防御日志
fail2ban-client -vvv start
tail -f /var/log/fail2ban.log

一条健康的服务器,"失败登录"记录应该随时间明显下降——因为反复失败的 IP 已经被封掉了。如果一周后失败记录仍保持高位且来源分散,说明你遇到的是分布式爆破,此时应升级到云厂商的安全组限速或高防/云盾类产品,参见 服务器被攻击了怎么办 与 服务器怎么防DDoS攻击。

常见误区 / 排错提示

  1. fail2ban 装完就以为生效了。 Debian 系必须先确认 logpath 指向 /var/log/auth.log 且 backend = systemd;RHEL 系若仍用文件日志需改为 /var/log/secure,否则 jail 里 Currently failed: 0。
  2. Docker 环境下 fail2ban 封了 IP 但容器照样被访问。 Docker 会绕过 iptables 的 INPUT 链走 FORWARD 链,需要配合 -j DOCKER-USER 链或使用下文 antipatterns 说明做调整。
  3. 自己连续输错密码被封,慌了就去改 maxretry = 100。 正确做法是把常用出口加进 ignoreip,或者用 fail2ban-client set sshd unbanip 临时解封,而不是削弱策略。
  4. 改了端口但忘了改云厂商安全组,导致彻底失联。 云主机的安全组优先级高于系统防火墙,两者都要放通;先用控制台 VNC 登录再修。
  5. 把 fail2ban 的 bantime 设为 -1 永久封禁后,日志被大量僵尸 IP 占满。 建议配合 dbpurgeage 保留期(默认 1 天到数年可调)与 --restart 定期清理,或使用 ipset + 定期落盘归档。