年末年始の不正アクセスを防ぐSSHログイン監視&不審IP自動ブロック

実務レシピ

「手作業は負け」「スクリプトはシンプルに」がモットーのBash玄(ばっしゅげん)です。

年末年始や大型連休など、インフラ担当者が長期間オフィスを離れる時期は、botや攻撃者によるSSH総当たり攻撃(ブルートフォース攻撃)のリスクが一段と高まります。「休暇中にサーバーが乗っ取られたらどうしよう」「休み明けにログを見たら大量の不正侵入試行で溢れていた」といった不安を抱えてはいませんか。

本記事では、休暇中も心置きなく休めるよう、Fail2banを用いた不審IPの自動遮断、journalctlを活用した不正ログイン試行の即時集計ワンライナー、そしてアクセス急増時にSlackへ自動アラートを飛ばすBashスクリプトの構築手順を徹底解説します。手作業による監視をゼロにし、安全な無人運用体制を今すぐコピペで整えましょう。


年末年始の無人運用で急増するSSHブルートフォース攻撃の実態

管理者が不在となる長期休暇期間中は、システム監視の手が緩むタイミングを狙ったbotや海外クローラーによる自動攻撃が活発化します。インターネット上に公開されているLinuxサーバーやクラウドVPSは、特別な広報を行っていなくても、IPレンジへの網羅的なポートスキャンによって数時間のうちに標的として捕捉されます。

特に危険なのが、デフォルトのポート22を開放し、パスワード認証を許可したまま運用している環境です。攻撃スクリプトは root、admin、test、ubuntu といった典型的なユーザー名と辞書攻撃データを組み合わせ、数分間で数千回から数万回におよぶ接続試行を機械的に送り込んできます。万が一脆弱なパスワードを設定しているアカウントが存在すれば、あっけなく侵入を許し、バックドアの設置や暗号資産マイニング、別サーバーへの踏み台攻撃などに悪用されてしまいます。

こうした脅威から休暇中のサーバーを確実に保護するには、単一の対策に依存しない多層防御の設計が不可欠です。

  • 侵入経路の遮断: デフォルトポートの変更および公開鍵認証(ed25519推奨)への限定
  • 攻撃行動の自動抑止: Fail2banによるしきい値超過IPのパケットフィルタ遮断
  • 異常の早期検知: ログ監視スクリプトによる急激なアタック増加のSlackリアルタイム通知

本記事では、この多層防御の中核を担う「Fail2banによる自動ブロック」と「Bashスクリプトによる異常検知・Slack通知」を実践していきます。


最短10分!Fail2banの導入とSSH自動ブロック設定

Fail2banは、認証ログを継続的に監視し、一定時間内に指定回数の認証失敗を繰り返した不審なIPアドレスを、iptablesやnftables、firewalldと連携して自動的に遮断(BAN)してくれる軽量なデーモンです。

1. Fail2banのインストール(Ubuntu/Debian・RHEL系)

主要なLinuxディストリビューションごとのインストール手順です。UbuntuやDebian、AlmaLinuxなどの最新環境では、従来のrsyslogではなくsystemdジャーナルが標準となっている場合が多いため、必要な連携パッケージも併せて導入します。

Ubuntu / Debian 環境

UbuntuやDebianでは、公式リポジトリから fail2ban をインストールします。

# パッケージ一覧の更新とFail2banのインストール
sudo apt update
sudo apt install -y fail2ban python3-systemd

RHEL系 / AlmaLinux / Rocky Linux 環境

RHEL系のディストリビューションでは、EPEL(Extra Packages for Enterprise Linux)リポジトリを有効化してからインストールします。

# EPELリポジトリの有効化とFail2banのインストール
sudo dnf install -y epel-release
sudo dnf install -y fail2ban fail2ban-systemd

2. jail.local の作成とSSH保護パラメータ設定

Fail2banの設定ファイルは /etc/fail2ban/ 配下に格納されています。デフォルトの設定ファイル /etc/fail2ban/jail.conf を直接編集してしまうと、パッケージのアップデート時に変更内容が上書きされて消滅する危険があります。

実務では、設定の上書き用ファイルとして /etc/fail2ban/jail.local を新規作成するのが鉄則です。

# 既存設定を汚さないよう、必要な項目だけを jail.local に記述
sudo tee /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
# 攻撃検知時のBAN時間(秒単位、または 1h, 1d 等の接尾辞): 1日遮断
bantime = 1d

# 試行回数をカウントする時間枠: 10分間
findtime = 10m

# 遮断までの許容失敗回数: 5回
maxretry = 5

# systemd環境に最適なログ監視バックエンド
backend = systemd

[sshd]
# SSH監視Jailを有効化
enabled = true
port = ssh
filter = sshd
EOF

設定パラメータの役割は以下のとおりです:

  • bantime: 遮断する期間。長期休暇中は再攻撃を防ぐため、長めの 1d(1日)や 1w(1週間)を推奨します。
  • findtime: 失敗回数を積算する時間枠。10m(10分間)を指定します。
  • maxretry: findtime 内にこの回数だけ認証失敗するとBANされます。一般的な実務では 3 〜 5 程度が安全です。
  • backend = systemd: 近年のLinux環境でrsyslogを停止している場合でも、systemdジャーナルから直接ログを高速に読み取れます。

3. 【最重要】自爆防止!管理者IPを保護する ignoreip のホワイトリスト指定

Fail2ban導入で最も恐ろしい事故は、管理者自身がSSHクライアントの設定不備やパスワード入力ミスによりサーバーから締め出される「自爆事故」です。特に休暇中に自宅や出先からアクセスできなくなると、現地対応やクラウド管理コンソールからの復旧作業を強いられます。

これを防ぐため、自社オフィスの固定IPや自宅の固定グローバルIP、およびローカルループバックアドレスを必ず ignoreip に指定してホワイトリスト登録します。

/etc/fail2ban/jail.local の [DEFAULT] セクションに ignoreip 行を追記または反映します。

# 管理者IPを含めた jail.local の設定例
sudo tee /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
# ループバックアドレスと管理者の接続元IPを空白区切りで指定(CIDR表記も可能)
ignoreip = 127.0.0.1/8 ::1 192.0.2.100 203.0.113.0/24

bantime = 1d
findtime = 10m
maxretry = 5
backend = systemd

[sshd]
enabled = true
port = ssh
filter = sshd
EOF

※ 192.0.2.100 や 203.0.113.0/24 の部分は、ご自身の固定グローバルIPや信頼できる踏み台サーバーのCIDRレンジに置き換えてください。

設定ファイルを配置したら、Fail2banサービスを起動・自動起動設定します。

# サービスの有効化と起動
sudo systemctl enable --now fail2ban

# 稼働ステータスの確認
sudo systemctl status fail2ban --no-pager

運用の要!Fail2banの稼働確認と誤BAN解除手順

Fail2banが正常に動いているかを確認し、万が一正当なアクセスが遮断された場合に即座に救出できるコマンドを把握しておきましょう。

1. 現在のブロック状態を確認する(fail2ban-client status)

Fail2banの稼働状況や現在遮断されているIPアドレスの一覧は、fail2ban-client コマンドで確認します。

# 全体ステータスと有効化されているJail一覧を表示
sudo fail2ban-client status

実行結果の例:

Status
|- Number of jail:      1
`- Jail list:   sshd

SSH監視Jail(sshd)の詳細なステータスを確認するには、引数に sshd を渡します。

# SSH監視Jailの詳細(ブロック件数・現在BAN中のIPリスト)を表示
sudo fail2ban-client status sshd

実行結果の例:

Status for the jail: sshd
|- Filter
|  |- Currently failed: 2
|  |- Total failed:     48
|  `- File list:        systemd
`- Actions
   |- Currently banned: 3
   |- Total banned:     12
   `- Banned IP list:   198.51.100.23 203.0.113.45 192.0.2.88

Currently banned で現在遮断中のIP総数、Banned IP list でブロック中のIPアドレス一覧が一目で把握できます。

また、詳細な挙動やエラーの有無は Fail2ban 本体のログファイルで確認可能です。

# Fail2banの動作ログを直近30行表示
sudo tail -n 30 /var/log/fail2ban.log

2. 誤って遮断されたIPを手動で即時解除する(unbanip)

同僚エンジニアや連携サービス、あるいは自分自身が意図せずBANされてしまった場合は、unbanip サブコマンドを実行して即座に遮断を解除します。

# 指定したIPアドレスのBANを解除するコマンド
sudo fail2ban-client set sshd unbanip 198.51.100.23

実行結果として 1(解除成功)が返ってくれば、対象IPからのパケット遮断ルールが iptables / nftables から即時削除され、再びSSH接続が可能になります。

複数のIPを誤って遮断してしまい、全件を一括解除したい場合は以下のコマンドが便利です。

# sshd JailでBANされているすべてのIPを一括解除
sudo fail2ban-client unban --all

【自作ワンライナー】不正ログイン試行(Failed password)を集計・可視化する

Fail2banを導入した後は、「実際にどのようなIPから何回攻撃が来ているのか」を定期的に把握したくなります。手作業でログを目視確認するのは非効率の極みです。Bashパイプラインを駆使した集計ワンライナーで、一瞬にして頻出攻撃元IPトップ10を可視化しましょう。

1. journalctl から直近の不正試行IPトップ10を集計するワンライナー

最新のUbuntuやDebian、AlmaLinuxでは、SSHのログはsystemdジャーナル(journalctl)で一元管理されています。rsyslogが動いていないサーバーでも即座に実行できるワンライナーです。

Ubuntu / Debian 環境(サービス名が ssh の場合):

# journalctlから認証失敗(Failed password)を抽出し、IPアドレスごとの出現回数トップ10を表示
sudo journalctl -u ssh --no-pager | grep "Failed password" | awk '{for(i=1;i<=NF;i++) if($i=="from") print $(i+1)}' | sort | uniq -c | sort -nr | head -n 10

RHEL系 / AlmaLinux / CentOS 環境(サービス名が sshd の場合):

# RHEL系向け:journalctlから認証失敗IPトップ10を集計
sudo journalctl -u sshd --no-pager | grep "Failed password" | awk '{for(i=1;i<=NF;i++) if($i=="from") print $(i+1)}' | sort | uniq -c | sort -nr | head -n 10

パイプラインの処理解説

  1. journalctl -u ssh --no-pager: SSHサービスのジャーナルログを全件パイプへ流し込みます。
  2. grep "Failed password": パスワード認証失敗、または存在しない無効ユーザーによる試行ログを抽出します。
  3. awk '{for(i=1;i<=NF;i++) if($i=="from") print $(i+1)}': ログ行内にある from という単語を探し、その直後にあるIPアドレス($(i+1))だけを正確に切り出します。ログのフォーマット差分に左右されない堅牢な記述です。
  4. sort | uniq -c: IPアドレスごとにソートした上で重複をまとめ、出現回数をカウントします。
  5. sort -nr: カウント数の多い順(数値降順)にソートします。
  6. head -n 10: 最も攻撃試行回数が多かった上位10件を表示します。

実行結果の例:

   1420 198.51.100.12
    854 203.0.113.88
    412 192.0.2.45
    189 198.51.100.99
     76 203.0.113.201

このように、わずか数秒で集計結果が表示されます。

2. ログファイル(auth.log / secure)からの集計ワンライナー

rsyslogが稼働しているレガシー環境やログローテーションされた過去のテキストファイルを直接調査したい場合は、/var/log/auth.log(Debian/Ubuntu)または /var/log/secure(RHEL系)を直接集計します。無駄な cat を排除したスマートなワンライナーです。

Ubuntu / Debian 系(auth.log)

# auth.logから認証失敗IPトップ10を集計
sudo awk '/Failed password/ {for(i=1;i<=NF;i++) if($i=="from") print $(i+1)}' /var/log/auth.log | sort | uniq -c | sort -nr | head -n 10

RHEL系 / AlmaLinux 系(secure)

# secureログから認証失敗IPトップ10を集計
sudo awk '/Failed password/ {for(i=1;i<=NF;i++) if($i=="from") print $(i+1)}' /var/log/secure | sort | uniq -c | sort -nr | head -n 10

awk 自身にパターンマッチング(/Failed password/)とファイル直接読み込みを行わせることで、cat や grep の無駄なプロセス生成(UUOC: Useless Use of Cat)を防止し、巨大なログファイルでも高速に処理できます。


【完成形スクリプト】不審なアクセス急増を検知してSlackへ即時通知するBashスクリプト

Fail2banによる個別遮断に加え、短時間の間に集中的なブルートフォース攻撃を受けている場合は、異常事態として管理者のSlackへ通知が飛ぶようにしておくと安心です。

ここでは、直近1時間の不正アクセス試行回数を集計し、あらかじめ設定したしきい値(例: 20回)を超えた場合にSlackへアラートメッセージと攻撃元IPトップ5を投稿する完成形スクリプトを作成します。

1. スクリプト全文(set -euo pipefail・しきい値判定・Slack Webhook送信)

サーバー上の /usr/local/bin/ssh-attack-notifier.sh として以下のスクリプトを配置します。

#!/usr/bin/env bash
# ==============================================================================
# SSH不正アクセス急増検知&Slack通知スクリプト
# ==============================================================================
set -euo pipefail

# ------------------------------------------------------------------------------
# 設定項目
# ------------------------------------------------------------------------------
# Slack Incoming Webhook URL(ご自身のURLに差し替えてください)
readonly WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"

# 検知しきい値(直近1時間あたりの失敗回数がこれ以上ならアラート送信)
readonly THRESHOLD=20

# サーバー識別名(ホスト名)
readonly HOSTNAME="$(hostname -f 2>/dev/null || hostname)"

# 一時集計用ログファイル
readonly TMP_DIR="/tmp/ssh_alert_$$"
mkdir -p "$TMP_DIR"
trap 'rm -rf "$TMP_DIR"' EXIT

readonly IP_SUMMARY_FILE="$TMP_DIR/ip_summary.txt"

# ------------------------------------------------------------------------------
# ログ集計処理
# ------------------------------------------------------------------------------
# ディストリビューションに応じたSSHサービス名の判定
SSH_SERVICE="ssh"
if ! systemctl list-units --full --all | grep -Fq "ssh.service"; then
    SSH_SERVICE="sshd"
fi

# 直近1時間(1 hour ago)の認証失敗ログからIPを抽出して集計
journalctl -u "$SSH_SERVICE" --since "1 hour ago" --no-pager 2>/dev/null \
    | awk '/Failed password/ {for(i=1;i<=NF;i++) if($i=="from") print $(i+1)}' \
    | sort \
    | uniq -c \
    | sort -nr > "$IP_SUMMARY_FILE"

# 総失敗回数を算出
TOTAL_ATTACKS=0
if [[ -s "$IP_SUMMARY_FILE" ]]; then
    TOTAL_ATTACKS=$(awk '{sum += $1} END {print sum}' "$IP_SUMMARY_FILE")
fi

# ------------------------------------------------------------------------------
# しきい値判定とSlack通知
# ------------------------------------------------------------------------------
if (( TOTAL_ATTACKS >= THRESHOLD )); then
    # 攻撃元IP上位5件のサマリーテキストを生成
    TOP_IPS=$(head -n 5 "$IP_SUMMARY_FILE" | awk '{printf "%s 回: %s\\n", $1, $2}')

    # 現在日時
    NOW=$(date "+%Y-%m-%d %H:%M:%S")

    # Slackへ送るメッセージ本文の組み立て
    MESSAGE="⚠️ *[SSH警告] 不審なログイン試行が急増しています*\\n\\n"
    MESSAGE+="*対象サーバー*: \`${HOSTNAME}\`\\n"
    MESSAGE+="*検知日時*: ${NOW}\\n"
    MESSAGE+="*直近1時間の総攻撃試行回数*: *${TOTAL_ATTACKS}* 回(しきい値: ${THRESHOLD} 回)\\n\\n"
    MESSAGE+="*頻出攻撃元IPトップ5*:\\n\`\`\`\\n${TOP_IPS}\`\`\`\\n"
    MESSAGE+="※Fail2banによる自動遮断状況を \`sudo fail2ban-client status sshd\` で確認してください。"

    # JSONペイロードの作成
    PAYLOAD=$(printf '{"text": "%s"}' "$MESSAGE")

    # curlコマンドでSlack Incoming WebhookへPOST送信
    curl -sS -X POST \
        -H "Content-Type: application/json" \
        -d "$PAYLOAD" \
        "$WEBHOOK_URL" > /dev/null
fi

exit 0

スクリプトの安全・高品質ポイント

  1. set -euo pipefail の明記: 未定義変数の参照やコマンド失敗、パイプ途中でのエラー発生時にスクリプトを直ちに安全停止させます。
  2. 厳格な変数クォーティング: パスや変数展開をすべて "$var" のようにダブルクォートで保護し、意図せぬ単語分割やglob展開を防いでいます。
  3. trap による一時リソース自動削除: スクリプト終了時(正常・異常問わず)にテンポラリディレクトリを確実に消去します。
  4. サービス名の自動判別: Debian/Ubuntu系の ssh と RHEL系の sshd の差異を自動吸収して動作します。

スクリプトを作成したら、実行権限を付与し、rootユーザーのみが読み書きできるようにパーミッションを設定します。

sudo chmod 700 /usr/local/bin/ssh-attack-notifier.sh

2. cronによる定期自動実行設定とflockによる多重起動防止

このスクリプトを定期的に実行するため、crontab に登録します。

ログファイルが肥大化していたりネットワークの遅延が発生したりした場合に、前回のスクリプトが終了する前に次のcronが重複起動すると、サーバーの負荷増や重複通知の原因になります。

これを防止するため、flock コマンドを用いたファイルロックによる多重起動防止を組み込むのが実務の鉄則です。

# rootユーザーのcrontab編集画面を開く
sudo crontab -e

以下の行を追記します。毎時0分にスクリプトを定期実行する設定例です。

# 毎時0分にSSH不正ログイン試行を監視(flockで多重起動防止)
0 * * * * /usr/bin/flock -n /var/run/ssh-attack-notifier.lock /usr/local/bin/ssh-attack-notifier.sh > /dev/null 2>&1
  • /usr/bin/flock -n /var/run/ssh-attack-notifier.lock: ロックファイルを取得します。もし前回の処理がまだ実行中だった場合、-n(ノンブロッキング)オプションにより待機せず即座に終了(スキップ)するため、プロセスが滞留しません。

これで、完全無人で動作する監視・通知パイプラインが完成しました。


まとめ & 関連記事

年末年始や大型連休を安心して過ごすためのSSH監視&自動防御体制の要点を振り返ります。

  • Fail2banによる自動防御: jail.local を作成し、ブルートフォース攻撃元を即座にブロック。ignoreip で管理者の締め出し(自爆)を未然に防止。
  • journalctl による現状把握: パイプラインを駆使したワンライナーで、いつでも頻出攻撃元IPトップ10を即時可視化。
  • Bashスクリプト&cronによる異常検知: 直近1時間の急増を検知し、flock で二重起動を防ぎながらSlackへ自動アラート。

休暇突入前にチェック!SSH多層防御チェックリスト

休暇に入る前に、以下の項目が完了しているかサーバーのコンソールで指差し確認を行いましょう。

  1. [ ] パスワード認証を禁止し、公開鍵認証のみに制限しているか
  2. [ ] SSHポート番号をデフォルトの22番から変更しているか
  3. [ ] fail2ban-client status sshd でFail2banが元気に稼働しているか
  4. [ ] ignoreip に自身の固定IPがホワイトリスト登録されているか
  5. [ ] Slack通知スクリプトのテスト実行で通知が正しく届くか

関連記事

サーバーの堅牢化と自動化をさらに推し進めたい方は、ぜひ以下の記事も併せてご参照ください。

Bash玄

はじめまして!Bash玄です。

エンジニアとしてシステム運用に携わる中で、手作業の多さに限界を感じ、Bashスクリプトを活用して業務を効率化したのがきっかけで、この道に入りました。「手作業は負け」「スクリプトはシンプルに」をモットーに、誰でも実践できるBashスクリプトの書き方を発信しています。

このサイトでは、Bashの基礎から実践的なスクリプト作成まで、初心者でもわかりやすく解説しています。少しでも「Bashって便利だな」と思ってもらえたら嬉しいです!

# 好きなこと
- シンプルなコードを書くこと
- コマンドラインを快適にカスタマイズすること
- 自動化で時間を生み出すこと

# このサイトを読んでほしい人
- Bashに興味があるけど、何から始めればいいかわからない人
- 定型業務を自動化したい人
- 効率よくターミナルを使いこなしたい人

Bashの世界に一歩踏み出して、一緒に「Bash道」を極めていきましょう!

Bash玄をフォローする

コメント