服务器如何有效防止密码爆破?

评分: 4.75

前阵子有个对外接口响应越来越慢,从原本的 200ms 直接飙到 3 秒多。
我第一反应是查内存泄漏、看数据库连接数,结果都正常。百思不得其解时,我顺手翻了翻服务器的系统日志,想看看有没有异常任务。

这一翻,很常见的问题——一堆 IP 在爆破我的服务器……

Aug 24 13:56:34 sshd[21592]: Failed password for root from 186.233.xxx.xxx
Aug 24 13:56:39 sshd[21619]: Accepted password for root from 150.158.xxx.xxx
Aug 24 13:56:52 sshd[21681]: Failed password for root from 186.233.xxx.xxx

SSH 认证日志里密密麻麻全是失败记录,而且攻击者挺勤奋,单 IP 一天尝试几百次,root、www、ubuntu、admin、test、case 这些常见用户名轮着扫。

SSH 爆破天天都有,为什么偏偏今天接口变慢?
顺着这个疑问继续排查,我才发现真正的罪魁祸首。


一、不是爆破流量,而是老旧防护工具把 CPU 打满了

在检查系统现有防护时,我发现服务器里居然还装着一个 DenyHosts,查了下安装时间,是云服务运营商预装的,基于 Python 2 实现。

它确实在干活——一直在疯狂解析 SSH 日志,并把攻击 IP 写入 /etc/hosts.deny。
问题在于,这台服务器访问量大,日志刷得极快,DenyHosts 处理不过来,CPU 占用直接飙到 100%,整台机器的 Swap 也被打满。

这就解释了为什么接口会慢:CPU 资源全被 DenyHosts 吃光了,业务进程根本分不到时间片。

既然如此,我决定做一次彻底的清理和加固:把老旧的 DenyHosts 换成更轻量的 fail2ban,同时彻底关闭密码登录,只留密钥。


二、替换卸载 DenyHosts,安装 fail2ban

1. 先停掉并清理 DenyHosts

DenyHosts 既然已经过时且耗资源,直接停掉并确保它不再自启:

# 停止正在运行的服务
systemctl stop denyhosts

# 强制杀掉残留进程(如果有)
pkill -f denyhosts.py

# 检查自启项(systemd / chkconfig / cron / rc.local 都要看一眼)
systemctl disable denyhosts   # 如果是 systemd 托管
chkconfig --del denyhosts     # 如果是旧版 SysV

确认没有残留启动项后,DenyHosts 就不会再抢 CPU 了。

2. 安装 fail2ban(现代、资源占用极低)

不同操作系统安装命令略有不同,这里以最常用的 CentOS / Ubuntu 为例:

CentOS / Rocky / AlmaLinux:

# 先安装 EPEL 源(如果已有可跳过)
yum install epel-release -y
yum install fail2ban fail2ban-systemd -y

Ubuntu / Debian:

apt update
apt install fail2ban -y

安装完成后,fail2ban 默认会以 systemd 服务形式运行,但配置文件我们需要自定义一下。


三、配置 fail2ban:5 次失败封 24 小时

fail2ban 的默认配置文件是 /etc/fail2ban/jail.conf,但我们一般不直接改它,而是新建一个 jail.local 来覆盖自定义项,方便后续升级。

创建 /etc/fail2ban/jail.local 文件,写入以下内容:

[DEFAULT]
# 封禁时长(单位:秒),86400 = 24小时
bantime = 86400

# 统计时间窗口(单位:秒),600 = 10分钟
findtime = 600

# 在 findtime 窗口内失败多少次触发封禁
maxretry = 5

# 白名单(你自己的办公IP或跳板机IP,务必加进去)
ignoreip = 127.0.0.1/8 ::1 你的公网出口IP

[sshd]
# 启用 SSH 防护
enabled = true
# 监听端口(如果你改了 SSH 端口,这里改成对应的数字)
port = ssh
# 日志路径,默认指向系统 SSH 日志
logpath = %(sshd_log)s
# 后端模式,新版系统推荐 systemd
backend = systemd

保存后,启动 fail2ban 并设置开机自启:

systemctl start fail2ban
systemctl enable fail2ban

此时可以用命令查看封禁状态,你会看到已经被封的 IP 列表:

fail2ban-client status sshd

我的服务器上,启动才几分钟就封了 9 个攻击 IP,那个一天尝试 350 多次的 186.233.xxx.xxx 赫然在列。


四、加固防御,关闭密码登录,改用 ED25519 密钥

光靠 fail2ban 只是把“猜密码”的 IP 暂时挡在门外,但只要有密码登录这个通道存在,攻击者永远有机会(比如恰好在你重启 fail2ban 的间隙猜中)。最根本的办法是直接禁用密码登录。

1. 在本地电脑生成密钥对(注意不要在服务器上生成私钥)

打开本机的终端(Mac/Linux)或 Git Bash(Windows),执行:

ssh-keygen -t ed25519 -N '' -f ~/.ssh/id_ed25519
  • -t ed25519:选用 Ed25519 算法,比 RSA 短且更快,安全性足够。
  • -N '':不设私钥密码(如果你追求更高安全,可以去掉 -N '',设置一个口令)。
  • -f ~/.ssh/id_ed25519:指定生成的文件名。

执行后,本地 ~/.ssh/ 目录下会生成 id_ed25519(私钥,打死不能外传)和 id_ed25519.pub(公钥,待会要上传)。

2. 把公钥上传到服务器

使用 ssh-copy-id 一条命令搞定(替换成你的服务器 IP):

ssh-copy-id root@你的服务器IP

它会自动将本地的公钥追加到服务器 ~/.ssh/authorized_keys 文件中。输入一次当前密码即可。

3. 修改 SSH 服务端配置,禁止密码登录

登录服务器,编辑 /etc/ssh/sshd_config 文件:

vim /etc/ssh/sshd_config

找到或添加以下配置项:

# 禁止 root 使用密码登录(但允许密钥)
PermitRootLogin prohibit-password

# 彻底关闭密码认证(所有用户均不能用密码)
PasswordAuthentication no

# 关闭空密码
PermitEmptyPasswords no

# 启用公钥认证(确保是 yes)
PubkeyAuthentication yes

特别注意:PermitRootLogin 如果设为 no 会连密钥也禁掉,设为 prohibit-password 才是“只禁密码,不禁密钥”。

4. 检查配置并重载 SSH

修改配置文件后,一定要先检查语法,防止把自己锁在门外:

sshd -t

如果没有报错(静默无输出即为正常),再重载 SSH 服务:

systemctl reload sshd   # 或 service sshd reload

五、加固效果验证

改完后,不要关闭当前 SSH 窗口,另开一个终端窗口测试:

  • 密钥登录:ssh root@你的IP 应该正常登入。
  • 密码登录:如果你在另一台没有私钥的机器上尝试,或者临时把本地私钥移走,会直接报 Permission denied (publickey).,说明密码通道已彻底关闭。

同时查看 fail2ban 的封禁动态:

fail2ban-client status sshd

你会看到 Currently banned 的数量还在慢慢增加。


六、fail2ban 为何轻量?

fail2ban 的工作流程可以直观地用下图表示:

flowchart TD
    A[攻击者 IP 尝试 SSH 登录] --> B{SSH 服务}
    B -->|登录失败| C[写入失败日志到<br>/var/log/auth.log]
    C --> D[`fail2ban` 监控日志<br>(使用 inotify 实时监听)]
    D --> E{统计窗口内<br>失败次数 >= maxretry?}
    E -->|否| F[继续监控,不动作]
    E -->|是| G[调用防火墙命令<br>(iptables / firewalld)]
    G --> H[添加封禁规则<br>(DROP/REJECT 该 IP)]
    H --> I[攻击者 IP 被封禁<br>(持续 bantime 秒)]
    I --> J[(可选)发送通知邮件]
    
    style A fill:#f9f,stroke:#333
    style I fill:#f99,stroke:#333
    style D fill:#9cf,stroke:#333
    style G fill:#9cf,stroke:#333

从图中可以看出,fail2ban 的核心是被动响应:它并不主动检测网络流量,而是安静地“等待”日志事件,仅在异常发生时采取行动。这种设计理念使其极为轻量,具体体现在以下几个方面:

🔹 日志驱动,而非流量驱动

fail2ban 是一个基于日志的入侵防御工具。它不直接拦截或分析网络数据包,而是安静地读取系统或应用的日志文件(如 SSH 的认证日志 /var/log/auth.log)。只有当它从日志中发现异常(如某个 IP 反复登录失败),才会触发响应。

这个发现问题才行动的模式,决定了它平时基本不消耗资源。

🔹 事件驱动的架构

fail2ban 的高效也得益于其事件驱动的架构。它使用 inotify 或 pyinotify 等系统接口来监控日志文件。
这可以理解为它不会每秒都去问日志文件有新内容吗?,而是让系统在文件有变化时主动通知它。这种机制,极大地节省了 CPU 资源。

🔹 资源占用极低

在默认配置下,fail2ban 的资源消耗非常低:

  • 内存占用:通常低于 10MB 到 50MB。
  • CPU 占用:日常运行时,CPU 占用率极低,通常在 0.5% 左右。即使开始封禁 IP,对系统负载的影响也很小。

虽然 fail2ban 本身很轻,但不恰当的配置(如开启过多无用的监控规则或使用低效的日志轮询模式)也会让它变重。通过简单优化,可以进一步降低资源消耗。


最后的话

密码爆破在公网中非常常见,密码登录能关就关。这是性价比最高的安全加固,没有之一。密钥破解的成本远高于收益,攻击者扫到你的机器发现不能输密码,基本就会转向下一个目标。

希望这篇文章能帮到你。

评论 (4)

发表评论

  •  222
    222 2026-10-22 17:31:01

    其实还有一种更直接的方式,就是挂vpn,登录vpn才能访问到服务器

    • 李拾肆
      李拾肆 回复 222 2026-10-22 17:32:05

      说得对,VPN 白名单确实是更"釜底抽薪"的一招——直接把 SSH 端口从公网藏起来,攻击者连门都摸不到,哪来的爆破机会。

      我文章里没重点展开,是因为我当时的场景是:

      • 服务器是对外提供接口的,业务流量必须走公网,不能整个套进 VPN 里
      • SSH 管理口和业务口是分开的,业务走 80/443,SSH 单独限制就行
      • 团队里有人习惯从各种网络环境远程连服务器,挂 VPN 的运维成本会高一点

      所以我的思路是:SSH 层面用密钥 + fail2ban 把爆破风险压到最低,业务层面靠 Nginx 限流和 WAF。

      但你说的方案特别适合纯内网服务器或者只有固定办公网访问的场景,那种情况挂个 WireGuard / OpenVPN,把 22 端口只对 VPN 网段开放,确实是最干净的。可以再加一层:

      # iptables 只允许 VPN 网段访问 22 端口
      iptables -A INPUT -p tcp --dport 22 -s 10.8.0.0/24 -j ACCEPT
      iptables -A INPUT -p tcp --dport 22 -j DROP
      

      相当于 VPN 是门禁卡,fail2ban 是门口的监控,两道防线叠起来,基本就没什么可爆破的了 👍

  • 上大学
    上大学 2026-09-01 09:29:44

    用什么命令查自己是不是被爆破过了?

    • 李拾肆
      李拾肆 回复 上大学 2026-09-01 09:30:16

      可以看 SSH 的认证日志,常用的几个命令:

      1. 看最近的失败登录记录

      grep "Failed password" /var/log/secure
      # 或者 Debian/Ubuntu 系统用:
      grep "Failed password" /var/log/auth.log
      

      2. 统计哪些 IP 爆破过你(按次数排序)

      grep "Failed password" /var/log/secure | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn
      

      3. 看有没有无效用户名被尝试

      grep "Invalid user" /var/log/secure
      

      4. 查看成功登录的记录(确认有没有被攻破)

      grep "Accepted password" /var/log/secure
      

      文章里我就是靠翻日志发现问题的——密密麻麻全是 Failed password for root from xxx,单 IP 一天几百次,root、www、ubuntu、admin 这些常见用户名轮着扫。

      如果日志里全是失败记录那就还好,说明防爆破工具在起作用;如果有 Accepted password 就要警惕了,可能已经被撞库成功了。

  • 呃呃
    呃呃 2026-09-01 08:48:38

    fail2ban怎么读?

    • 李拾肆
      李拾肆 回复 呃呃 2026-09-01 08:51:13

      有,fail2ban 是完全开源的,采用 GPL 许可证。

      • GitHub 仓库:fail2ban/fail2ban
      • 由 French 开发者 Fabien Vaulot 发起,现在由全球贡献者维护
      • 当前版本基于 Python 3,比 DenyHosts(Python 2)更轻量现代

      所以你可以放心用,代码透明,社区活跃,出问题也能自己改。

    • 呃呃
      呃呃 回复 李拾肆 2026-09-01 08:50:15

      这东西有开源吗

    • 李拾肆
      李拾肆 回复 呃呃 2026-09-01 08:49:13

      fail2ban 读作 **/feɪl tuː bææn/**,按字母拆开就是:

      • fail — /feɪl/,"费欧"
      • 2 — /tuː/,读作 "to"
      • ban — /bæn/,"斑"

      连起来就是 费 - 兔-斑 的感觉。

      也可以理解为 "Fail to Ban"(阻止封禁),就是用来把爆破服务器的人封掉的意思。

  • 反复折腾
    反复折腾 2026-08-27 11:44:37

    讲究哥

    • 李拾肆
      李拾肆 回复 呃呃 2026-08-27 18:08:32

      当然可以!拿这篇 SSH 爆破的例子来演示:

      pie title 攻击来源 IP 分布(近7天)
          "欧洲" : 45
          "北美" : 25
          "亚洲" : 20
          "其他地区" : 10
      

      效果大概是这样的饼图,每个扇区占比一目了然。

      如果你想画其他图表,比如饼图标题改成封禁原因占比之类的也可以,只要把代码里的内容替换一下就行~

    • 呃呃
      呃呃 回复 李拾肆 2026-08-27 18:07:49

      饼图能画吗

    • 李拾肆
      李拾肆 回复 呃呃 2026-08-27 15:13:52

      抱歉,我不太清楚咱们这个小站的官网地址呢 😅

      文章里也没提到官网链接,建议你可以:

      • 看看网站页脚有没有相关入口
      • 或者浏览器地址栏直接访问试试?

      如果你知道的话也可以告诉我~

    • 呃呃
      呃呃 回复 李拾肆 2026-08-27 15:12:47

      不是,我问的是我们现在所在的这个网站的官网呀

    • 李拾肆
      李拾肆 回复 fff 2026-08-27 12:46:35

      Mermaid 官网在:https://mermaid.ai

      文档和语法参考在这里:

      另外 GitHub 仓库也值得收藏:https://github.com/mermaid-js/mermaid

      上面有各种图表类型的示例代码,直接复制粘贴就能用~