すくりぷつ
きむちぃの国やら 4000 年の歴史の国やら SHOGUN 様の国などからお客様がたくさんいらしてくださるおかげで、いろいろ大繁盛な今日この頃ですが、みなさまいかがお過ごしでしょうか。
てなわけで、貴重な 産卵シーン 睡眠時間とか携帯電話の電池が減る一方ということで、ひとつスクリプトをこさえてみました。1 台の ftp サーバだけだとオーバスペックなのですが、複数台の ftp サーバが同じ NAS の同じ領域をマウントしていることを前提に、ある 1 台の ftp が大繁盛になってしまった場合に、そのサーバだけではなく他のサーバでもお引き取りいただけるようになっています。
ログファイルのパス (TARGET_LOG) と引っかける文字列 ('no such user found from') と IP アドレスの場所 (awk '{ print $15 }' ${TEMP_FILE}) を変えてやれば、http や ssh にも応用できます。複数台での運用を前提にしているので、HOGE_DIR は NAS をマウントしている領域上でなければ意味がありません。
使い方は、root で
[root@boinc ~]# ./hoge.sh
です。デフォルトでは、ログの最新 2000 行の中から、'no such user found from' が 50 以上見つかった場合に、その IP アドレスを iptables に登録します。ログの最新 25000 行の中から 128 以上見つかった場合に iptables に登録するには、
[root@boinc ~]# ./hoge.sh 128 25000
とします。新しく iptables に登録した IP アドレスは、メールで通知 (MAILTO) します。
んで、以下ソース。
#!/bin/sh # # usage: hoge.sh <threshold> <numlines> TARGET_LOG='/var/log/proftpd/proftpd.log' HOGE_DIR='/var/tmp/hoge' BLACKLIST="${HOGE_DIR}/blacklist" BLACKLIST_BACKUP="${BLACKLIST}.${YMDHMS}" IPTABLES_CONF='/etc/sysconfig/iptables' IPTABLES_CONF_BACKUP="${IPTABLES_CONF}.${YMDHMS}" TEMP_FILE="${HOGE_DIR}/temp" IPLIST_FILE="${HOGE_DIR}/iplist" UNIQ_FILE="${HOGE_DIR}/uniqlist" LOCK_FILE="${HOGE_DIR}/.lock" NEW_FILE="${HOGE_DIR}/newlist" UPDATE_FILE="${HOGE_DIR}/.updated" MAILTO='hoge@example.com' REMOVAL_DAYS='7' THIS=`basename ${0}` HOSTNAME=`${HOSTNAME}` YMD=`date +%Y%m%d` YMDHMS=`date +%Y%m%d%H%M%S` LOGGING="logger -p local0.info -i -t hoge" exit_proc() { rm -f ${TEMP_FILE} ${IPLIST_FILE} ${UNIQ_FILE} ${LOCK_FILE} ${NEW_FILE} } THIS_UID=`id -u` if [ ${THIS_UID} -ne 0 ] then echo "${THIS}: need root priviledges" >&2 exit 1 fi if [ ! -d ${HOGE_DIR} ] then mkdir -p ${HOGE_DIR} fi if [ $# -eq 0 ] then THRESHOLD=100 NUMLINES=2000 elif [ $# -eq 1 ] then THRESHOLD=${1} NUMLINES=2000 elif [ $# -eq 2 ] then THRESHOLD=${1} NUMLINES=${2} else echo "${THIS}: invalid usage" >&2 echo "${THIS} [threshold] [numlines]" >&2 exit 1 fi # lock while true do if [ ! -e ${LOCK_FILE} ] then touch ${LOCK_FILE} echo $$ >> ${LOCK_FILE} sleep 3 PID=`cat ${LOCK_FILE}` if [ ${PID} -eq $$ ] then break fi fi sleep 1 done if [ ! -e ${BLACKLIST} ] then touch ${BLACKLIST} fi mv ${BLACKLIST} ${BLACKLIST_BACKUP} if [ $? -ne 0 ] then echo "failed to backup blacklist" | ${LOGGING} rm -f ${BLACKLIST_BACKUP} exit_proc exit 0 fi # update blacklist LINES=`wc -l ${BLACKLIST_BACKUP} | awk '{ print $1 }'` if [ ${LINES} -ne 0 ] then for LINE in `cat ${BLACKLIST_BACKUP}` do ADD_YMD=`echo ${LINE} | awk -F: '{ print $2 }'` DEL_YMD=`date -d "${REMOVAL_DAYS} days ago" +%Y%m%d` if [ ${ADD_YMD} -ge ${DEL_YMD} ] then echo ${LINE} >> ${BLACKLIST} fi done fi # retrieve attacker's IP addresses tail -n ${NUMLINES} ${TARGET_LOG} | grep 'no such user found from' > ${TEMP_FILE} if [ $? -eq 0 ] then awk '{ print $15 }' ${TEMP_FILE} > ${IPLIST_FILE} if [ $? -ne 0 ] then echo "failed to retrieve attacker's IP address" | ${LOGGING} rm -f ${BLACKLIST_BACKUP} exit_proc exit 1 fi if [ ! -s ${IPLIST_FILE} ] then echo "missing attacker's IP address list...proftpd logging format changed ?" | ${LOGGING} rm -f ${BLACKLIST_BACKUP} exit_proc exit 1 fi sort ${IPLIST_FILE} | uniq > ${UNIQ_FILE} if [ $? -ne 0 ] then echo "failed to sort/uniq attacker's IP address" | ${LOGGING} rm -f ${BLACKLIST_BACKUP} exit_proc exit 1 fi if [ ! -s ${UNIQ_FILE} ] then echo "missing sorted list" | ${LOGGING} rm -f ${BLACKLIST_BACKUP} exit_proc exit 1 fi # add attacker's IP addresses to the blacklist for IPADDR in `cat ${UNIQ_FILE}` do CNT=`grep ${IPADDR} ${IPLIST_FILE} | wc -l` if [ $CNT -lt ${THRESHOLD} ] then continue fi grep -F ${IPADDR} ${BLACKLIST} > /dev/null 2>&1 if [ $? -eq 0 ] then continue fi echo "${IPADDR}:${YMD}" >> ${BLACKLIST} echo "add ${IPADDR} to ${BLACKLIST}" | ${LOGGING} echo "${IPADDR}" >> ${NEW_FILE} done fi # unlock rm -f ${LOCK_FILE} # check updated if [ ! -e ${NEW_FILE} -a ! -e ${UPDATE_FILE} ] then echo "${THIS}: no update iptables" | ${LOGGING} rm -f ${BLACKLIST_BACKUP} exit_proc exit 0 elif [ ! -e ${NEW_FILE} -a -e ${UPDATE_FILE} ] then grep -F ${HOSTNAME} ${UPDATE_FILE} > /dev/null 2>&1 if [ $? -eq 0 ] then echo "${THIS}: update iptables for all hosts done" | ${LOGGING} rm -f ${BLACKLIST_BACKUP} rm -f ${UPDATE_FILE} exit_proc exit 0 fi fi # backup iptables conf file cp -a ${IPTABLES_CONF} ${IPTABLES_CONF_BACKUP} if [ $? -ne 0 ] then echo "failed to backup iptables conf file" | ${LOGGING} rm -f ${BLACKLIST_BACKUP} exit_proc exit 0 fi # update iptables conf file cat << _EOF_ > ${IPTABLES_CONF} # Firewall configuration written by system-config-securitylevel # Manual customization of this file is not recommended. *filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] :RH-Firewall-1-INPUT - [0:0] -A INPUT -j RH-Firewall-1-INPUT -A FORWARD -j RH-Firewall-1-INPUT -A RH-Firewall-1-INPUT -i lo -j ACCEPT -A RH-Firewall-1-INPUT -p icmp --icmp-type any -j ACCEPT -A RH-Firewall-1-INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT _EOF_ for LINE in `cat ${BLACKLIST}` do IPADDR=`echo ${LINE} | awk -F: '{ print $1 }'` if [ $? -ne 0 ] then echo "failed to create iptables conf file, restore backup" | ${LOGGING} cp -a ${IPTABLES_CONF_BACKUP} ${IPTABLES_CONF} rm -f ${BLACKLIST_BACKUP} exit_proc exit 1 fi echo "-A RH-Firewall-1-INPUT -m state --state NEW -i eth0 -s ${IPADDR} -j DROP" >> ${IPTABLES_CONF} done cat << _EOF_ >> ${IPTABLES_CONF} -A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT -A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 21 -j ACCEPT -A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 443 -j ACCEPT -A RH-Firewall-1-INPUT -j REJECT --reject-with icmp-host-prohibited COMMIT _EOF_ if [ -e ${NEW_FILE} ] then echo ${HOSTNAME} > ${UPDATE_FILE} mail -s "${HOSTNAME}: iptables blacklist updated" ${MAILTO} < ${NEW_FILE} fi service iptables restart exit_proc echo "update blacklist successfully." | ${LOGGING} exit 0
あ、そうそう。iptables のヒアドキュメントの部分は、環境に合わせて変更してくださいね。
こいつを cron で動かせば、多い日も安心 (?) なこと請け合いです。アクセスの多いサーバでは 10 分ごと、そうでもないサーバでは 1 時間ごとくらいでいいでしょう。
最後に、このすくりぷつにはバグがあります。最初にそれを指摘した方には 100 円差し上げますw