服务器被爆破 2.5 万次后,我搭了一套自动防御与攻击地图

先看一组数字。

我这台小小的云服务器,从监控上线到现在,累计记录了 25215 次失败登录;光是最近 24 小时,就有 477 次。榜单第一名的 IP(51.89.42.*)对着 SSH 端口,试了 4400 多次密码。

而这台服务器上:不跑电商、不存数据、没有任何"值钱"的东西。它只是有公网 IP。

有公网 IP,就等于站在广场中央

很多人对攻击的想象是"被盯上了":某个人查到你,然后对你发起进攻。真实的互联网不是这样运作的。

全网有无数扫描器在批量遍历 IP,像发传单一样把常用端口挨个试一遍。SSH 的 22 端口、数据库的 3306、面板的 8888……谁家开着门,谁就被记进一张"待处理清单"。整个过程没有人在针对你,你只是恰好被扫到了。

不是你被盯上了,而是整张网都在被翻。

这也是我做上一轮整改的原因:密码进保险箱、SSH 改成仅密钥、管理面板端口从公网关掉。那些解决的是"进不来"的问题。但还有另一半问题没解决:门外的噪音。

失败登录日志每天在涨、慢速爆破像水滴一样磨、分布式扫描从几十个 IP 轮流来。手动一条条封,根本封不过来。所以我决定给它配一整套自动防御。

门外永远有人在试钥匙
图 1 · 门外永远有人在试钥匙

第一层:fail2ban,快刀斩乱麻

fail2ban 干的事非常朴素:盯着登录日志,同一个 IP 短时间失败多次,就把它扔进防火墙黑名单。

我的配置是 5 次失败 / 10 分钟触发,首次封禁 1 天,再犯就翻倍,最长 4 周。这层负责处理最没技术含量的那批攻击——高频爆破。它们量最大,但最好处理:封得够快,它们连门把手都摸不到。

# 看看当下的战况
sudo fail2ban-client status sshd

输出里的"Currently banned"就是当下被拒之门外的数量。有意思的是它经常只有个位数——不是没人攻击,而是攻击者一旦被秒封,就不再产生新的失败记录了。

第二层:CrowdSec,盯住"滴水"的人

fail2ban 的短板也很明显:它只会数次数。如果有人每 10 分钟试 1 次,永远触发不了阈值,但对一台服务器来说,这依然很烦。

CrowdSec 补的就是这块。它同样看日志,但引入了"场景"的概念:高频爆破、慢速爆破、时间型爆破、端口探测、拒绝连接探测,还有针对 OpenSSH 那个 CVE-2024-6387 的利用尝试,一共 6 个 SSH 场景在同时跑。

它把每条可疑记录扔进对应的"水桶"里,桶满了就出告警、下决策。fail2ban 漏过的慢速与分布式攻击,会在这里慢慢积累成形。

第三层:bouncer,把决策写进防火墙

CrowdSec 只负责"想",不负责"拦"。真正落刀的是 crowdsec-firewall-bouncer:它每 10 秒从本地接口拉一次封禁决策,写进一个叫 ipset 的集合,再把集合挂到 iptables 的 INPUT 和 DOCKER-USER 链上。

为什么用 ipset?因为决策可能是几百上千条,逐条写进 iptables 又慢又乱;ipset 是一个哈希集合,防火墙一次匹配,开销几乎可以忽略。挂在 DOCKER-USER 上还带来一个额外好处:连容器里的流量也一起管。

除了本机检测,注册 CrowdSec Console 之后还能同步社区黑名单——全球受攻击的服务器把恶意 IP 共享出来,你的防火墙白捡一份"通缉名单"。

再加上云厂商自带的云镜,四层叠起来大概是这样:fail2ban 处理最快的那批,CrowdSec 处理最阴的那批,bouncer 负责执行,云镜兜底。

四道闸门,各管一段
图 2 · 四道闸门,各管一段

光封还不够:我想看见它

安全做久了会有一个隐忧:所有东西都在后台悄悄运行,你怎么知道它到底有没有在工作?

所以我加了两块"仪表盘"。

一块是 Grafana 看板(Prometheus 抓 CrowdSec 的指标),首页是"防护总览":封禁数、攻击趋势、Top 场景、实时封禁明细。趋势图会在夜里规律性地跳起来——那是扫描器们上夜班的时间。

另一块是我最喜欢的:实时攻击地图。脚本每 5 分钟统计一次登录日志里最近 24 小时的失败 IP,取前 100 名做地理定位,打点到一张世界地图上。哪里的 IP 在敲门、敲了多少次、属于哪种攻击类型,全在上面。

配合地图还有 TOP5 攻击源、攻击类型细分、国家排行。底图用的是高德暗色,加载失败会自动降级到备用地图。统计脚本和前端都是独立的,挂在定时任务里自己跑,不依赖面板。

把攻击画成地图之后,安全从一条条日志,变成了"看得见的天气"。
攻击地图:24 小时里的敲门记录
图 3 · 攻击地图:24 小时里的敲门记录

日常怎么管

整套系统平时不需要我操心,但有三个动作最常用。

看战况:

sudo docker exec crowdsec cscli decisions list

手动封一个烦人的 IP,立即生效:

sudo docker exec crowdsec cscli decisions add --ip 1.2.3.4 --duration 24h --reason manual

解封误伤:

sudo docker exec crowdsec cscli decisions delete --ip 1.2.3.4

这里有个反直觉的点:告警列表经常是空的。原因不难理解——fail2ban 封得太快,IP 被拉黑之后连失败日志都产生不了,CrowdSec 自然收不到告警。想看它一直在干活,去看"场景指标"那个水桶统计,数字一直在涨。

看板看趋势,按钮管手动
图 4 · 看板看趋势,按钮管手动

顺便,把"公示栏"也配齐了

在国内跑网站有个绕不开的环节:备案。ICP 备案和公安联网备案办完之后,按规定要把备案号和图标挂在页面底部。

博客本站用主题原生设置就够了;另外几个子域(监控面板、AI 网关、运维面板)则是用 Nginx 的 sub_filter 注入了一条底部的备案细条,还做了个小判断:页面上有密码输入框(也就是登录页)时显示,登录后自动隐藏。

中间有段时间站点配置被面板重建,页脚丢了,于是我在服务器上留了个幂等脚本,重跑一次就补回来——已经补过的会提示"already patched",不会重复注入。这种"配置会被面板覆盖"的场景,留一个兜底脚本比记一堆手动步骤靠谱得多。

如果你想照做:技术实现清单

整套东西没有自研组件,都是现成的开源工具拼起来。下面是我这边的实际组合,基本可以直接抄。

fail2ban(宿主)——封得最快的那一层:

# /etc/fail2ban/jail.d/sshd.local
[sshd]
enabled  = true
maxretry = 5
findtime = 10m
bantime  = 1d
bantime.increment = true
bantime.factor    = 2
bantime.maxtime   = 4w

CrowdSec(Docker)——检测层。核心是把宿主机的 auth.log 只读挂进容器,并装上 sshd 场景:

# docker-compose.yml(节选)
services:
  crowdsec:
    image: crowdsecurity/crowdsec:latest
    container_name: crowdsec
    environment:
      COLLECTIONS: "crowdsecurity/linux crowdsecurity/sshd"
    volumes:
      - ./acquis.d:/etc/crowdsec/acquis.d
      - /var/log/auth.log:/var/log/auth.log:ro
      - crowdsec-db:/var/lib/crowdsec/data
    ports:
      - "127.0.0.1:8088:8088"   # LAPI 只给本机用

# acquis.d/sshd.yaml:告诉它读哪个日志
filenames:
  - /var/log/auth.log
labels:
  type: syslog

firewall-bouncer(宿主)——执行层。装好之后用 cscli 生成一把 key,填进 bouncer 配置,它就会自动维护 ipset 和 iptables 规则:

sudo apt install crowdsec-firewall-bouncer-iptables
sudo docker exec crowdsec cscli bouncers add firewall-bouncer
# 然后把生成的 key 填进:
# /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml
# api_url: http://127.0.0.1:8088/
# api_key: <上面生成的 key>

可视化——Prometheus 抓 CrowdSec 的 6060 指标端口,Grafana 挂官方 CrowdSec 看板;面板本身用 Nginx 反代加 acme.sh 签发的证书上 HTTPS。攻击地图则是三个脚本加一条定时任务:

/opt/attack-map/
├── extract_attack_ips.py   # 解析 auth.log,取最近 24h 失败 IP TOP100
├── build_map.py            # ip-api.com 定位 + 生成地图 JSON
└── update-attack-map.sh    # 串起来,写出 webroot/attack-map.json

# /etc/cron.d/attack-map:每 5 分钟跑一次
*/5 * * * * root /opt/attack-map/update-attack-map.sh

地图前端就是一张静态页加一点 JavaScript:读 JSON、在高德暗色底图上打点;高德挂了自动降级到备用底图。页面上只显示目标服务器的域名,不暴露 IP。

备案栏在子域站点上是这么注入的——不改页面源码,用 Nginx 的 sub_filter 在 </body> 前插一条:

sub_filter '</body>' '<div class="site-beian">备案号 / 公安备案图标</div></body>';
sub_filter_once on;

再配一小段判断:页面里有密码输入框(登录页)才显示,登录后隐藏。整个方案还留了一个幂等补丁脚本,站点配置被面板重建后重跑一次就能补回来,补过的会提示跳过。

这套链路的特点是"松耦合":日志、检测、封禁、可视化各自独立,任何一层坏掉都不会拖垮其他层,也方便以后单独替换。

写在最后

做完这一整套,我最大的感受是:安全防护不是"换一把更贵的锁",而是把门口发生的事变成一套自动运转的系统——有人试钥匙,就有人记录、上锁、画图。

代价呢?几乎没有。整套东西跑在一台 3GB 内存的小服务器上,日常占用可以忽略,倒是让我养成了每天打开攻击地图看一眼的习惯。

当然,这些都是"亡羊补牢"式的建设。如果你的服务器还开着密码登录、面板端口裸奔在公网,先把那些关上,再来加这套自动防御。

最后留个问题:如果你也有一台带公网 IP 的机器,你知道它每天被扫多少次吗?评论区聊聊。


本文由个人实践整理,文中服务器与攻击者 IP 均已做模糊处理。


服务器被爆破 2.5 万次后,我搭了一套自动防御与攻击地图
https://linxiang.cloud//archives/server-security-crowdsec-attack-map
作者
Administrator
发布于
2026年09月26日
许可协议