swatchdog のインストール
Ubuntu で Swatch IDS を構築し、不正アクセスを防止する。
必要なパッケージをインストール。
apt install swatch sleepenh perl
おそらく Perl の file-tail もこれで一緒にインストールされるはず。
起動スクリプトを作成する
Swatch はただのコマンドなので、設定ファイルをもとに一括で起動するスクリプトを作成し、systemd へ登録する。使われてないが一応ロックファイルも作成する仕様にしている。
vi /usr/local/bin/swatch
#!/bin/bash
### BEGIN INIT INFO
# Provides: swatch
# Short-Description: Start Stop Swatchdog Daemon
# Default-Start: 2 3 4 5
# Default-Stop:
### END INIT INFO
PATH=/bin:/usr/sbin:/usr/bin
LANG=C
. /lib/lsb/init-functions
DAEMON=/usr/local/bin/swatch
test -x $DAEMON || exit 1
case "$1" in
start)
ls /tmp/.swatchdog_script.* > /dev/null 2>&1
if [ $? -ne 0 ]; then
for conf in /etc/swatch/*.conf
do
WATCHLOG=`grep "^# logfile" $conf | awk '{ print $3 }'`
swatchdog --config-file $conf --tail-file $WATCHLOG \
--script-dir=/tmp --awk-field-syntax --use-cpan-file-tail --daemon \
>> /var/log/swatch/swatch.log 2>&1
echo "Starting Swatchdog... $conf $WATCHLOG" >> /var/log/swatch/swatch.log
if [ $? -ne 0 ]; then
echo "`date` Script error. Swatchdog couldn't start." >> /var/log/swatch/swatch.log
echo " Script error. Swatchdog couldn't start."
exit 1
fi
done
[ $? = 0 ] && touch /var/lock/subsys/swatch
echo "`date` Swatchdog Daemon has started." >> /var/log/swatch/swatch.log
echo " Swatchdog Daemon has started."
else
echo " Swatchdog Daemon already has started."
fi
;;
stop)
ls /tmp/.swatchdog_script.* > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "`date` Shutting down Swatchdog Daemon." >> /var/log/swatch/swatch.log
echo " Shutting down Swatchdog Daemon..."
for pid in `ps aux | grep swatchdog | grep -v grep | awk '{print $2}'`
do
kill $pid
done
rm -f /var/lock/subsys/swatch /tmp/.swatchdog_script.*
else
echo " Swatchdog Daemon is not running."
fi
;;
restart)
/usr/local/bin/swatch stop
/usr/local/bin/swatch start
;;
status)
ls /tmp/.swatchdog_script.* > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo -e "\n Swatchdog Daemon is monitering as following pid...\n"
ps aux | grep swatchdog | grep -v grep | awk '{print $2,$9,$10,$11,$12,$13,$14,$15,$16}'
else
echo -e "\n Swatchdog Daemon is not running.\n"
fi
;;
*)
echo "Bad usage. Please use swatch command with... {start|stop|restart|status}."
exit 1
esac
exit 0
systemd へ登録する
systemd への登録。systemd は最終生成された子プロセスのみを監視する仕様らしいので、type は oneshot としている。
vi /etc/systemd/system/swatch.service
[Unit]
Description=Swatchdog Log Monitering Daemon
[Service]
ExecStart=/usr/local/bin/swatch start
ExecReload=/usr/local/bin/swatch restart
ExecStop=/usr/local/bin/swatch stop
Restart=no
Type=oneshot
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
swatch のアクションスクリプトを書く
続いて各種ログから不正アクセスチャレンジを検出した際に実行するスクリプトを作成。ロックしたIPの解除は敢えて変則的な36時間後に設定。24時間とか48時間とかでも良い。
2023年3月、話題のChatGPTさんに校正校閲改善してもらいました。ただし、BASH_REMATCH
を使っているので、1行のログに複数のIPアドレスがあった場合は最後にマッチしたものとなります。
vi /usr/local/bin/swatch_action.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin
LANG=C
# ログを標準入力から取得
read LOG
# ログからIPアドレスらしきものを抽出
if [[ $LOG =~ (([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]) ]]; then
IPADDR=${BASH_REMATCH[0]}
else
echo "`date` [swatch_action.sh:Couldn't get IPADDR] $LOG" >> /var/log/swatch/IPerr.log
exit
fi
# 自固定グローバルIP(環境に応じて変更)
MYIP="xxx.xxx.xxx.xxx"
# IPが指定したものであれば終了(ホワイトリスト)
if [ "$IPADDR" = "$MYIP" ]; then
exit
fi
# 拒否リストにIPが含まれている場合は終了
TargetIP=`iptables -w -L INPUT -n | grep DROP.*all | grep " $IPADDR "`
sleepenh 0.5 > /dev/null 2>&1
if [ -n "$TargetIP" ]; then
exit
fi
# IPアドレスが取得できた場合の処理はここから
# IPアドレスがプライベートIPアドレスの場合は終了
if [[ "$IPADDR" = "127.0.0.1" || "$IPADDR" =~ ^10\. || "$IPADDR" =~ ^172\.(1[6-9]|2[0-9]|3[01])\. || "$IPADDR" =~ ^192\.168\. || "$IPADDR" =~ \.0$ ]]; then
exit
fi
# 以下IPTABLESに登録がない場合の処理
# ログをアドレス別ログに記録し、行数をカウント
echo "$LOG" >> /var/log/swatch/$IPADDR
cnt=`cat /var/log/swatch/$IPADDR | wc -l`
# アクセスしてきたIPのログが5行以上または引数でlockを指定されている場合登録処理
if [ $cnt -ge 5 ] || [ "$1" = "lock" ]; then
### IPTABLESに登録
iptables -w -I INPUT -s $IPADDR -j DROP
### 解除予定(36時間後)を予約
echo "iptables -D INPUT -s $IPADDR -j DROP > /dev/null 2>&1; rm -f /var/log/swatch/$IPADDR > /dev/null 2>&1" | at now+36hour > /dev/null 2>&1
echo "`date` $IPADDR locked. Release schedule at `date --date '36hours' +'%Y/%m/%d %H:%M:%S'`." >> /var/log/swatch/swatch.log
fi
実行権限を与える。
chmod 755 /usr/local/bin/swatch_action.sh
実際の環境では、ログ(ソフト)ごとに専用のスクリプトを作成し運用している。また、ブロックする時間も1500時間以上ブロックしている。スクリプトと設定ルールの書き方で本来ブロックしたくないIPがブロックされてしまったりするので、運用の経過を注意して確認し、目的とするIPだけがブロックされるように調整する。
IPアドレスを抽出する別の方法。
ChatGPTさんいわく、grep
を使うより組み込み機能を使った方が速いよ、との事。でも必ず最後にマッチしたものが対象とするIPとは限らない。ログのフォーマットに合わせてスクリプトを記述する。
IPADDR=`echo $LOG | grep -o "\(\([1-9]\?[0-9]\|1[0-9]\{2\}\|2[0-4][0-9]\|25[0-5]\)\.\)\{3\}\([1-9]\?[0-9]\|1[0-9]\{2\}\|2[0-4][0-9]\|25[0-5]\)"`
設定の例
設定ファイルの例。/etc に swatch ディレクトリがなければ mkdir で作成。設定ファイルは xxx.conf で作成したものを読み込むように起動スクリプト中に記載している。
Postfix, Dovecot の不正アクセス対策の例。
vi /etc/swatch/mail.conf
# logfile /var/log/mail.log
watchfor /warning.*authentication failed/
pipe "/usr/local/bin/swatch_action.sh"
throttle=00:00:20
watchfor /warning.*verification failed/
pipe "/usr/local/bin/swatch_action.sh"
throttle=00:00:20
watchfor /Aborted login.*failed/
pipe "/usr/local/bin/swatch_action.sh"
throttle=00:00:20
watchfor /Disconnected.*auth failed/
pipe "/usr/local/bin/swatch_action.sh"
throttle=00:00:20
watchfor /NOQUEUE.*reject.*Relay access denied/
pipe "/usr/local/bin/swatch_action.sh"
throttle=00:00:20
apache への不正アクセス対策の例。
vi /etc/swatch/httpd.conf
# logfile /var/log/apache2/access.log
# PHPMyadminへのアクセスを試みるipを規制(必要な人は書いちゃダメ)
watchfor /phpmyadmin/i
pipe "/usr/local/bin/swatch_action.sh lock"
throttle=00:00:30
# MySQL関連への不正ログインを試みるipを規制(必要な人は書いちゃダメ)
watchfor /mysql/i
pipe "/usr/local/bin/swatch_action.sh lock"
throttle=00:00:30
# Directory traversal
watchfor /etc.*passwd/i
pipe "/usr/local/bin/swatch_action.sh lock"
throttle=00:00:30
watchfor /proc.*self/i
pipe "/usr/local/bin/swatch_action.sh lock"
throttle=00:00:30
ログ関連
ログ用のディレクトリとファイルを作成する。所有者は root:root 、パーミッションは 644(デフォルト)でOK。
mkdir /var/log/swatch
chmod 655 /var/log/swatch
touch /var/log/swatch/swatch.log
touch /var/log/swatch/IPerr.log
touch /var/log/swatch/DMerr.log
ログローテ設定。
vi /etc/logrotate.d/swatch
/var/log/swatch/swatch.log
/var/log/swatch/DMerr.log
/var/log/swatch/IPerr.log
{
weekly
rotate 8
missingok
notifempty
sharedscripts
postrotate
/usr/local/bin/swatch restart
endscript
}
重複や漏れ等の整合性維持を自動化する
iptables
とatジョブ
の整合性を保つスクリプトを作成。なぜかわからないが、片方だけにしか登録されていないことがしばしばある。
vi atcheck.sh
#!/bin/bash
##################################################################
# swatchの動作と連携して問題を未然に防ぐスクリプト
# 4時間ごとにcronでの定期実行、サーバー再起動後、iptables再設定後に実行を推奨
##################################################################
PATH=/bin:/usr/sbin:/usr/bin
LANG=C
####################################################################
# ATjobに重複して解除登録しているIPのジョブを削除
# 後に登録した方を優先し、先に動くジョブが削除対象
####################################################################
IParray=()
for K in `atq | sort -r | awk '{print $1}'`
do
IPADDR=`at -c ${K} | grep iptables | cut -d " " -f 5`
Ovl=`echo "${IParray[@]}" | grep " $IPADDR "`
if [ -n "$Ovl" ]; then
atrm $K > /dev/null 2>&1
else
IParray+=" $IPADDR "
fi
done
IParray=()
###############################################################
# 解除登録されているがIPTABLESに登録されていないIPを一括登録
###############################################################
ATarray=()
for N in `atq | awk '{print $1}'`
do
IPADDR=`at -c ${N} | grep iptables | cut -d " " -f 5`
RuleCheck=`iptables -w -L INPUT -n | grep " $IPADDR "`
sleepenh 0.4 > /dev/null 2>&1
if [ -z "$RuleCheck" -a -n "$IPADDR" ]; then
echo `iptables -w -I INPUT -s $IPADDR -j DROP > /dev/null 2>&1`
fi
ATarray=($IPADDR "${ATarray[@]}")
done
####################################################################
# IPTABLESに登録されているが、解除予約されていないIPをatに一括登録
####################################################################
for M in `iptables -w -L INPUT -n | grep DROP.*all | awk '{print $4}'`
do
IPADDR=${M}
if [ $IPADDR != '0.0.0.0/0' ]; then
if ! `echo ${ATarray[@]} | grep -q "$IPADDR" > /dev/null 2>&1` ; then
echo "iptables -D INPUT -s $IPADDR -j DROP > /dev/null 2>&1; \
rm -f /var/log/swatch/$IPADDR > /dev/null 2>&1" | \
at now+36hour > /dev/null 2>&1
echo "`date` $IPADDR locked. Release schedule at `date --date '36hours' +'%Y/%m/%d %H:%M:%S'`." > /var/log/swatch/swatch.log
fi
fi
done
前回の投稿記事で作成した/etc/cron.weekly/dl_iplist
中のコメントを外してIPアドレス一覧更新後の実行を有効化する。iptables.sh
が実行されるとルールが再設定され、swatch
で登録したIPが全て消えてしまう。作成した atcheck.sh
はcrontab
で適当な間隔で実行(4-8時間間隔くらい)。
実行権限を与える。
chmod 655 atcheck.sh
swatchの起動とステータス確認
swatchを起動。
systemctl start swatch
自動起動を設定。
systemctl enable swatch
起動状態を確認。
systemctl status swatch
● swatch.service - Swatchdog Log Monitering Daemon
Loaded: loaded (/etc/systemd/system/swatch.service; disabled; vendor preset: enabled)
Active: active (exited) since Sat 2021-03-30 18:11:28 JST; 1 day 19h ago
Process: 962666 ExecStart=/usr/local/bin/swatch start (code=exited, status=0/SUCCESS)
Main PID: 962666 (code=exited, status=0/SUCCESS)
Tasks: 0 (limit: 18949)
Memory: 6.0M
CGroup: /system.slice/swatch.service
3月 30 18:11:28 mydomain.jp systemd[1]: Starting Swatchdog Log Monitering Daemon...
3月 30 18:11:28 mydomain.jp swatch[962666]: Swatchdog Daemon has started.
3月 30 18:11:28 mydomain.jp systemd[1]: Finished Swatchdog Log Monitering Daemon.
こんな感じで表示されれば OK。以下コマンドでも pid と設定ファイル等が確認できる。
swatch status
Permission denied
と表示される場合、実行権限付与忘れを確認する。
管理のための便利スクリプト
スクリプトが期待してない動作をしてiptables
に登録され、該当するIPを削除したいときなど、AT
のジョブナンバーが知りたいことがしばしばある。atq
コマンドで内容を見れるが、いちいち確認していくのが面倒。というわけで、ジョブナンバー、削除予定のIPアドレスを一覧で画面に出力するコマンドを自作した。
vi atqipshow.sh
#!/bin/bash
PATH=/bin:/usr/sbin:/usr/bin
LANG=C
echo ""
echo ""
echo "------------- ATQIPSHOW ver 0.4b by yukisku begin. -------------"
echo ""
echo -e " ATjob \t IP addr \t\t status"
echo "-----------------------------------------------------------------"
IParray=()
cnt=0
jobnumcnt=()
for N in `atq | sort -r | awk '{print $1}'`
do
IPADDR=`at -c ${N} | grep iptables | cut -d " " -f 6`
if [[ -z "$IPADDR" ]]; then
IPADDR="Unrelated process"
else
jobnumcnt=$(( jobnumcnt + 1 ))
fi
Ovl=`echo "${IParray[@]}" | grep " $IPADDR "`
if [[ -n "$Ovl" ]]; then
cnt=$(( cnt + 1 ))
echo -e " $N \t\t $IPADDR \t already registered"
else
IParray+=" $IPADDR "
echo -e " $N \t\t $IPADDR"
fi
done
echo "-----------------------------------------------------------------"
echo ""
if [[ "$jobnumcnt" -ge 1 ]]; then
echo " $jobnumcnt job(s) exist."
else
echo " There is no job now."
fi
if [[ "$cnt" -ge 1 ]]; then
echo " $cnt ATQ job(s) are overlapped (Will be deleted by atcheck)."
else
echo " There is no job overlapped."
fi
echo ""
echo "-------------- ATQIPSHOW END --------------"
echo ""
使い方はbashで実行するだけ。
bash atqipshow.sh
ジョブナンバー、IPアドレス、重複登録されているかどうかが表示されます。