こちらのipsetによる防御。これを運用して分かったのが
- /16や/8で切る狂気
- 自分自身がロックアウトされるという恐怖
の2点。そこで、これを回避するためのスクリプトを改良しました。
#!/bin/bash
# セット名の定義
SET_NAME="ufw-blocklist"
SAVE_FILE="/etc/ufw/ipsets.save"
# 1. root権限チェック
if [[ $EUID -ne 0 ]]; then
echo "エラー: このスクリプトは sudo または root 権限で実行してください。"
exit 1
fi
# 保存用関数
save_ipset() {
read -p "設定をファイルに保存しますか? (y/n): " confirm
if [[ "$confirm" =~ ^[Yy]$ ]]; then
ipset save "$SET_NAME" -f "$SAVE_FILE"
echo "保存完了: $SAVE_FILE"
else
echo "保存をスキップしました(メモリ上の設定のみ更新)。"
fi
}
# ipsetが存在しない場合に作成
if ! ipset list "$SET_NAME" > /dev/null 2>&1; then
echo "情報: $SET_NAME が見つからないため新規作成します。"
if ipset create "$SET_NAME" hash:net; then
echo "作成成功"
else
echo "エラー: ipsetの作成に失敗しました。"
exit 1
fi
fi
# フェイルセーフ関数:接続中の自分のIP(SSHポータ22)を遮断しようとしていないかチェック
is_ssh_connected_ip() {
local target_ip=$1
# 現在22番ポートでサスペンド/エスタブリッシュしている接続元のIPをすべて取得
# (awkで不要なヘッダーやコロン以降のポート番号を削る)
local my_ssh_ips=$(ss -nt state established sport = :22 | awk 'NR>1 {print $5}' | sed 's/:[^:]*$//' | sort -u)
# テスト対象のターゲットからサブネット部分(/24など)を除外してIPのみにする
local clean_target=$(echo "$target_ip" | cut -d'/' -f1)
for ssh_ip in $my_ssh_ips; do
# 1. 完全一致チェック
if [[ "$ssh_ip" == "$clean_target" ]]; then
return 0 # ロックアウトの危険あり
fi
# 2. サブネットマスクごとの巻き込みチェック
if [[ "$target_ip" == */24 ]]; then
local target_net=$(echo "$clean_target" | cut -d'.' -f1-3)
local ssh_net=$(echo "$ssh_ip" | cut -d'.' -f1-3)
if [[ "$target_net" == "$ssh_net" ]]; then
return 0
fi
elif [[ "$target_ip" == */16 ]]; then
local target_net=$(echo "$clean_target" | cut -d'.' -f1-2)
local ssh_net=$(echo "$ssh_ip" | cut -d'.' -f1-2)
if [[ "$target_net" == "$ssh_net" ]]; then
return 0
fi
fi
done
return 1 # 安全
}
# IP/NWの形式を整形する関数
format_target() {
local ip=$1
echo "=== サブネット選択 ==="
echo "1) そのまま登録 (${ip})"
echo "2) /24 で登録 (${ip%.*}.0/24)"
echo "3) /16 で登録 (${ip%.*.*}.0.0/16)"
read -p "選択肢を選んでください [1-3]: " subnet_choice
case $subnet_choice in
2) echo "${ip%.*}.0/24" ;;
3) echo "${ip%.*.*}.0.0/16" ;;
*) echo "$ip" ;;
esac
}
# メインループ
while true; do
echo "------------------------------------------"
echo " ipset 管理メニュー ($SET_NAME)"
echo "------------------------------------------"
echo "1) IP/ネットワークを追加"
echo "2) IP/ネットワークを削除"
echo "3) ステータス表示 (list)"
echo "q) 終了 (Exit)"
echo "------------------------------------------"
read -p "番号を選択してください: " choice
case $choice in
1)
echo "--- 追加モード ---"
local added_any=false
while true; do
read -p "追加する IPアドレス: " raw_ip
if [ -n "$raw_ip" ]; then
# サブネットの選択
target=$(format_target "$raw_ip")
# フェイルセーフ発動チェック
if is_ssh_connected_ip "$target"; then
echo "【警告】現在あなたが接続しているSSH元IPが含まれているため、登録をブロックしました! ($target)"
else
if ipset add "$SET_NAME" "$target" 2>/dev/null; then
echo "追加成功: $target"
added_any=true
else
echo "エラー: 追加に失敗したか、既に登録されています。 ($target)"
fi
fi
fi
# 続けて入力するか確認
read -p "続けて他のIPを入力しますか? (y/n): " continue_add
if [[ ! "$continue_add" =~ ^[Yy]$ ]]; then
break
fi
echo "------------------------------------------"
done
# 追加が1件でもあれば一括で保存確認
if [ "$added_any" = true ]; then
save_ipset
fi
;;
2)
echo "--- 削除(解除)モード ---"
read -p "削除する IPアドレス: " raw_ip
if [ -n "$raw_ip" ]; then
target=$(format_target "$raw_ip")
# 削除時も念のためセーフティ(既存の接続を誤って切らないようにするため)
if is_ssh_connected_ip "$target"; then
echo "【警告】現在接続中のSSHセッションが含まれています。削除を中止します。 ($target)"
else
if ipset del "$SET_NAME" "$target" 2>/dev/null; then
echo "削除成功: $target"
save_ipset
else
echo "エラー: 削除に失敗したか、リストに存在しません。 ($target)"
fi
fi
fi
;;
3)
echo "--- 現在のリスト ---"
ipset list "$SET_NAME"
;;
q)
echo "終了します。"
exit 0
;;
*)
echo "無効な選択です。"
;;
esac
echo ""
done
ステップ 1:スクリプトの配置と実行権限の付与
まずはスクリプトをサーバーに保存し、実行できるように権限を付与します。
- スクリプトファイルの作成: 任意のディレクトリ
- 任意の場所にファイルを作成します(例:
blocklist-mgr.sh)。 - 実行権限の付与: chmod コマンド
sudo chmod +x blocklist-mgr.shを実行して、スクリプトに実行権限を与えます。- root権限で起動: sudo 必須
sudo ./blocklist-mgr.shで起動します。
ステップ 2:メニュー画面の操作
起動すると、以下のような対話型メニューが表示されます。
```text
ipset 管理メニュー (ufw-blocklist)
1) IP/ネットワークを追加
2) IP/ネットワークを削除
3) ステータス表示 (list)
q) 終了 (Exit)
番号を選択してください:
1 IP/ネットワークを追加する場合(メニューで「1」を選択)
悪意のあるアクセス元IPを見つけたら、「1」を入力して追加モードに入ります。
- IPアドレスの入力: ブロックしたいIP(例:
192.168.1.50)を入力します。 - サブネット(範囲)の選択:以下のように範囲を聞かれるので、攻撃の規模に合わせて選択します。
- 1(単一IPのみ)
- 2(
/24:周辺のIP 256台をまとめてブロック) - 3(
/16:大規模なネットワークごとブロック)
- 連続入力: 「続けて他のIPを入力しますか? (y/n)」と聞かれるので、複数ある場合は
yで続けて入力できます。 - ファイルへの永続化:
追加が終わると「設定をファイルに保存しますか? (y/n)」と聞かれます。ここで y を選ぶと、OS再起動後もブロックが有効になります。
もし間違えて「自分が今SSH接続しているIP(またはその所属サブネット)」を入力してしまった場合、スクリプトが自動でそれを検知し、
【警告】登録をブロックしました! と表示して処理を中断します。これにより、リモートワーク中にサーバーから締め出される悲劇を防ぎます。
2 ブロックを解除(削除)する場合(メニューで「2」を選択)
誤ってブロックしてしまったIPや、制限を解除したいIPがある場合は「2」を選択します。
追加時と同様にIPを入力し、サブネット範囲を選択すれば、リストから安全に削除されます。ここでも自分が接続中のIPを誤って削除(変更)しないようセーフティが働きます。
3 現在のブロックリストを確認する場合(メニューで「3」を選択)
「3」を選択すると、現在 ipset に登録されてリアルタイムでブロックされているIPの一覧がズラリと表示されます。正しく反映されているか確認したいときに便利です。
4 終了する場合(メニューで「q」を選択)
「q」を入力すると、安全にメニューを閉じます。