使用 CrowdSec 为 Debian 服务器实现恶意 IP 自动封禁

公网服务器每天都会遇到各种自动化扫描。

即使没有主动公开服务地址,SSH 端口也经常会收到针对 root、admin、test 等用户名的登录尝试。Web 服务则可能遭遇针对 .env、wp-admin、phpinfo.php 等路径的探测。

这些请求大多来自自动化扫描程序。虽然不一定构成实际入侵,但会产生大量无意义的日志,同时增加服务暴露面。

单纯依靠防火墙静态规则,难以根据攻击行为动态调整封禁名单。

因此,我选择尝试 CrowdSec,通过服务器日志识别恶意行为,再联动 nftables 自动封禁攻击来源。

本文记录在 Debian 12 上部署 CrowdSec 的过程,以及它如何判断一个 IP 是否应该被封禁。

一、CrowdSec 是什么?

CrowdSec 是一个开源的入侵检测与安全响应系统。

它通过分析 SSH、Web Server 等服务产生的日志,检测暴力破解、漏洞扫描、恶意爬虫等行为。

与传统的静态 IP 黑名单不同,CrowdSec 主要根据行为特征决定是否封禁,并且可以利用社区威胁情报。

CrowdSec 的工作可以分为两部分:

  • Security Engine:读取日志,识别攻击,生成封禁决策。
  • Remediation Component(Bouncer):执行封禁决策,例如修改防火墙规则。

两者相互独立。

也就是说,仅安装 CrowdSec Security Engine,并不会自动阻止网络连接。

1.1 工作流程

SSH / Caddy / Nginx
         |
         v
    Application Logs
         |
         v
   CrowdSec Parser
         |
         v
  Detection Scenarios
         |
         v
    Security Alerts
         |
         v
   Decision Profiles
         |
         v
      Local API
         |
         v
   Firewall Bouncer
         |
         v
       nftables
         |
         v
      Block IP

CrowdSec 将日志分析与防火墙执行解耦,意味着它既可以在单台服务器上使用,也能通过中心 API 向多台服务器下发封禁决策。

1.2 CrowdSec 与 Fail2ban

Fail2ban 是 Linux 上常见的自动封禁工具。

两者都能通过日志检测 SSH 暴力破解,但设计思路存在差异。

特性CrowdSecFail2ban
SSH 暴力破解检测支持支持
Web 攻击检测支持支持,通常需要对应过滤规则
社区威胁情报支持通常需要额外集成
分布式部署原生支持通常需要自行集成
封禁方式BouncerAction
检测规则Parser + ScenarioFilter + Jail
配置复杂度相对较高相对较低

如果只需要封禁 SSH 登录失败的 IP,Fail2ban 已经足够。

如果希望同时保护多个 Web 服务、SSH 和其他网络服务,CrowdSec 的模块化架构更有扩展空间。

二、部署环境

本文使用以下环境:

项目配置
操作系统Debian 12 Bookworm
CrowdSec1.8.x
防火墙nftables
安装方式APT
检测目标SSH、Caddy、Nginx
封禁方式Firewall Bouncer

采用原生安装,而不是 Docker。

主要原因是 CrowdSec 需要读取宿主机的 systemd journal,并通过 Bouncer 操作系统防火墙。原生安装能够减少额外的权限、网络和挂载配置。

三、安装 CrowdSec

3.1 添加官方 APT 仓库

首先安装依赖:

sudo apt update

sudo apt install -y \
  debian-archive-keyring \
  ca-certificates \
  curl \
  gnupg

创建密钥目录:

sudo install -d -m 0755 /etc/apt/keyrings

导入 CrowdSec 软件源的签名密钥:

curl -fsSL \
  https://packagecloud.io/crowdsec/crowdsec/gpgkey |
  sudo gpg --dearmor \
  -o /etc/apt/keyrings/crowdsec.gpg

添加软件源:

echo \
  "deb [signed-by=/etc/apt/keyrings/crowdsec.gpg] https://packagecloud.io/crowdsec/crowdsec/any any main" |
  sudo tee /etc/apt/sources.list.d/crowdsec.list

更新 APT 缓存:

sudo apt update

检查软件包来源:

apt-cache policy crowdsec

确认使用的是官方仓库后安装:

sudo apt install -y crowdsec

检查版本:

sudo cscli version

查看服务:

sudo systemctl status crowdsec

3.2 安装 Firewall Bouncer

CrowdSec 本身负责检测,但不直接执行防火墙封禁。

因此,需要额外安装 Firewall Bouncer。

本文采用 nftables:

sudo apt install -y \
  crowdsec-firewall-bouncer-nftables

设置服务自启:

sudo systemctl enable --now \
  crowdsec-firewall-bouncer

检查状态:

sudo systemctl status \
  crowdsec-firewall-bouncer

检查 Bouncer 是否注册到 CrowdSec:

sudo cscli bouncers list

正常情况下应看到:

Name                Valid   Type
firewall-bouncer    true    crowdsec-firewall-bouncer

名称只是示例,实际名称可能包含安装时自动生成的后缀。

Bouncer 的配置文件一般位于:

/etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml

其中需要重点关注:

mode: nftables
api_url: http://127.0.0.1:8080/

此外还包含 API Key。

通过 APT 安装时通常会自动完成 API 注册,因此不建议直接覆盖整个配置文件。

四、配置 SSH 攻击检测

SSH 是公网服务器最常见的攻击入口之一。

即使已经禁用密码登录,仍然可能收到大量无效用户名探测和公钥认证尝试。

4.1 安装 SSH Collection

CrowdSec Hub 提供了预定义的 SSH 检测规则。

安装:

sudo cscli collections install \
  crowdsecurity/sshd

查看:

sudo cscli collections list

Collection 是一组 Parser 和 Scenario 的集合。

其中:

  • Parser 负责理解 SSH 日志。
  • Scenario 负责判断是否构成攻击。

4.2 配置 journald

Debian 12 默认使用 systemd,因此可以直接从 journald 获取 SSH 日志。

首先检查:

sudo journalctl -u ssh.service \
  -n 20 --no-pager

如果能看到 SSH 登录记录,就可以配置 CrowdSec 读取对应的日志。

创建文件:

/etc/crowdsec/acquis.d/ssh.yaml

写入:

source: journalctl

journalctl_filter:
  - "_SYSTEMD_UNIT=ssh.service"

labels:
  type: syslog

这里需要注意两个细节。

第一,journalctl_filter 必须匹配实际的日志字段。

第二,type: syslog 告诉 CrowdSec 采用相应的日志解析流程,不应随意改为 sshd。

如果安装时自动发现的日志源已经包含 SSH,应避免重复采集。

验证配置:

sudo crowdsec -t

重启服务:

sudo systemctl restart crowdsec

检查采集情况:

sudo cscli metrics show acquisition

SSH 出现新的日志事件后,应该可以在采集统计中看到对应来源。

4.3 验证 SSH 日志解析

可以利用 cscli explain 测试日志解析器。

下面使用文档保留 IP,避免暴露真实攻击来源:

sudo cscli explain \
  --log 'Oct 19 18:48:40 server sshd[1234]: Invalid user test from 203.0.113.10 port 51878' \
  --type syslog

如果解析成功,应能看到相应的 syslog 和 SSH 解析流程。

注意:cscli explain 是日志解析诊断工具,不会因为这一条测试记录就自动封禁 IP。

五、配置 Caddy 日志检测

除了 SSH,CrowdSec 还可以根据反向代理的访问日志识别恶意 HTTP 请求。

这里以 Caddy 为例。

5.1 安装 Caddy Collection

sudo cscli collections install \
  crowdsecurity/caddy

如果还使用 Nginx,可以额外安装:

sudo cscli collections install \
  crowdsecurity/nginx

5.2 配置日志采集

假设 Caddy 的访问日志存放在:

/var/log/caddy/

并使用 JSON 格式。

创建:

/etc/crowdsec/acquis.d/caddy.yaml

内容:

filenames:
  - /var/log/caddy/*.log

labels:
  type: caddy

重启 CrowdSec:

sudo crowdsec -t
sudo systemctl restart crowdsec

检查:

sudo cscli metrics show acquisition

如果显示日志被读取和解析,说明采集器与解析器已经正常工作。

不过,这还不代表已经检测到了恶意请求。

5.3 CrowdSec 如何判断 HTTP 攻击?

CrowdSec 可以根据不同 Scenario 判断:

  • 是否连续访问敏感文件。
  • 是否尝试探测大量不存在的路径。
  • 是否存在已知漏洞探测行为。
  • 是否出现异常 HTTP 请求频率。
  • 是否匹配恶意 HTTP 客户端行为特征。

例如,访问以下路径:

/.env
/wp-admin/
/phpinfo.php
/.git/config

这些路径经常出现在自动化扫描中。

但并非访问一次就一定触发封禁。

CrowdSec 会结合具体 Scenario 的规则判断,避免将正常访问误判为攻击。

六、CrowdSec 什么时候自动封禁 IP?

这是使用 CrowdSec 时最值得理解的问题。

CrowdSec 并不是检测到一次 SSH 登录失败就立即封禁,而是在行为达到 Scenario 的检测条件后生成告警与封禁决策。

6.1 Leaky Bucket 算法

CrowdSec 的许多 Scenario 使用 Leaky Bucket(漏桶)算法。

可以把每个 IP 想象成一个独立的水桶:

  • 每次符合条件的攻击事件向桶中加入一个单位。
  • 桶会按照设定速度持续泄漏。
  • 如果事件积累速度超过泄漏速度,导致桶溢出,就会触发攻击告警。

例如,一个假设的规则:

type: leaky
capacity: 5
leakspeed: "10s"

表示:

  • 桶容量为 5。
  • 每 10 秒泄漏一个事件。
  • 当积累事件超出容量时触发溢出。

如果攻击者在短时间内不断尝试登录,就可能触发封禁。

如果每隔几分钟才尝试一次,事件可能在积累到阈值之前就已经泄漏。

这也是为什么某些 IP 明明连续扫描了多个用户名,却没有立即被封禁。

6.2 查看实际检测阈值

不要凭印象判断 CrowdSec 的默认阈值。

不同 Scenario、Hub 版本以及本地自定义规则,可能使用不同参数。

查看 SSH 暴力破解规则:

sudo cscli scenarios inspect \
  crowdsecurity/ssh-bf

也可以直接查看已安装的规则文件:

cat /etc/crowdsec/scenarios/ssh-bf.yaml

重点关注:

capacity:
leakspeed:
groupby:
distinct:

其中:

  • capacity 决定桶的容量。
  • leakspeed 决定事件消退速度。
  • groupby 通常用来源 IP 分组。
  • distinct 用来避免重复事件被反复计数。

因此,CrowdSec 不能简单理解为“10 分钟内失败 5 次就封禁”。

真正触发条件应以 Scenario 的完整规则为准。

6.3 封禁持续多久?

CrowdSec 在产生 Alert 后,通过 Profile 决定是否生成封禁 Decision。

查看:

cat /etc/crowdsec/profiles.yaml

常见的 IP 封禁策略如下:

name: default_ip_remediation

filters:
  - Alert.Remediation == true && Alert.GetScope() == "Ip"

decisions:
  - type: ban
    duration: 4h

on_success: break

其中:

duration: 4h

表示封禁持续 4 小时。

实际安装的 Profile 可能还有其他条件或策略,因此修改前应该检查完整配置,避免重复定义同名规则。

6.4 从告警到真正封禁

即使 Scenario 已经触发,还需要满足后续条件:

  1. 检测场景产生 Alert。
  2. Profile 将 Alert 转换为 Decision。
  3. Firewall Bouncer 拉取 Decision。
  4. nftables 将来源 IP 加入封禁集合。
  5. 实际防火墙规则命中该来源 IP。

如果其中某一步失败,就可能出现 CrowdSec 显示了攻击告警,但网络连接并没有被阻止的情况。

七、验证自动封禁是否生效

安装成功并不意味着整个封禁链路已经验证成功。

建议分成两个部分:先验证防火墙执行,再验证攻击检测。

7.1 检查 Bouncer

sudo cscli bouncers list

确认:

  • Bouncer 已注册。
  • API 认证有效。
  • 最近仍在拉取 Decision。

7.2 创建测试封禁

使用 RFC 5737 保留的文档示例 IP:

sudo cscli decisions add \
  --ip 203.0.113.10 \
  --type ban \
  --duration 5m \
  --reason manual-test

查看决策:

sudo cscli decisions list

接下来检查 nftables:

sudo nft list ruleset | grep -i crowdsec

确认封禁 IP 是否进入对应集合:

sudo nft list ruleset | grep '203.0.113.10'

如果可以看到测试 IP,并且该集合被有效的防火墙规则引用,就说明 Bouncer 已经将封禁决策同步到防火墙。

测试完毕后解除:

sudo cscli decisions delete \
  --ip 203.0.113.10

注意,203.0.113.10 是文档测试地址,适合验证决策同步,不适合测试真实网络连接是否被阻断。

7.3 查看真实攻击告警

sudo cscli alerts list

查看封禁决策:

sudo cscli decisions list

查看 Scenario 指标:

sudo cscli metrics show scenarios

若某个 Scenario 的 Overflow 计数增加,说明攻击检测规则曾触发。

但是否产生封禁,还要结合 Profile、Simulation 和 Decision 一起判断。

检查 Simulation 状态:

sudo cscli simulation status

如果 Scenario 处于模拟模式,可能只产生检测结果,不执行封禁。

7.4 避免误封自己的管理 IP

公网服务器测试封禁时,最重要的是不要把唯一的管理入口封掉。

建议:

  • 提前准备云平台提供的控制台。
  • 不要直接封禁当前 SSH 客户端的出口 IP。
  • 对可信的固定管理地址设置必要的 AllowList。
  • 调整策略前先确认 nftables 规则优先级。
  • 使用另一台受控设备进行真实连接测试。

八、CrowdSec 会检查旧日志吗?

默认情况下,CrowdSec 主要以实时日志分析为主,并不保证启动时自动扫描安装之前的所有历史日志。

例如,在部署 CrowdSec 前,SSH 日志中可能已经记录了几百次失败登录。

这些事件不会仅仅因为安装了 CrowdSec 就全部变成当前的封禁决策。

8.1 常规运行方式

情况处理方式
新产生的 SSH 日志实时检测
新产生的 Caddy 日志实时检测
安装前的历史日志通常不自动完整回溯
需要重新分析旧日志使用 Replay Mode

实际读取起点仍可能受到数据源及其配置影响。

8.2 导出历史 SSH 日志

例如导出过去 24 小时:

sudo journalctl -u ssh.service \
  --since "24 hours ago" \
  --output=short \
  --no-pager \
  > /tmp/ssh-history.log

CrowdSec 支持通过 Replay Mode 分析已有的日志文件。

它能够根据日志中的原始时间戳重放事件,判断当时是否达到检测阈值。

但需要注意,Replay 可能向 API 提交告警,因此不应随意在生产环境直接回放大量历史攻击日志。

更稳妥的方式是使用独立配置和测试环境,避免影响实际封禁决策。

官方文档:

https://docs.crowdsec.net/u/user_guides/replay_mode/

九、日常管理

CrowdSec 提供了命令行工具 cscli,大部分日常维护都可以在终端完成。

9.1 查看服务状态

systemctl status crowdsec
systemctl status crowdsec-firewall-bouncer

9.2 查看日志采集情况

sudo cscli metrics show acquisition

9.3 查看 Parser 指标

sudo cscli metrics show parsers

9.4 查看 Scenario 指标

sudo cscli metrics show scenarios

9.5 查看封禁 IP

sudo cscli decisions list

9.6 手动封禁

sudo cscli decisions add \
  --ip 203.0.113.10 \
  --duration 4h \
  --reason manual-ban

9.7 手动解除

sudo cscli decisions delete \
  --ip 203.0.113.10

9.8 查看 CrowdSec 日志

sudo journalctl -u crowdsec -f

查看 Bouncer 日志:

sudo journalctl \
  -u crowdsec-firewall-bouncer -f

9.9 更新规则

CrowdSec Hub 的规则可以独立更新:

sudo cscli hub update
sudo cscli hub upgrade

更新检测规则不等同于升级 CrowdSec 软件本身。软件更新仍通过 APT 管理。

十、常见问题

CrowdSec 已经运行,但没有封禁任何 IP

先检查日志采集:

sudo cscli metrics show acquisition

然后检查解析器:

sudo cscli metrics show parsers

再检查:

sudo cscli metrics show scenarios
sudo cscli alerts list
sudo cscli decisions list

最后检查 Bouncer 与 nftables:

sudo cscli bouncers list
sudo nft list ruleset

正常的排查顺序应该是:

Log Acquisition
      ↓
Parser
      ↓
Scenario
      ↓
Alert
      ↓
Decision
      ↓
Bouncer
      ↓
Firewall

不要在尚未确认日志被正确读取时就急于修改封禁阈值。

为什么攻击日志很多,却没有生成封禁?

因为日志数量不等于符合检测条件的事件数量。

部分日志可能没有被正确解析,也可能不符合 Scenario 的 filter。

另外,即使同一个 IP 多次失败登录,如果两次事件的间隔足够长,Leaky Bucket 也可能不会溢出。

为什么显示已经封禁,但仍然能访问?

可能原因包括:

  • Bouncer 尚未同步 Decision。
  • nftables 规则没有正确挂载到流量经过的 Hook。
  • 防火墙规则顺序不正确。
  • IPv4 和 IPv6 没有同时覆盖。
  • 实际流量经过反向代理,日志中的 IP 并非真实客户端地址。
  • 被拦截的连接与当前测试连接不是同一来源。

因此,cscli decisions list 只能证明 CrowdSec 生成了决策,不能单独证明网络层拦截成功。

CrowdSec 会自动解封吗?

会。

当封禁 Decision 到期后,Bouncer 应同步更新防火墙集合,移除对应 IP。

不过,如果相同 IP 同时被其他防火墙规则封禁,CrowdSec 解封并不会自动移除那些独立规则。

十一、总结

CrowdSec 的核心价值不仅是自动封禁 IP,而是将日志解析、行为检测和封禁执行拆分为独立组件。

这使得它能够从最基础的 SSH 防暴力破解,逐步扩展到 Web 服务、反向代理和多服务器集中防护。

实际部署时,需要重点理解三个问题:

第一,CrowdSec 不是根据固定的失败次数直接封禁。 它通过 Scenario 判断行为是否异常,许多规则采用 Leaky Bucket 算法,因此攻击的频率和持续时间都会影响检测结果。

第二,检测成功不等于封禁成功。 Alert、Decision、Bouncer 和防火墙规则是不同阶段,排查问题时应逐层确认。

第三,CrowdSec 更适合持续监控新产生的安全事件。 如果需要分析历史攻击,可以使用 Replay Mode,但应避免让历史告警意外影响生产封禁。

对于已经使用 Debian、systemd、Caddy 或 Nginx 的服务器而言,CrowdSec 配合 nftables 是一种值得考虑的自动化安全防护方案。

它不能替代及时更新软件、安全配置、SSH 公钥认证和最小权限原则,但能够减少大量重复的自动化扫描和暴力破解请求。

参考资料