背景
RaspberryPiにSoftEther VPN https://ja.softether.org/ を入れて、遠隔地からRaspberryPi配下のLANのNasneやブルーレイレコーダをDiXiMソフトで再生したり、RaspberryPiのSSDにローカルアクセスして便利に使っている。iOS/Android標準のVPN設定や、FireHDでOpenVPNconnectアプリなどサクッとVPN接続できるのはありがたい。
しかしVPNのポートを全世界に向け開けっぱなしにすると、SoftEtherのログファイルserver_log/vpn_YYYYMMDD.logに毎日数十万ラインのアクセスを試みる怪しいIPアドレスが出現する。iptablesの豊富な機能で抑え込む方法もあるかもしれないが、ここでは、アクセスを許すホワイトリストのIPアドレスにのみVPNのポートの接続を許可するbashスクリプトを用意することにした。
前提
iptablesコマンドで、WAN側との管理が出来ている環境に、
iptables -A INPUT -p udp -m multiport --dports ポート -s IPアドレス -j ACCEPT
を追加して、IPアドレスからのポートを接続を許可させる。
遠隔地のIPアドレスは、https://ipinfo.io/what-is-my-ip からグローバルIPアドレスAA.BB.CC.DD(たいていはDynamicアドレス)を得る。また、このページの Connection > Route の AA.BB.0.0/NN 表示を採用するか、whois AA.BB.CC.DD からCIDR表記のIPアドレス範囲を得る。
使い方例
AA.BB.0.0/NN 行を含む ipt_PROVIDER1.txt を用意する。
但しAA.BB.CC.DD/32 のときは、/32を付けずに、
AA.BB.CC.DD とする。
# cat /usr/local/IPT_DATA/ipt_PROVIDER1.txt
AA.BB.0.0/NN # comment
# ipt_ALLOW.sh -a ipt_PROVIDER1.txt と実行すると
1: iptables -A INPUT -p udp -m multiport --dports 500,1194,4500 -s AA.BB.0.0/NN -j ACCEPT
1: iptables -A INPUT -p tcp -m multiport --dports 443,992,1194,5555 -s AA.BB.0.0/NN -j ACCEPT
ipt_PROVIDER1.txt ipt_PROVIDER2.txt ipt_PROVIDER3.txt とか
個別にポートを接続を許可のテスト確認ができたなら、ipt_addr.txt に集めておく。RaspberryPiが再起動された場合など、
ipt_ALLOW.sh -a
と実行して設定の復帰が ipt_addr.txt から行われる。
実行のイメージ
# egrep -v '^\s*#|^\s*$' /usr/local/IPT_DATA/ipt_addr.txt
AAA.BBB.0.0/16 # PROVIDER1
CC.DD.0.0/16 # PROVIDER2
EEE.FFF.GG.0/19 # PROVIDER3
# ipt_ALLOW.sh -a
1: iptables -A INPUT -p udp -m multiport --dports 500,1194,4500 -s AAA.BBB.0.0/16 -j ACCEPT
1: iptables -A INPUT -p tcp -m multiport --dports 443,992,1194,5555 -s AAA.BBB.0.0/16 -j ACCEPT
2: iptables -A INPUT -p udp -m multiport --dports 500,1194,4500 -s CC.DD.0.0/16 -j ACCEPT
2: iptables -A INPUT -p tcp -m multiport --dports 443,992,1194,5555 -s CC.DD.0.0/16 -j ACCEPT
3: iptables -A INPUT -p udp -m multiport --dports 500,1194,4500 -s EEE.FFF.GG.0/19 -j ACCEPT
3: iptables -A INPUT -p tcp -m multiport --dports 443,992,1194,5555 -s EEE.FFF.GG.0/19 -j ACCEPT
# ipt_ALLOW.sh -a -s http
1: iptables -A INPUT -p tcp --dport 80 -s AAA.BBB.0.0/16 -j ACCEPT
2: iptables -A INPUT -p tcp --dport 80 -s CC.DD.0.0/16 -j ACCEPT
3: iptables -A INPUT -p tcp --dport 80 -s EEE.FFF.GG.0/19 -j ACCEPT
# ipt_ALLOW.sh -d -s http
1: iptables -D INPUT -p tcp --dport 80 -s AAA.BBB.0.0/16 -j ACCEPT
2: iptables -D INPUT -p tcp --dport 80 -s CC.DD.0.0/16 -j ACCEPT
3: iptables -D INPUT -p tcp --dport 80 -s EEE.FFF.GG.0/19 -j ACCEPT
既知の問題
CIDR IPアドレス表記は、AA.BB.CC.DD/NN のうち数字ピリオドフォーマットしかチェックしておらず、論理的な誤りがある場合は、iptablesに想定の登録ができない。
-c | --check) # ルール反映チェックである程度誤りは検出できるハズ。
bash スクリプトコード
# !/bin/bash
### $Id: ipt_ALLOW.sh,v 1.00 2021/06/12 16:50:50 rapiq $
# -*- coding: utf-8 -*-
PROGNAME=$(basename $0)
# 本ipt_ALLOW.shスクリプトは、CIDR IPアドレスからの
# VPNポート接続をiptablesで許可操作(ルール追加削除)する
# -A, -a ルール追加 -D, -d ルール削除
usage() {
echo "Usage: $PROGNAME [OPTIONS] FILE"
echo " This script is 'iptables -A|-D ' From FILE."
echo " FILE[ipt_addr.txt] ... CIDR IP address text."
echo "Options:"
echo " -h, --help"
echo " -a, --add_rule" # -A, -a ルール追加
echo " -d, --del_rule" # -D, -d ルール削除
echo " -v, --verbose" # 操作ログ表示
echo " -c, --check" # ルール反映チェック
echo " -s, --service" # サービス名 vpn ssh http smtp で開放ポートを選ぶ
}
# SoftEther VPN Port udp 500,1194,4500 / tcp 443,992,1194,5555
vpn_ADD_DEL() {
UDP_CIDR=`grep udp $ACCEPT_Ln|grep 500,1194,4500|grep $CIDR`
if [ $? = $JUDGE ]; then # udp での登録状態はどうか
if [ $JUDGE = 0 ]; then # 登録があるのに追加しようとしている
SKIP_UDP="DUP. `echo "$UDP_CIDR"|awk '{print $1,$4,$8}'`"
else # 登録がないのに削除しようとしている
SKIP_UDP="REGISTER rule NOTHING $CIDR"
fi
echo "${CUR}: ${IPARAM}!SKIP udp $SKIP_UDP" | tee -a $LOGF # 操作はスキップ
else
COM_UDP="iptables $IPARAM INPUT -p udp -m multiport --dports 500,1194,4500 -s $CIDR -j ACCEPT"
echo "${CUR}: ${COM_UDP}" | tee -a $LOGF
if [ ! -z $ARG_V ]; then # -z 文字列長が 0 なら真
echo "${CUR}: ${COM_UDP}"
fi
# udp 追加 削除 を実行する
$COM_UDP
if [ $? != 0 ]; then # コマンドは正常終了か
echo "${CUR}: iptables $IPARAM $CIDR ERROR." | tee -a $LOGF
else # udp 追加 削除 できているか
if [ ! -z $ARG_C ]; then # ルール反映チェック
# 期待通りにiptables -A/-D が実行できている確認をする(くどい)
UDP_GREP=`iptables -L -n|egrep '^ACCEPT|udp'|grep 500,1194,4500|grep $CIDR`
if [ $? != $JUDGE ]; then # udp での登録状態はどうか
if [ $JUDGE = 0 ]; then # 追加したが登録できていない
SKIP_UDP=" NOT rule ADD. `echo "$UDP_CIDR"|awk '{print $1,$4,$8}'`"
else # 削除したが登録が残っている
SKIP_UDP=" NOT rule DELETE. `echo "$UDP_CIDR"|awk '{print $1,$4,$8}'`"
fi
echo "${CUR}: !SKIP udp $SKIP_UDP" | tee -a $LOGF # 操作はスキップ
if [ ! -z $ARG_V ]; then
echo "${CUR}: !SKIP udp $SKIP_UDP"
fi
else # ルール反映チェックは成功
if [ $JUDGE = 1 ]; then # 削除できている Check OK
echo "${CUR}: Check DELETE rule OK. $CIDR"
else # 追加できている Check OK
echo "${CUR}: Check ADD rule OK. $CIDR"
fi
fi
fi
fi
fi
TCP_CIDR=`grep tcp $ACCEPT_Ln|grep 443,992,1194,5555|grep $CIDR`
if [ $? = $JUDGE ]; then # tcp での登録状態はどうか
if [ $JUDGE = 0 ]; then # 登録があるのに追加しようとしている
SKIP_TCP="DUP. `echo "$TCP_CIDR"|awk '{print $1,$4,$8}'`"
else # 登録がないのに削除しようとしている
SKIP_TCP="REGISTER rule NOTHING $CIDR"
fi
echo "${CUR}: ${IPARAM}!SKIP tcp $SKIP_TCP" | tee -a $LOGF # 操作はスキップ
else
COM_TCP="iptables $IPARAM INPUT -p tcp -m multiport --dports 443,992,1194,5555 -s $CIDR -j ACCEPT"
echo "${CUR}: ${COM_TCP}" | tee -a $LOGF
if [ ! -z $ARG_V ]; then # -z 文字列長が 0 なら真
echo "${CUR}: ${COM_TCP}"
fi
# tcp 追加 削除 を実行する
$COM_TCP
if [ $? != 0 ]; then
echo "${CUR}: iptables $IPARAM $CIDR ERROR." | tee -a $LOGF
else # tcp 追加 削除 できているか
if [ ! -z $ARG_C ]; then # ルール反映チェック
# 期待通りにiptables -A/-D が実行できている確認をする(くどい)
TCP_GREP=`iptables -L -n|egrep '^ACCEPT|tcp'|grep 500,1194,4500|grep $CIDR`
if [ $? != $JUDGE ]; then # tcp での登録状態はどうか
if [ $JUDGE = 0 ]; then # 追加したが登録できていない
SKIP_TCP=" NOT rule ADD. `echo "$TCP_CIDR"|awk '{print $1,$4,$8}'`"
else # 削除したが登録が残っている
SKIP_TCP=" NOT rule DELETE. `echo "$TCP_CIDR"|awk '{print $1,$4,$8}'`"
fi
echo "${CUR}: !SKIP tcp $SKIP_TCP" | tee -a $LOGF # 操作はスキップ
if [ ! -z $ARG_V ]; then
echo "${CUR}: !SKIP tcp $SKIP_TCP"
fi
else # ルール反映チェックは成功
if [ $JUDGE = 1 ]; then # 削除できている Check OK
echo "${CUR}: Check DELETE rule OK. $CIDR"
else # 追加できている Check OK
echo "${CUR}: Check ADD rule OK. $CIDR"
fi
fi
fi
fi
fi
}
tcp1_ADD_DEL() { # TCP 1 PORT 操作
TCPPORT1=$1
TCP_CIDR=`grep tcp $ACCEPT_Ln|grep dpt:$TCPPORT1|grep $CIDR`
if [ $? = $JUDGE ]; then # tcp での登録状態はどうか
if [ $JUDGE = 0 ]; then # 登録があるのに追加しようとしている
SKIP_TCP="DUP. `echo "$TCP_CIDR"|awk '{print $1,$4,$8}'`"
else # 登録がないのに削除しようとしている
SKIP_TCP="REGISTER rule NOTHING $CIDR"
fi
echo "${CUR}: ${IPARAM}!SKIP tcp $SKIP_TCP" | tee -a $LOGF # 操作はスキップ
else
COM_TCP="iptables $IPARAM INPUT -p tcp --dport $TCPPORT1 -s $CIDR -j ACCEPT"
echo "${CUR}: ${COM_TCP}" | tee -a $LOGF
if [ ! -z $ARG_V ]; then # -z 文字列長が 0 なら真
echo "${CUR}: ${COM_TCP}"
fi
# tcp 追加 削除 を実行する
$COM_TCP
if [ $? != 0 ]; then
echo "${CUR}: iptables $IPARAM $CIDR ERROR." | tee -a $LOGF
else # tcp 追加 削除 できているか
if [ ! -z $ARG_C ]; then # ルール反映チェック
# 期待通りにiptables -A/-D が実行できている確認をする(くどい)
TCP_GREP=`iptables -L -n|egrep '^ACCEPT|tcp'|grep dpt:${TCPPORT1}|grep $CIDR`
if [ $? != $JUDGE ]; then # tcp での登録状態はどうか
if [ $JUDGE = 0 ]; then # 追加したが登録できていない
SKIP_TCP=" NOT rule ADD. `echo "$TCP_CIDR"|awk '{print $1,$4,$8}'`"
else # 削除したが登録が残っている
SKIP_TCP=" NOT rule DELETE. `echo "$TCP_CIDR"|awk '{print $1,$4,$8}'`"
fi
echo "${CUR}: !SKIP tcp $SKIP_TCP" | tee -a $LOGF # 操作はスキップ
if [ ! -z $ARG_V ]; then
echo "${CUR}: !SKIP tcp $SKIP_TCP"
fi
else # ルール反映チェックは成功
if [ $JUDGE = 1 ]; then # 削除できている Check OK
echo "${CUR}: Check DELETE rule OK. $CIDR"
else # 追加できている Check OK
echo "${CUR}: Check ADD rule OK. $CIDR"
fi
fi
fi
fi
fi
}
# SSH Port tcp 22
ssh_ADD_DEL() {
tcp1_ADD_DEL 22 # SSH
}
# HTTP Port tcp 80
http_ADD_DEL() {
tcp1_ADD_DEL 80 # HTTP
}
# SMTP Port tcp 25
smtp_ADD_DEL() {
tcp1_ADD_DEL 25 # SMTP
}
#
# 引数 オプション ファイル名 の処理
#
OPT=`getopt -o hadvcs: -l help,add_rule,del_rule,verbose,check,service: -- "$@"`
if [ $? != 0 ] ; then
exit 1
fi
eval set -- "$OPT"
while true
do
case "$1" in
-h | --help)
usage; exit 1
;;
-a | --add_rule) # -A, -a ルール追加
ARG_A=1; shift 1
;;
-d | --del_rule) # -D, -d ルール削除
ARG_D=1; shift 1
;;
-v | --verbose) # 操作ログ表示
ARG_V=1; shift 1
;;
-c | --check) # ルール反映チェック
ARG_C=1; shift 1
;;
-s | --service) # サービス名 vpn ssh http smtp で開放ポートを選ぶ
ARG_S=1; shift 1; VALUE_S="$1"; shift 1
;;
--)
shift; param+=( "$@" ); break
;;
*)
echo "ERR_F0020 Internal error!" 1>&2; exit 1
;;
esac
done
if [ "$ARG_A" = "" ] && [ "$ARG_D" = "" ]; then
usage
echo "ERR_F0030 !!! MUST USE -a or -d"
exit 1
fi
if [ ${EUID} != 0 ]; then # 実行UIDが "0"(root)で管理者権限
echo "ERR_F0010 User not root."
exit 1
fi
if [ ! -z $ARG_V ]; then
echo "START: $PROGNAME at `date`"
fi
# サービス名 (-s --service) vpn ssh http smtp で開放ポートを選ぶ
if [ "$VALUE_S" = "" ]; then
SRVNAME="vpn" ### 省略時サービス名
else
SRVNAME="$VALUE_S"
fi
IPTDIR="/usr/local/IPT_DATA"
if [ ! -d ${IPTDIR} ]; then
echo "ERR_F0040 $IPTDIR NOTEXISTS."
exit 1
fi
ACCEPT_Ln="${IPTDIR}/ACCEPT_Ln"
iptables -L -n|egrep ^ACCEPT|sort -k 4 > $ACCEPT_Ln
cd ${IPTDIR}
if [ $? != 0 ]; then
echo "ERR_F0050 cd $IPTDIR."
exit 1
fi
LOGDIR="${IPTDIR}/LOG"
if [ ! -d ${LOGDIR} ]; then
mkdir -p ${LOGDIR}
if [ $? != 0 ]; then
echo "ERR_F0060 $LOGDIR Cannot mkdir."
exit 1
fi
fi
YMDHM=`date '+%Y%m%d_%H%M'`
if [ "$ARG_A" != "" ]; then
IPARAM="-A"
LOGF="${LOGDIR}/${SRVNAME}_${YMDHM}_A.log"
JUDGE=0 # grep exists ERROR 0 if a line is selected
fi
if [ "$ARG_D" != "" ]; then
IPARAM="-D"
LOGF="${LOGDIR}/${SRVNAME}_${YMDHM}_D.log"
JUDGE=1 # grep NOT exists ERROR 1 if no lines were selected
fi
if [ "$param" = "" ]; then
IPTFILE="${IPTDIR}/ipt_addr.txt"
else
IPTFILE="${IPTDIR}/${param}"
fi
if [ ! -f "$IPTFILE" ]; then
echo "ERR_F0070 $IPTFILE NOT FOUND."
exit 1
fi
LSFILE=`ls -l ${IPTFILE}|awk '{print $5,$6,$7,$8,$9}'`
echo "" >> $LOGF
echo "${YMDHM} IPARAM=${IPARAM} $LSFILE" >> $LOGF
CUR=0 #########################################################
while read pline # $IPTFILE を一行読み込む #
do #########################################################
# egrep で#で始まるコメント行および空白行を削除する
echo $pline | egrep -q '^\s*#|^\s*$'
if [ $? = 0 ]; then
continue
fi
if [ "$pline" = "EOF" ]; then
if [ ! -z $ARG_V ]; then
echo "${IPTFILE} EOF Detected."
fi
break
fi
CUR=`expr ${CUR} + 1`
if [ ! -z $ARG_V ]; then
echo "${CUR}: ${pline}"
fi
echo "${CUR}: ${pline}" >> $LOGF
CIDR=`echo ${pline}|awk '{print $1}'`
echo $CIDR | perl -ne '/^\d+\.\d+\.\d+\.\d+\/\d+$/ and exit 1';RET=$?
# Classless Inter-Domain Routing サイダー クラスレスアドレッシング
if [ $RET != 1 ]; then ### 数字のみで整合性のチェックではない
echo $CIDR | perl -ne '/^\d+\.\d+\.\d+\.\d+/ and exit 1';RET2=$?
if [ $RET2 != 1 ]; then ### /NN がない場合もある
echo "${CUR}: $CIDR ADDRESS Missing." | tee -a $LOGF
exit 0
fi
fi
case "$SRVNAME" in
vpn) # SoftEther VPN Port udp 500,1194,4500 / tcp 443,992,1194,5555
vpn_ADD_DEL ;;
ssh) # SSH Port tcp 22
ssh_ADD_DEL ;;
http) # HTTP Port tcp 80
http_ADD_DEL ;;
smtp) # SMTP Port tcp 25
smtp_ADD_DEL ;;
*)
echo "ERR_F0080 $SRVNAME service NOT FOUND."
exit 1 ;;
esac
done < $IPTFILE
SEQ=`printf "%03d" ${CUR}`
ENDM1="${IPTFILE} iptables $IPARAM COUNT=${SEQ} END." >> $LOGF
ENDM2="LOGFILE: $LOGF" >> $LOGF
ENDM3="END: $PROGNAME at `date`" >> $LOGF
if [ ! -z $ARG_V ]; then
echo $ENDM1
echo $ENDM2
echo $ENDM3
fi
# end of ipt_ALLOW.sh
SoftEtherのログserver_log/の調査
数十万ラインのアクセスを試みる怪しいIPアドレスが出現することは、もうなくなったが、どんなログで、どこから忍び寄って来ていたのか調べてみた。
# cd /usr/local/vpnserver/server_log
#(1) もっとも行数多いログ・ファイルは?
# wc -l vpn_2021*log | sort -nr | sed -n 2,2p
532919 vpn_20210517.log
#(2) どんな行がたくさん出現するか?
# cat vpn_20210517.log|awk '{print $3,$4}'|cut -c -16|sort|uniq -c|sort -nr|head -12
318204 IPsec IKE
214457 IPsec Client
61 Connection "CID-
59 The connection
59 On the
59 For the
10 OpenVPN Module:
8 OpenVPN Session
2 SSL communicatio
#(3) "IPsec IKE" "IPsec Client"が多い
# grep "IPsec IKE" vpn_20210517.log|head -1
2021-05-17 00:10:26.541 IPsec IKE Session (IKE SA) 269587 (Client:
88330) (3.6.XXX.YYY:33277 -> 192.168.0.8:4500): A new IKE SA (Main
Mode) is created. Initiator Cookie: 0xC9832C40B1D51852, Responder
Cookie: 0x7FC03F33EC4C6452, DH Group: MODP 1024 (Group 2), Hash
Algorithm: SHA-1, Cipher Algorithm: 3DES-CBC, Cipher Key Size: 192
bits, Lifetime: 4294967295 Kbytes or 1 seconds
# grep "IPsec Client" vpn_20210517.log|head -1
2021-05-17 00:10:26.531 IPsec Client 88330 (3.6.XXX.YYY:33277 ->
192.168.0.8:4500): A new IPsec client is created.
#(4) IPアドレス抜き出しスクリプト
# cat ipa_read.pl
# !/usr/bin/perl
use strict;
use warnings;
my $lc=0;
while (my $line = <STDIN>){
chomp($line);
$lc++;
if($line =~ /\b((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\b/) {
print "$1\n";
}
}
print STDERR "LINE COUNT = $lc\n";
#(5) IPアドレスの集計 上位3つ
# cat vpn_20210517.log|./ipa_read.pl|sort|uniq -c|sort -nr|head -3
LINE COUNT = 532919
197438 3.6.XXX.YYY
125396 5.254.XX.ZZZ
23868 18.221.YYY.ZZZ
#
コメント#(5) IPアドレスの集計で、19万回,12万回も探られて侵入しようと試みていると思われる。サーバを立てたなら、不正アクセスを監視して情報漏えいや踏み台にされることのないように注意したい。