Brute Force Attack対策+α(Firewalld)
20181031

パスワードやファイル名に対してのブルートフォースアタック、総当り攻撃、辞書攻撃及びhttpサーバへのディレクトリトラサーバル対策

SSH、FTP、MAIL、HTTP、およびDNSサーバのログを定期的に監視し、攻撃元IPをFirewalldの「Drop」ゾーンに登録していくスクリプトを自動実行させます。
攻撃を検知してから何らかの処置を取る事から、簡易的ではありますがIPSのような動作となります。
※攻撃元は動的なIPアドレスを利用されることが多いため、Firewalldの一時的なルールに追加するスクリプトとしております。
 そのため、システム再起動時には初期化されますが、登録期間が短ければ(未ログローテーション)再登録されます。
 2017/08/07 恒久的に登録する仕組みも追加

※iptablesをご利用の方は、Brute Force Attack対策(iptables)をご参照ください。
確認環境
 CentOS7.x
 CentOS6.x
 CentOS5.x
 Cron
 sshd
 vsftpd
 Postfix+Dovecot
 Apache
 BIND

頻繁にtmpファイルを作成する事から、「tmpfs(RAMディスク)」の設定も検討して下さい。
20160415 メールの強化と処理を大きく変更した。
20170807 根本的に処理を変更
20181031 プロセスを分けて擬似的にマルチコア対応し、システムによっては処理が数倍早くなった。
◆処理の流れ(デフォルトの場合)
 1.SSH、FTP、MAIL、HTTP、およびDNSサーバのログをCronにて5分毎に監視する。

 2.累計10回の不正アクセスの痕跡を確認した場合はIPレベルでのアクセス拒否を実施する。
 →Firewalldの「Drop」ゾーンに登録
 →不正なアクセスのログが無ければ処理の終了
 →ホワイトリストファイルにIPアドレスを1行ずつ記述する事で監視除外IPの指定が可能
 →HTTP監視においては、存在しないディレクトリ及びファイルの参照を試みる行為を監視します。
 →また、HTTP監視においては誤検知を減らす工夫が必要となる為、除外ディレクトリの指定が可能

 3.不正アクセスの痕跡を確認し、対処した事場合は詳細をログに保存

 4.上記内容をroot宛にメールで送信

◆監視ログメッセージ
 SSH
  "Invalid user"
  "Failed password"

 FTP
  "FAIL LOGIN"

 MAIL
  "authentication failure"
  "auth failed"
  "Recipient address rejected: User unknown in local recipient table"
  "SSL_accept error"
  "Connection concurrency limit exceeded"
  "NOQUEUE: reject:"
  "policyd-spf"

 HTTP
  "File does not exist"
  "script" & "not found or unable to stat"
  "../"
  "//"
  "//" ←HTTP通信時
  "404" ←HTTPS通信時
  ".php"に対して301コードを複数回行っている場合 ←HTTPSへ移行並びに自動リダイレクトをしていない場合はコメントアウト推奨

 DNS
  "denied"
◆まずはシェルスクリプトの作成
 [root@Server ~]# vi /root/brute_force_attack.sh
 ※過去安定版が必要な場合はこちらから。

#!/bin/sh
export LANG=ja_JP.UTF-8

# Ver 20181031(マルチコア対応版)

# 攻撃判断回数(デフォルト10回以上)
NG="10"

# 回数の多い攻撃を恒久的に遮断する
# 「yes」で有効(デフォルト有効)
PNG="yes"
# 攻撃判断回数(デフォルト100回以上)
PNGL="100"

# そもそも遮断するIPは恒久的に設定する
# 「yes」で有効。(デフォルト無効)
PER="no"

# ログ監視の有効化設定 「no」で無効。(デフォルトは全て有効)
TELNET_CHECK="yes"
SSH_CHECK="yes"
FTP_CHECK="yes"
MAIL_CHECK="yes"
HTTP_CHECK="yes"
DNS_CHECK="yes"

# 監視ログファイル
# TelnetとSSHに利用
SSH_LOG="/var/log/secure"
# FTPに利用
FTP_LOG="/var/log/vsftpd.log"
# Mailに利用
MAIL_LOG="/var/log/maillog"
# HTTPに利用
HTTP_LOG="/var/log/httpd/error_log"
HTTP_LOG2="/var/log/httpd/access_log"
HTTPS_LOG="/var/log/httpd/ssl_access_log"
# DNSに利用
DNS_LOG="/var/log/named.log"

# HTTP監視除外ディレクトリ及びファイル設定(デフォルトでは「/home」ディレクトリ以下に存在するファイル全て)
DIR="/home"

# 攻撃を検知した際にメールを送信するか否か。「no」で無効。(デフォルト有効)
MAILMESSAGES="yes"
# メール送信先アドレス。(デフォルトroot宛)
MAILADDRESS="root"

# 検知ログのファイル名
BR_LOG="/var/log/brute_force_attack.log"

# Firewalldログのファイル名(エラー防止) 「yes」で有効。(デフォルト有効)
# 二つも無い場合は適当なファイル名を入れれば問題無し
ERR_CHECK="yes"
ERR_LOG="/var/log/firewalld"
ERR_LOG2="/var/log/messages"

# ホワイトリストファイルの指定(任意) 「yes」で有効。(デフォルト有効)
WHITE_CHECK="yes"
WHITE_LIST="/root/sh/allow_ip"

# ブラックリストファイル指定(任意) 「yes」で有効。(デフォルト無効)
BLACK_CHECK="no"
BLACK_LIST="/root/blacklist-ip"

# 一時ファイル作成ディレクトリの指定
TMP_DIR="/tmp"

# 以降設定不要

function_CORE()
{
if [ -e $TMP_DIR/brute_force_attack/$PROCESS_DIR ]; then
PSCOUNT=`ps -ef | grep $0 | grep $1 | grep -v grep | wc -l` if [ $PSCOUNT -ge 1 ] ; then
LOG_DATE=`date '+%Y/%m/%d %k:%M:%S'`
echo "["$LOG_DATE"] ""多重起動($1)を検知 プロセスを終了しました。" >> $BR_LOG
exit
else
LOG_DATE=`date '+%Y/%m/%d %k:%M:%S'`
echo "["$LOG_DATE"] ""スタックを検知したため、$PROCESS_DIRのtmpファイルを削除しました。" >> $BR_LOG
rm -rf $TMP_DIR/brute_force_attack/$PROCESS_DIR
exit
fi
else
mkdir -p $TMP_DIR/brute_force_attack/$PROCESS_DIR
fi

TMP="$TMP_DIR/brute_force_attack/$PROCESS_DIR/brute_force_attack.tmp"
TMP2="$TMP_DIR/brute_force_attack/$PROCESS_DIR/brute_force_attack2.tmp"
TMP3="$TMP_DIR/brute_force_attack/$PROCESS_DIR/brute_force_attack3.tmp"
IP_TMP="$TMP_DIR/brute_force_attack/ip.tmp"
FLAG_TMP="$TMP_DIR/brute_force_attack/flag.tmp"

if [ $1 = "TELNET" ] ; then
grep "login:" $SSH_LOG | grep "FAILED LOGIN" | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" > $TMP
MSGLOG="$SSH_LOG"
elif [ $1 = "SSH" ] ; then
grep "sshd" $SSH_LOG | grep "Invalid user" | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" > $TMP
grep "sshd" $SSH_LOG | grep "Failed password" | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" >> $TMP
MSGLOG="$SSH_LOG"
elif [ $1 = "FTP" ] ; then
grep "FAIL LOGIN" $FTP_LOG | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" > $TMP
MSGLOG="$FTP_LOG"
elif [ $1 = "MAIL" ] ; then
grep "authentication failure" $MAIL_LOG | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" > $TMP
grep "auth failed" $MAIL_LOG | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" >> $TMP
grep "Recipient address rejected: User unknown in local recipient table" $MAIL_LOG | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" >> $TMP
grep "SSL_accept error" $MAIL_LOG | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" >> $TMP
grep "Connection concurrency limit exceeded" $MAIL_LOG | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" >> $TMP
grep "NOQUEUE: reject:" $MAIL_LOG | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" >> $TMP
# 下記、SPF導入済みのログ。踏み台となっているPCをWebアクセス等で防御するのは良くないかも?一旦無効化
# grep "policyd-spf" $MAIL_LOG | grep "Fail" | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" >> $TMP
MSGLOG="$MAIL_LOG"
elif [ $1 = "HTTP" ] ; then
DIR_LIST=`ls $DIR`
grep "error" $HTTP_LOG | grep "File does not exist" | grep -v "$DIR_LIST" | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" > $TMP
grep "error" $HTTP_LOG | grep "script" | grep "not found or unable to stat" | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" >> $TMP
# PHPへのアクセスでSSLにリダイレクトしない物をカウント。通常はリダイレクトされるため怪しい通信のみがカウントされるはず。
# HTTPSへ移行並びに自動リダイレクトをしていない場合はコメントアウト推奨
grep ".php" $HTTP_LOG | grep "301" | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" >> $TMP
grep "\.\./" $HTTP_LOG2 | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" >> $TMP
grep "//" $HTTP_LOG2 | grep -v http:// | grep -v https:// | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" >> $TMP
grep "not found or unable to stat" $HTTPS_LOG | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" >> $TMP
# faviconやappleのiconは除外する
grep " 404 " $HTTPS_LOG | grep -v favicon.ico | grep -v apple-touch-icon | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" >> $TMP
MSGLOG="$HTTP_LOG"
MSGLOG2="$HTTP_LOG2"
elif [ $1 = "DNS" ] ; then
grep "denied" $DNS_LOG | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" > $TMP
MSGLOG="$DNS_LOG"
else
:
fi
usleep 50000
# ホワイトリスト処理、重複アドレス処理、エラー防止処理、重複防止処理
cat $TMP | while read line;do ipcalc -sc $line && echo $line;done > $TMP2
cp -pr $TMP2 $TMP
usleep 50000

echo "" > $TMP2
if [ $WHITE_CHECK = "yes" ] ; then
if [ -s $WHITE_LIST ]; then
cat $WHITE_LIST >> $TMP2
fi
fi
if [ $ERR_CHECK = "yes" ] ; then
if [ -s $ERR_LOG ]; then
grep "ERROR" $ERR_LOG | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" >> $TMP2
fi
if [ -s $ERR_LOG2 ]; then
grep "firewalld" $ERR_LOG2 | grep "ERROR" | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" >> $TMP2
fi
fi
firewall-cmd --list-all-zones | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" >> $TMP2
firewall-cmd --list-all-zones --permanent | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" >> $TMP2
usleep 50000

IPLIST=`cat $TMP2`
for IP in $IPLIST
do
usleep 50000
grep -v "$IP" $TMP > $TMP3
cp -pr $TMP3 $TMP
done
echo "" > $TMP2
echo "" > $TMP3
usleep 50000
LIST=`cat $TMP`
for IP in $LIST
do
usleep 50000
COUNT=`grep -w "$IP" $TMP | wc -l`
if [ $COUNT -ge $NG ] ; then
if grep -w $IP $IP_TMP >/dev/null 2>&1; then
:
elif grep -w $IP $BR_LOG >/dev/null 2>&1; then
if [ $PER = "yes" -o $COUNT -ge $PNGL -a $PNG = "yes" ] ; then
firewall-cmd --permanent --zone=drop --add-source=$IP >/dev/null 2>&1
touch $FLAG_TMP
REG="恒久的に登録"
if [ $BLACK_CHECK = "yes" ] ; then
echo $IP >> $BLACK_LIST
fi
else
firewall-cmd --zone=drop --add-source=$IP >/dev/null 2>&1
REG="一時的に登録"
fi
LOG_DATE=`date '+%Y/%m/%d %k:%M:%S'`
echo "["$LOG_DATE"] ""[$1ログ監視] IPをFirewallのDrop-Zoneへ再登録("$REG"):"$COUNT "回 ["$IP"]" >> $BR_LOG
echo $IP >> $IP_TMP
else
if [ $PER = "yes" -o $COUNT -ge $PNGL -a $PNG = "yes" ] ; then
firewall-cmd --permanent --zone=drop --add-source=$IP >/dev/null 2>&1
touch $FLAG_TMP
REG="恒久的に登録"
if [ $BLACK_CHECK = "yes" ] ; then
echo $IP >> $BLACK_LIST
fi
else
firewall-cmd --zone=drop --add-source=$IP >/dev/null 2>&1
REG="一時的に登録"
fi
LOG_DATE=`date '+%Y/%m/%d %k:%M:%S'`
echo "["$LOG_DATE"] ""[$1ログ監視] IPをFirewallのDrop-Zoneへ"$REG":"$COUNT "回 ["$IP"]" >> $BR_LOG
echo $IP >> $IP_TMP
if [ $MAILMESSAGES = "yes" ] ; then
if [ $1 = "HTTP" ] ; then
cat "$MSGLOG" > $TMP3
cat "$MSGLOG2" >> $TMP3
MSG=`grep -w "$IP" $TMP3`
else
MSG=`grep -w "$IP" $MSGLOG`
fi
echo -e "[$1]ログ監視にて、"$COUNT"回の不正なアクセスログを 確認。\n対象ホストのIPアドレ スをFirewall のDrop-Zoneへ"$REG" しました。\n\nIPアドレス [$IP]\n\n$MSG"| mail -s "$0" $MAILADDRESS
fi
fi
fi
done
# 一時ファイル削除
rm -rf $TMP_DIR/brute_force_attack/$PROCESS_DIR
#最後のプロセスは終了処理を実行する
COUNT=`ls -l $TMP_DIR/brute_force_attack/ | grep ^d | wc -l`
if [ $COUNT -eq 0 ] ; then
if [ -s $IP_TMP ]; then
rm -rf $IP_TMP
fi
if [ -s $FLAG_TMP ]; then
rm -rf $FLAG_TMP
firewall-cmd --reload
fi
fi
exit
}

#実行処理
if [ -z $1 ] ; then
if [ -s $FIREWALL_TMP ]; then
:
else
firewall-cmd --list-all-zones | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" > $FIREWALL_TMP
fi
elif [ $1 = "TELNET" ] ; then
usleep 50000
PROCESS_DIR="TELNET"
function_CORE TELNET
elif [ $1 = "SSH" ] ; then
usleep 50000
PROCESS_DIR="SSH"
function_CORE SSH
elif [ $1 = "FTP" ] ; then
usleep 50000
PROCESS_DIR="FTP"
function_CORE FTP
elif [ $1 = "MAIL" ] ; then
usleep 50000
PROCESS_DIR="MAIL"
function_CORE MAIL
elif [ $1 = "HTTP" ] ; then
usleep 50000
PROCESS_DIR="HTTP"
function_CORE HTTP
elif [ $1 = "DNS" ] ; then
usleep 50000
PROCESS_DIR="DNS"
function_CORE DNS
fi
sleep 5
if [ $TELNET_CHECK = "yes" ] ; then
usleep 50000
bash $0 TELNET &
fi
if [ $SSH_CHECK = "yes" ] ; then
usleep 50000
bash $0 SSH &
fi
if [ $FTP_CHECK = "yes" ] ; then
usleep 50000
bash $0 FTP &
fi
if [ $MAIL_CHECK = "yes" ] ; then
usleep 50000
bash $0 MAIL &
fi
if [ $HTTP_CHECK = "yes" ] ; then
usleep 50000
bash $0 HTTP & fi
if [ $DNS_CHECK = "yes" ] ; then
usleep 50000
bash $0 DNS & fi

◆一応実行権限を付与
 [root@Server ~]# chmod +x /root/brute_force_attack.sh
◆cronにて自動実行するよう設定しましょう。
 [root@Server ~]# echo "*/5 * * * * root bash /root/brute_force_attack.sh >/dev/null 2>&1" >> /etc/crontab

この場合、5分毎に監視して対処します。
変更する場合はcrontabにて設定を確認して下さい。

終わり。