CrowdSec を使って Debian サーバーで悪意ある IP を自動ブロックする

公開サーバーは毎日、さまざまな自動スキャンに遭遇します。

サービスを意図的に公開していなくても、SSH ポートには root、admin、test などのユーザー名に対するログイン試行が日常的に届きます。Web サービスでは .env、wp-admin、phpinfo.php などのパスに対する探索が行われます。

これらのリクエストの多くは自動化されたスキャナによるものです。実際の侵入に至るとは限りませんが、意味のないログを大量に発生させ、サービスの露出面(attack surface)を拡大させます。

静的なファイアウォールルールだけでは、攻撃の挙動に応じてブロック対象リストを動的に調整することが困難です。

そこで私は CrowdSec を試してみることにしました。サーバーログから悪意ある挙動を識別し、nftables と連携して攻撃元の IP を自動的にブロックします。

本稿では、Debian 12 への CrowdSec の導入手順と、それが「ある IP をブロックすべきかどうか」をどのように判断するかを記録します。

1. CrowdSec とは?

CrowdSec はオープンソースの侵入検知・セキュリティレスポンスシステムです。

SSH や Web サーバーなどが生成するログを分析し、総当たり攻撃(ブルートフォース)、脆弱性スキャン、悪意あるクローラーなどの挙動を検知します。

従来の静的な IP ブラックリストとは異なり、CrowdSec は主に挙動の特徴に基づいてブロックすべきかどうかを判断し、コミュニティの脅威インテリジェンスも利用できます。

CrowdSec の処理は 2 つに分かれます。

  • Security Engine:ログを読み込み、攻撃を識別し、ブロック判定を生成します。
  • Remediation Component(Bouncer):ブロック判定を実行します。たとえばファイアウォールルールを変更します。

この 2 つは互いに独立しています。

つまり、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

ログ分析とファイアウォールの実行を切り離しているため、単一サーバーでも、中央 API を通じて複数サーバーにブロック判定を配信する場合でも同じように機能します。

1.2 CrowdSec と Fail2ban の比較

Fail2ban は Linux 上でよく使われる自動ブロックツールです。

どちらもログから SSH の総当たり攻撃を検知できますが、設計思想には違いがあります。

特徴CrowdSecFail2ban
SSH 総当たり攻撃の検知対応対応
Web 攻撃の検知対応対応(通常は対応する filter が必要)
コミュニティの脅威インテリジェンス対応通常は追加の連携が必要
分散構成ネイティブ対応通常は自前で連携が必要
ブロック方式BouncerAction
検知ルールParser + ScenarioFilter + Jail
設定の複雑さ比較的高い比較的低い

SSH のログイン失敗だけであれば IP をブロックするだけでいいなら、Fail2ban でも十分です。

複数の Web サービス、SSH および他のネットワークサービスをまとめて保護したい場合、CrowdSec のモジュール化されたアーキテクチャのほうが拡張の余地があります。

2. 導入環境

本稿では次の環境を使用します。

項目設定
OSDebian 12 Bookworm
CrowdSec1.8.x
ファイアウォールnftables
インストール方法APT
検知対象SSH、Caddy、Nginx
ブロック方式Firewall Bouncer

Docker ではなくネイティブインストールを採用します。

主な理由は、CrowdSec がホストの systemd ジャーナルを読み取り、Bouncer を通じて OS のファイアウォールを操作する必要があるためです。ネイティブインストールなら、余分な権限、ネットワーク、マウントの設定を省けます。

3. 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 登録が自動的に完了するため、設定ファイル全体を直接上書きすることは推奨しません。

4. 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 を使うため、SSH ログは journald から直接取得できます。

まず確認します。

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

ここでは 2 点注意が必要です。

第一に、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 はログ解析の診断ツールです。このテストレコード 1 件のために IP がブロックされることはありません。

5. 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

ログが読み取られ解析されている表示があれば、acquisitor と parser は正常に動作しています。

ただし、これでは悪意あるリクエストが実際に検知されたことを意味しません。

5.3 CrowdSec は HTTP 攻撃をどう判断する?

CrowdSec は異なる Scenario に基づいて、次のような点を判断します。

  • 機密ファイルへ連続してアクセスしているか。
  • 存在しないパスを大量に探索しているか。
  • 既知の脆弱性を突く挙動があるか。
  • 異常な HTTP リクエスト頻度があるか。
  • 悪意ある HTTP クライアントの挙動特徴に一致しているか。

たとえば次のようなパスへのアクセスです。

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

これらは自動スキャンでよく現れるパスです。

ただし、1 回アクセスしただけでは必ずブロックされるわけではありません。

CrowdSec は該当する Scenario のルールを組み合わせて判断し、通常のアクセスを攻撃と誤認しないようにしています。

6. CrowdSec はいつ IP をブロックするのか

これは CrowdSec を使う上で最も理解しておくべきポイントです。

CrowdSec は SSH のログイン失敗を 1 回検出しただけで即座にブロックするのではなく、挙動が Scenario の検知条件を満たしたときにアラートとブロック判定を生成します。

6.1 Leaky Bucket アルゴリズム

CrowdSec の多くの Scenario は Leaky Bucket(漏斗)アルゴリズムを使います。

各 IP を個別のバケットに例えることができます。

  • 条件に合致する攻撃イベントごとに、バケットへ 1 単位が加わります。
  • バケットは設定された速度で継続的に漏れます。
  • イベントの蓄積が漏れる速度を上回りバケットが溢れたとき、攻撃アラートが発火します。

たとえば、仮のルールは次のようになります。

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

これは次を意味します。

  • バケットの容量は 5 です。
  • 10 秒ごとに 1 件のイベントが漏れます。
  • 蓄積イベントが容量を超えたときに溢れが発火します。

攻撃者が短時間に続けてログインを試みれば、ブロックが発火する可能性があります。

数分おきにしか試行しない場合、イベントはしきい値に到達する前に漏(leak)してしまう可能性があります。

これが、ある 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. 自動ブロックが機能しているかの検証

インストールが成功しただけでは、ブロックの連鎖全体が検証できたわけではありません。

2 つに分けるのがよいです。まずファイアウォールの実行を検証し、次に攻撃の検知を検証します。

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

Decision を確認します。

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 のルールの優先順位を確認する。
  • 実際の接続テストには別の管理下にあるデバイスを使う。

8. CrowdSec は古いログを見るのか?

既定では、CrowdSec はリアルタイムのログ解析を中心とするもので、インストール前のすべての履歴ログを起動時に自動的に走査することを保証していません。

たとえば、CrowdSec を導入する前就已经、SSH ログには数百回のログイン失敗が記録されていることがあります。

そのイベントは、CrowdSec をインストールしたからといってすべて現在のブロック Decision に変わるわけではありません。

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/

9. 日々の運用管理

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 で管理します。

10. よくある質問

CrowdSec は動いているのに、何もブロックされない

まずログ取得を確認します。

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 が何度もログインに失敗していても、2 つのイベントの間隔が十分に長ければ、Leaky Bucket は溢れないことがあります。

ブロック済みと表示されるのに、まだアクセスできてしまうのはなぜ?

考えられる原因は次のとおりです。

  • Bouncer がまだ Decision を同期していない。
  • nftables のルールが、トラフィックが実際に通過する Hook に正しく接続されていない。
  • ファイアウォールルールの順序が正しくない。
  • IPv4 と IPv6 の両方をカバーできていない。
  • 実際のトラフィックがリバースプロキシを経由しており、ログの IP が実際のクライアントアドレスではない。
  • 遮断された接続と、いま試している接続の送信元が一致していない。

したがって cscli decisions list は CrowdSec が Decision を生成したことを示すにすぎず、それだけではネットワーク層での遮断成功を証明できません。

CrowdSec は自動的にブロックを解除するのか?

はい。

ブロック Decision の期限が来ると、Bouncer はファイアウォールのセットを更新し、該当 IP を削除するはずです。

ただし、同じ IP が他のファイアウォールルールでもブロックされている場合、CrowdSec による解除では、それらの独立したルールまで自動的に取り除かれるわけではありません。

11. まとめ

CrowdSec の本質的な価値は、IP の自動ブロックそのものだけでなく、ログ解析・挙動検知・ブロック実行を独立したコンポーネントに分割できることにあります。

これにより、SSH の総当たり攻撃対策という最も基本的な用途から、Web サービス、リバースプロキシ、複数サーバーの集中防御へと段階的に拡張できます。

実際の導入では、3 点の理解が重要です。

第一に、CrowdSec は固定の失敗回数で直接ブロックするわけではありません。 Scenario で挙動が異常かどうかを判定し、多くのルールは Leaky Bucket アルゴリズムを使うため、攻撃の頻度と持続時間の両方が検知結果に影響します。

第二に、検知の成功はブロックの成功を意味しません。 Alert、Decision、Bouncer、ファイアウォールルールはそれぞれ別の段階です。問題を切り分けるときは、段階を一つずつ確認してください。

第三に、CrowdSec は継続的に発生する新たなセキュリティイベントを監視するのに向いています。 履歴の攻撃を分析したい場合は Replay Mode が使えますが、履歴のアラートが意図せず本番のブロックに影響しないよう注意が必要です。

すでに Debian、systemd、Caddy、Nginx を使っているサーバーであれば、CrowdSec と nftables の組み合わせは検討に値する自動化されたセキュリティ対策です。

ソフトウェアの定期的な更新、危険な設定の排除、SSH の公開鍵認証、最小権限の原則を置き換えるものではありませんが、繰り返し発生する大量の自動スキャンと総当たり攻撃リクエストを削減できます。

参考資料