静看光阴荏苒
不管不顾不问不说也不念

Debian部署CrowdSec保护SSH和Web服务

CrowdSec的一大亮点就是有一个CAPI(官方的中央API服务器)专门收集和共享各类威胁情报,其中包括攻击者的IP,这些恶意IP会被记录到“社区黑名单”,CrowdSec会定期从CAPI拉取这份黑名单来保护你的服务器。同时如果你的服务器刚被攻击了,那么CrowdSec也会把刚刚发现的恶意IP提交给CAPI,全球其它数万个部署了CrowdSec的服务器也在实时上报,这样就提供了一套联动的防御体系。平子有人类命运共同体,CrowdSec有网络安全共同体

除此之外,CrowdSec的整体设计架构也挺有意思,有人说它和Fail2Ban很像,都是读取、分析系统日志然后作出决策,但现如今的CrowdSec已经远不止这些能力了,CrowdSec不久前还推出了AppSec组件,它可以将你的CrowdSec安装变成一个功能齐全的WAF(虽然肯定不如长亭的雷池

本文将详细介绍CrowdSec的安装与使用方法。我的服务器系统是Debian,Web服务器是NGINX。

安装需要用到的软件包:

apt update
apt install curl gnupg apt-transport-https debian-archive-keyring

导入GPG密钥:

mkdir -p /etc/apt/keyrings/
curl -fsSL https://packagecloud.io/crowdsec/crowdsec/gpgkey | gpg --dearmor > /etc/apt/keyrings/crowdsec_crowdsec-archive-keyring.gpg

创建存储库配置文件:

nano /etc/apt/sources.list.d/crowdsec_crowdsec.list

写入如下内容:

deb [signed-by=/etc/apt/keyrings/crowdsec_crowdsec-archive-keyring.gpg] https://packagecloud.io/crowdsec/crowdsec/any any main
deb-src [signed-by=/etc/apt/keyrings/crowdsec_crowdsec-archive-keyring.gpg] https://packagecloud.io/crowdsec/crowdsec/any any main

现在就可以安装CrowdSec了:

apt update
apt install crowdsec

CrowdSec LAPI(Local API)默认监听的端口是8080(127.0.0.1:8080)如果服务器已经有程序占用了8080端口,毕竟8080算是一个比较热门的端口,很多程序都喜欢用这个端口,此时为了避免端口冲突CrowdSec是不会自己启动的,你需要将LAPI的端口改为其它的才能让CrowdSec正常运行,为此我们得修改两个配置文件:

nano /etc/crowdsec/config.yaml

这里我将其改为8088:

api:
  server:
    log_level: info
    listen_uri: 127.0.0.1:8088

还有这个配置文件:

nano /etc/crowdsec/local_api_credentials.yaml

将url改为如下所示内容:

url: http://127.0.0.1:8088

启动并设置开机自启:

systemctl enable --now crowdsec

默认情况下,CrowdSec会尝试检测服务器正在运行的服务(CrowdSec >= 1.7.0),然后安装相应的日志源和集合。例如我的服务器在安装CrowdSec前就有SSH服务、NGINX服务在运行,那么CrowdSec就会自动将这些日志源和集合安装好,可以执行如下命令查询当前已经安装的日志源:

cscli metrics show acquisition

这里就列出了我的SSH和NGINX日志源,这里我还有一个Appsec的日志源,这个我稍后说明:

╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Acquisition Metrics                                                                                                                       │
├─────────────────────────────────────────────────┬────────────┬──────────────┬────────────────┬────────────────────────┬───────────────────┤
│ Source                                          │ Lines read │ Lines parsed │ Lines unparsed │ Lines poured to bucket │ Lines whitelisted │
├─────────────────────────────────────────────────┼────────────┼──────────────┼────────────────┼────────────────────────┼───────────────────┤
│ appsec:appsec                                   │ 221        │ 221          │ -              │ 210                    │ -                 │
│ file:/var/log/nginx/access.log                  │ 2.41k      │ 2.40k        │ 1              │ 1.09k                  │ -                 │
│ file:/var/log/nginx/error.log                   │ 422        │ 259          │ 163            │ 235                    │ -                 │
│ journalctl:journalctl-_SYSTEMD_UNIT=ssh.service │ 13.90k     │ 9.14k        │ 4.76k          │ 35.87k                 │ -                 │
╰─────────────────────────────────────────────────┴────────────┴──────────────┴────────────────┴────────────────────────┴───────────────────╯

此时假设你不打算将NGINX作为Web服务器了,想用Caddy替换NGINX,那么你就需要手动配置一个新的日志源。先去CrowdSec的Hub找到你需要的集合:

找到Caddy的集合URL

根据集合页面提供的命令安装Caddy集合:

cscli collections install crowdsecurity/caddy

CrowdSec Hub上的每个集合都包含一个采集示例,Caddy也不例外,所以你只需要在acquis.d目录下创建一个日志采集的配置文件:

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

对于Caddy而言,你应该写入如下内容:

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

同时你的Caddyfile配置文件内应该指定日志文件的路径。重启CrowdSec使新的配置生效:

systemctl restart crowdsec

请注意CrowdSec或者说CrowdSec的安全引擎,本身是一个检测引擎(IDS),它只做行为检测,不负责阻止攻击行为。

我们刚才配置的这些内容如果拆分的更细一点,其实就是配置了:log-parsers(日志解析器) + scenarios(攻击场景) + Acquisition(日志采集)

其中日志解析器和攻击场景都是由collections(集合)提供的,集合就是自动打包好了这些内容,方便你一次性部署,你就不需要单独去一个个部署了,如果你没有特殊的需求,我们后续在使用过程中都是直接安装集合,很少单独去安装日志解析器和攻击场景。

CrowdSec在首次部署时会根据当时系统内运行的服务自动安装集合、配置日志采集,但如果后续你的系统内安装了新的服务,你想用CrowdSec保护它,那就需要你手动配置了,刚才我们已经使用Caddy实战演示过一遍完整的流程了。

在较新版本的CrowdSec中还有交互式自动探测与添加模式,如果你不想手动配置,可以尝试使用这个命令:

cscli setup interactive

静默全自动添加(适用于自动化脚本):

cscli setup unattended

使用自动化的方式往往没有手动配置可靠,如果可以请一定手动检查添加的配置是否正确。

CrowdSec基本的工作流程是:先采集某个服务的日志,然后将采集到的日志结构化解析再去和攻击场景匹配,比如一个攻击场景描述的是:只要一个IP在10秒内发起了5次SSH登录验证,且全部登录失败,那就说明这个IP在暴力破解SSH。当一个IP触发了此攻击场景后,CrowdSec的安全引擎会作出一个决策:把这个IP封禁4小时。

请注意这里的决策:“把这个IP封禁4小时”,此时的IP根本没有被真正的封禁,它只是CrowdSec安全引擎给出的一个建议或者说决定,还没有真正的去执行。

谁去执行CrowdSec安全引擎的决策?此时就需要你安装对应的Remediation Components(修复组件)了,官方以前又称为Bouncers(拦截器),这才是真正去执行决策的工具。在你安装并配置好修复组件后,CrowdSec就会从IDS变成一个IPS(入侵防御系统)。

下面我们就来实战配置一个防火墙Bouncer,防火墙Bouncer非常适合保护SSH基础架构服务。先查看当前系统使用的是iptables还是nftables:

iptables -V

如果回显中有显示nf_tables,那么就说明你的系统正在使用nftables:

iptables v1.8.11 (nf_tables)

此时你应该安装的软件包是:

apt install crowdsec-firewall-bouncer-nftables

否则安装:

apt install crowdsec-firewall-bouncer-iptables

查看防火墙bouncers运行状态,确保正常:

systemctl status crowdsec-firewall-bouncer.service

防火墙bouncers几乎是开箱即用的,你在安装好后无须修改任何配置应该就能很好的工作。

让我们更进一步,现在来保护Web服务(NGINX)。我的服务器之前已经安装好NGINX了,所以这里我不需要重新安装NGINX,并且我在安装CrowdSec前就已经安装了NGINX,那么CrowdSec会自动帮我配置好NGINX的集合和日志采集,所以这里我只需要安装NGINX bouncers需要用到的依赖即可:

apt install lua5.1 libnginx-mod-http-lua luarocks gettext-base lua-cjson

如果你没有安装NGINX,则这里需要先安装,仅支持Debian官方存储库的NGINX软件包,如果你是NGINX官方存储库安装的NGINX或者自己编译的NGINX则这里的步骤不适合你:

apt install nginx

然后安装对应的集合:

cscli collections install crowdsecurity/nginx

配置日志采集这里就不重复说明了,这是CrowdSec自动为我配置的,仅供参考:

filenames:
  - /var/log/nginx/*.log
labels:
  type: nginx
source: file

接下来安装crowdsec-nginx-bouncer:

apt install crowdsec-nginx-bouncer

这里有个坑,可能会遇到这个报错:

lua-cjson 2.1.0.10-1 depends on lua >= 5.1 (5.1-1 provided by VM)
gcc -O2 -fPIC -I/usr/include/lua5.1 -c lua_cjson.c -o lua_cjson.o
sh: 1: gcc: not found
Error: Build error: Failed compiling object lua_cjson.o

原因是系统内缺少编译用的gcc,也可能还缺少其它编译用的依赖包,所以这里安装一下这个全家桶:

apt install build-essential

将之前的crowdsec-nginx-bouncer软件包卸载重装以触发重新编译:

apt purge crowdsec-nginx-bouncer
apt install crowdsec-nginx-bouncer

由于安装了两次crowdsec-nginx-bouncer软件包,在CrowdSec那边可能会出现多个相同的Bouncer,可以通过这个命令查看:

cscli bouncers list

我不知道哪个是不再需要的Bouncer,所以我使用curl往NGINX发送一个请求:

curl 127.0.0.1

稍等片刻再次查看bouncers list应该就能发现Last API pull的时间更新了,没更新的那个就是没用的,可以执行如下命令删除:

cscli bouncers delete crowdsec-nginx-bouncer-1784122720

我还发现一个问题,crowdsec-nginx-bouncer软件包不支持Debian 11,我在另外一台Debian 11服务器内安装会报错。排查了一下发现是该软件包对NGINX版本有硬性限制:

crowdsec-nginx-bouncer
  Depends: nginx
    nginx-core
    nginx-extras
    nginx-full
    nginx-light
  Depends: lua5.1
  Depends: libnginx-mod-http-lua
  Depends: luarocks
  Depends: gettext-base
  Breaks: libnginx-mod-http-lua (<< 1.20)
  Breaks: nginx (<< 1.20)

此修改见Pull。这意味着如果NGINX版本低于1.20将无法安装,而Debian 11官方存储库内的NGINX版本为1.18。如果你也遇到这个问题,可以改为手动安装:

wget https://github.com/crowdsecurity/cs-nginx-bouncer/releases/download/v1.2.0/crowdsec-nginx-bouncer.tgz
tar xvzf crowdsec-nginx-bouncer.tgz
cd crowdsec-nginx-bouncer-v1.2.0/
./install.sh

经过测试,手动安装的crowdsec-nginx-bouncer可以完美工作在NGINX 1.18,至少目前我没有发现有什么问题,搞不懂官方为什么要放弃对NGINX 1.20之前的版本支持。

请注意手动安装crowdsec-nginx-bouncer也需要先安装之前提到的那些依赖包,尤其是build-essential,如果你在执行install.sh安装脚本后也遇到了编译lua_cjson.o报错,请补全依赖后执行如下命令重新编译lua_cjson.o

luarocks install lua-cjson 2.1.0.10-1

在默认情况下,CrowdSec安全引擎的决策是封禁(ban),当crowdsec-nginx-bouncer执行这个决策后,用户将无法访问你的网站,这在很多时候太过于武断了,可能会误杀正常访问的真实用户,此时你可以将HTTP场景的决策改为更灵活的验证码挑战(captcha)。配置验证码挑战,主要分为两个部分,既CrowdSec安全引擎要能够支持验证码挑战的决策,同时Bouncer还需要拥有“展示验证码”的能力。

我们先来配置crowdsec-nginx-bouncer,目前支持的验证码有:recaptcha / hcaptcha / turnstile,这里我选择使用turnstile。编辑crowdsec-nginx-bouncer的配置文件:

nano /etc/crowdsec/bouncers/crowdsec-nginx-bouncer.conf

修改如下内容:

CAPTCHA_PROVIDER=turnstile
# Captcha Secret Key
SECRET_KEY=
# Captcha Site key
SITE_KEY=
CAPTCHA_TEMPLATE_PATH=/var/lib/crowdsec/lua/templates/captcha.html
CAPTCHA_EXPIRATION=3600

为了让验证码挑战功能能够正常工作,你还需要编辑这个NGINX配置文件添加一个DNS resolver:

nano /etc/nginx/conf.d/crowdsec_nginx.conf

写入如下内容:

resolver 1.1.1.1 ipv6=off;

同时请确保该配置文件内有这一行内容,如果没有请自己加上:

lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;

重启NGINX服务使新配置生效:

systemctl restart nginx

接下来配置CrowdSec安全引擎,编辑profiles.yaml决策配置文件:

nano /etc/crowdsec/profiles.yaml

在文件的顶部加入如下内容:

name: captcha_remediation
filters:
  - Alert.Remediation == true && Alert.GetScope() == "Ip" && Alert.GetScenario() contains "http"
## Any scenario with http in its name will trigger a captcha challenge
decisions:
 - type: captcha
   duration: 4h
on_success: break
---

修改后的文件内容应该长这样:

name: captcha_remediation
filters:
  - Alert.Remediation == true && Alert.GetScope() == "Ip" && Alert.GetScenario() contains "http"
## Any scenario with http in its name will trigger a captcha challenge
decisions:
 - type: captcha
   duration: 4h
on_success: break
---
name: default_ip_remediation
#debug: true
filters:
 - Alert.Remediation == true && Alert.GetScope() == "Ip"
decisions:
 - type: ban
   duration: 4h
#duration_expr: Sprintf('%dh', (GetDecisionsCount(Alert.GetValue()) + 1) * 4)
# notifications:
#   - slack_default  # Set the webhook in /etc/crowdsec/notifications/slack.yaml before enabling this.
#   - splunk_default # Set the splunk url and token in /etc/crowdsec/notifications/splunk.yaml before enabling this.
#   - http_default   # Set the required http parameters in /etc/crowdsec/notifications/http.yaml before enabling this.
#   - email_default  # Set the required email parameters in /etc/crowdsec/notifications/email.yaml before enabling this.
on_success: break
---
name: default_range_remediation
#debug: true
filters:
 - Alert.Remediation == true && Alert.GetScope() == "Range"
decisions:
 - type: ban
   duration: 4h
#duration_expr: Sprintf('%dh', (GetDecisionsCount(Alert.GetValue()) + 1) * 4)
# notifications:
#   - slack_default  # Set the webhook in /etc/crowdsec/notifications/slack.yaml before enabling this.
#   - splunk_default # Set the splunk url and token in /etc/crowdsec/notifications/splunk.yaml before enabling this.
#   - http_default   # Set the required http parameters in /etc/crowdsec/notifications/http.yaml before enabling this.
#   - email_default  # Set the required email parameters in /etc/crowdsec/notifications/email.yaml before enabling this.
on_success: break

重启CrowdSec使新的决策配置生效:

systemctl restart crowdsec

然后我们模拟真实攻击场景进行测试,看看验证码挑战是否能够正常工作,往NGINX日志里面写一些假的访问日志:

for i in {1..15}; do
  echo "1.2.3.4 - - [22/Jul/2026:12:00:00 +0000] \"GET /random-test-$i.html HTTP/1.1\" 404 150 \"-\" \"Mozilla/5.0\"" | tee -a /var/log/nginx/access.log
done

将1.2.3.4换成你的代理IP或者蜂窝移动网络IP,然后查看决策列表:

cscli decisions list

正常的话应该有一条如图所示的记录:

此时你通过代理IP访问网站也应该弹出验证码挑战页面。如果你只是想单纯测试验证码挑战页面能否正常加载,可以使用此命令进行快速测试:

cscli decisions add -i 1.2.3.4 -t captcha

请注意此命令会完全绕过整个检测管线,所以无法用来测试profiles.yaml的逻辑。要真正验证profiles.yaml的配置是否正常,关键在于:必须触发一次真实的“告警(Alert)”

如果要测试完整的逻辑,建议使用往NGINX写假日志的方式。测试完成后别忘了删除决策:

cscli decisions delete -i 1.2.3.4

还记得最开始日志源里面有一个appsec:appsec吗?现在让我们来深入了解这个AppSec是什么。

AppSec全称:CrowdSec WAF - AppSec Component,你可以简单理解为配置好AppSec后,你安装的CrowdSec将变成一个功能齐全的WAF。

这和之前基于日志分析的模式有本质区别,在没有WAF的时候CrowdSec的防御其实叫“事后惩罚”,假设一个攻击者向你的WordPress网站发送一个SQL注入请求:

1.恶意的SQL注入请求直接成功到达了你的WordPress网站并执行了

2.NGINX把这次请求记录到了日志文件access.log

3.后台的CrowdSec进程读取日志,发现日志里包含SQL注入的攻击的请求

4.场景触发(Scenario):“哦!这个IP刚才对我们进行了SQL注入”

5.CrowdSec做出决策:ban掉这个IP

6.等到这个攻击者下次再来请求时,crowdsec-nginx-bouncer才会去封禁它

很明显这种模式防不住“一发入魂”的攻击:如果攻击者通过第一个SQL注入请求就把你的数据库拖走了,或者通过第一个XSS请求就盗取了管理员的Cookie。虽然CrowdSec在1秒后读取日志发现了异样并拉黑了它,但伤害已经造成了。而当你启用WAF后,请求还没到达网页时就被拦截了,直接返回403。

安装AppSec规则集(集合):

cscli collections install \
   crowdsecurity/appsec-virtual-patching \
   crowdsecurity/appsec-generic-rules

创建日志采集配置文件:

nano /etc/crowdsec/acquis.d/appsec.yaml

写入如下内容:

appsec_configs:
  - crowdsecurity/appsec-default
labels:
  type: appsec
listen_addr: 127.0.0.1:7422
source: appsec
name: myAppSecComponent

重启CrowdSec使新的配置生效:

systemctl restart crowdsec

编辑crowdsec-nginx-bouncer的配置文件:

nano /etc/crowdsec/bouncers/crowdsec-nginx-bouncer.conf

将crowdsec-nginx-bouncer接入AppSec:

APPSEC_URL=http://127.0.0.1:7422
APPSEC_FAILURE_ACTION=passthrough
APPSEC_CONNECT_TIMEOUT=
APPSEC_SEND_TIMEOUT=
APPSEC_PROCESS_TIMEOUT=
ALWAYS_SEND_TO_APPSEC=true
APPSEC_DROP_UNREADABLE_BODY=false
SSL_VERIFY=true

1.修改APPSEC_URL指向127.0.0.1:7422

2.启用ALWAYS_SEND_TO_APPSEC始终将所有请求都发送给AppSec检查。

重启NGINX使配置生效:

systemctl restart nginx

现在你已经拥有了基本的防护,为什么说是基本?因为刚才安装的两个集合内的规则仅能防护特定的已知CVE漏洞和一些常见的网络攻击,对于其它未知的攻击和漏洞基本无能为力。官方这么做的目的其实更多的是为了减少误报和方便运维(快速上手)。

如果你需要更高级别的防护,就需要启用CRS规则了。启用CRS规则有利有弊,利就是防护范围更广,全面覆盖各种通用且未知的SQL注入、XSS、RCE远程代码执行、路径穿越等Web攻击,也有一定的0Day防护能力,即便漏洞今天刚曝光,如果这个漏洞涉及SQL注入/XSS,CRS规则依然能直接阻断。但弊也很明显,CRS规则非常严格,正常用户发含特殊字符的文章/代码段时,可能会被误判,需要后期不断调优设置白名单。

CRS在CrowdSec WAF中又分为带外CRS和带内CRS,只有带内CRS才是真正的WAF模式,它的处理动作是:直接阻止请求,如果某个IP地址被阻止3次请求后还会被封禁。而带外CRS是:可疑请求不会立即被阻止,只有屡犯者才会被封禁。我个人是建议直接用带内CRS。

[不推荐]如果你使用带外CRS,则安装:

cscli collections install crowdsecurity/appsec-crs

如果你使用带内CRS,则安装:

cscli collections install crowdsecurity/appsec-crs-inband

然后编辑AppSec日志采集配置文件:

nano /etc/crowdsec/acquis.d/appsec.yaml 

带外CRS配置:

appsec_configs:
  - crowdsecurity/appsec-default
  - crowdsecurity/crs
labels:
  type: appsec
listen_addr: 127.0.0.1:7422
source: appsec
name: myAppSecComponent

带内CRS配置:

appsec_configs:
  - crowdsecurity/appsec-default
  - crowdsecurity/crs-inband
labels:
  type: appsec
listen_addr: 127.0.0.1:7422
source: appsec
name: myAppSecComponent

我使用带内CRS来保护我的WordPress,但遇到了非常多的误报,此时可以安装官方提供的插件,来一定程度缓解:

cscli collections install crowdsecurity/appsec-crs-exclusion-plugin-wordpress

很多时候还是会出现误报甚至把自己给拦在门外,导致无法登录服务器的尴尬场面,为避免这种情况发生,我们可以配置白名单。CrowdSec中有​​三种白名单类型:

  • Parser (at enrich stage)
  • Postoverflow
  • AllowLists

咱这里先介绍一下最简单最暴力的方法:AllowLists,这是一种IP / CIDR级别的白名单。

创建AllowList列表:

cscli allowlists create my_trusted_ip -d "我的代理节点IP"

添加IP或网段:

cscli allowlists add my_trusted_ip 8.9.6.4
cscli allowlists add my_trusted_ip 10.0.0.0/24 -d "内网网段"

查看列表内的IP:

cscli allowlist inspect my_trusted_ip

由于文章篇幅过长,还有很多内容没写,这篇文章先暂时写到这里,未完待续。

赞(0)
未经允许不得转载:荒岛 » Debian部署CrowdSec保护SSH和Web服务
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

分享创造快乐

广告合作资源投稿