事務所に8個の固定IPを引き、FWをとりつけ、サーバを立てており、メールやWEBをホスティングではなく、自前で運用しています。
FWを建ててはあるが、サービス提供ポートは開いているので、サービスレベルのセキュリティはそれなりに万全にする必要があると考えています。
まずは、基本中の基本ですが、アプリケーションが吐くログを監視して、不正アクセスの兆候をつかむように努力しています。
asteriskもこのなかで運用していますが、国際電話も発着信する必要があるのでPSTN側規制をかけることができずにおります。
したがって、アカウント&パスワードを強化するしか対策はないのですが、別システムからワンタイムアカウント&パスワードを発行するような強固な作りとしてしまうと、その都度SIPクライアントに登録しなおす必要があります。さすがに、自動的に追従してくれるSIPクライアントってのはありませんので、利便性とを量りにかけ、長文のアカウント&パスワードとすることにしました。
使用しているアカウント&パスワードは、日本人的思考で作成しているので、欧米的辞書攻撃には少なからず耐えられるとは思っていますが、不正アクセスをのさばらせておくのは許せません・・・
そこで、不正アクセスを検知し、一定時間アクセスをブロックする仕組みを考えてみました。
asteriskの認証が失敗すると、asteriskのログに痕跡が残ります。
痕跡を追跡して、不正アクセスしたIPアドレスを一時的にブロックするようにシステムを作りこんでみました。
asteriskのログを眺めてみますと、認証が失敗した場合に以下のようなログを見つけることができます。
[date] NOTICE[x] chan_sip.c: Registration from '"!"' failed for '220.90.137.98'- No matching peer found
自分が建てているasteriskでは、LANG=Cで動いているようで、[date]は"Sep 8 17:00:00"のように表示されます。今まで最高秒間20件程度の辞書攻撃をされたことがあり、そうすると1分間で1200件もの認証エラーが記録されたことがありました。
したがって、1分ごとにログを監視することにしました。
Linuxの場合は、cronにより分刻みでスクリプトを実行することができます。
ログを時刻と認証エラー文字列でgrepし、取得されたIPをユニークソートして、不正アクセスしたIPを特定して、OSレベルでブロックするようにしてみます。
OSレベルでブロックしようとするには、iptablesで実現します。
自分んとこのasterisk稼動サーバの構成は、eth0側がLAN、eth1がグローバルIPとなっており、グローバルIP側には基本となるFWポリシーを設定してあります。
iptablesによるパケットフィルタ設定は、iptablesコマンドを1つ1つ実行していくシェル方式がネット検索で引っかかることが多いと思います。ただし多数の設定を入れる場合は、テーブルを作成しておき、一括で読み込ませたほうが早く設定させることができます。
(テーブルに作成する具体的な方法については割愛いたします)
以下、asteriskが動いているサーバの"/etc/sysconfig/iptables"の内容です。(一部省略しています)
*nat
:PREROUTING ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
#
-A POSTROUTING -o eth1 -j MASQUERADE
COMMIT
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:ADDRESSFILTER - [0:0]
:SIPFILTER - [0:0]
:DROPROLE -[0:0]
#ADDRESS FILTER
-A INPUT -j ADDRESSFILTER
-A FORWARD -j ADDRESSFILTER
#SIP FILTER
-A INPUT -j SIPFILTER
-A FORWARD -j SIPFILTER
-A SIPFILTER -m udp -p udp --dport ! sip -j RETURN
#DROP
-A DROPROLE -j DROP
上記でIPFILTERというテーブルを定義しています。このテーブルに不正アクセスしてきたIPを追加で設定し、一定期間パケットをDROPするようにします。
SIPFILTERテーブルに192.168.0.1からのパケットをDROPする内容を追加するコマンドは以下のとおりです。
/sbin/iptables -A SIPFILTER -j DROP -s 192.168.0.1
本来は、udp sipのみをDROPするのが筋でしょうが、当該IPは悪意を持ってアクセスしているのが明白ですので、全パケットをDROPしてもいいと考えます。
udp sipのみをブロックしたい場合のコマンドは、"man iptables"でお調べください。
悪意を持った・・・と書きましたが、大多数はbotのことが多いと思います。設定しっぱなしだと都合が悪いので、一定時間が過ぎたら設定した内容を落とすようにします。
すでに追加されているDROPのIPを削除するコマンドは以下のとおりです。
/sbin/iptables -D SIPFILTER -j DROP -s 192.168.0.1
"-A"で設定した内容とまったく同じ内容で"-D"とするのがコツです。同じにしないと、「設定されていないよん」というエラーが出て、削除されません。
アクセスブロックする時間と、ブロックしているIPを管理する必要があります。ファイルに格納するのが一般的のようですが、管理のしやすさ(動的に変更確認できる)と、セキュアに外部から容易に確認できる方法を提供するために、LAN内にあるDBサーバに情報を格納してみました。
利用ライセンスと構築のしやすさから、今回はPostgreSQLで実現させています。
以下asteriskというDBを作成しました。
CREATE DATABASE asterisk
WITH OWNER = postgres
ENCODING = 'UTF8'
CONNECTION LIMIT = -1;
DBを作成しましたら、不正アクセスを検知した時刻を格納するテーブル(attack_ip)とブロック中のIPを格納するテーブル(drop_ip)、そしてブロックしている時間を格納するテーブル(interval_table)を作成します。
CREATE TABLE attack_ip
(
ipaddress text NOT NULL,
start_time timestamp without time zone NOT NULL,
CONSTRAINT pk_attack_id PRIMARY KEY (ipaddress, start_time)
)
WITH (
OIDS=FALSE
);
ALTER TABLE attack_ip OWNER TO postgres;
CREATE TABLE drop_ip
(
ipaddress text NOT NULL,
start_time timestamp without time zone NOT NULL,
CONSTRAINT pk_drop_id PRIMARY KEY (ipaddress)
)
WITH (
OIDS=FALSE
);
ALTER TABLE drop_ip OWNER TO postgres;
CREATE TABLE interval_table
(
interval_hour integer NOT NULL,
CONSTRAINT interval_table_pkey PRIMARY KEY (interval_hour)
)
WITH (
OIDS=FALSE
);
ALTER TABLE interval_table OWNER TO postgres;
interval_tableには、ブロックする時間をあらかじめいれておきましょう。
※下記例では1時間としていますがお好みにより増やしてください
INSERT INTO interval_table(interval_hour)VALUES(1);
つづいて、1分ごとに動かすシェルを作成します。
他にcronに登録されている数にもよりますが、毎分1秒ないし2秒に実行されるようです。
したがって、1分前のログと現在時分のログを対象にして不正ログを取得するようにしました。
内容については割愛しますが、実装されているshの仕様とDBおよびログの構成により、適宜変更することになると思います。
#!/bin/sh
LANG=C
DBNAME="asterisk"
DBUSER="postgres"
DBHOST="192.168.0.21"
DBNAME="asterisk"
ALOG="/var/log/asterisk/messages"
d=$(date "+%b %d %H:%M")
b=$(date -d "1 min ago" "+%b %d %H:%M")
#
SQLA="SELECT ipaddress FROM drop_ip WHERE start_time + CAST((SELECT interval_hour FROM interval_table)||' hour' AS INTERVAL) < NOW()"
for a in $(psql -t -U $DBUSER -h $DBHOST -d $DBNAME -c "$SQLA");do
SQLB="DELETE FROM drop_ip WHERE ipaddress='$a'"
psql -U $DBUSER -h $DBHOST -d $DBNAME -c "$SQLB"
iptables -D SIPFILTER -j DROP -s $a
done
#
for a in $(cat $ALOG\
|grep 'failed for'\
|egrep "$d|$b"\
|sed 's/failed for/\n/'\
|grep -v NOTICE\
|sed "s/'/\n/g"\
|grep .\
|grep "[0-9]"\
|sort -u);do
SQLC="SELECT COUNT(*) FROM drop_ip WHERE ipaddress='$a'"
cnt=$(psql -t -U $DBUSER -h $DBHOST -d $DBNAME -c "$SQLC")
if [ $cnt -eq 1 ];then
SQLD="UPDATE drop_ip SET start_time = NOW() WHERE ipaddress='$a'"
psql -U $DBUSER -h $DBHOST -d $DBNAME -c "$SQLD"
else
SQLE="INSERT INTO drop_ip (ipaddress, start_time)VALUES('$a',now())"
psql -U $DBUSER -h $DBHOST -d $DBNAME -c "$SQLE"
iptables -A SIPFILTER -j DROP -s $a
fi
SQLF="INSERT INTO attack_ip (ipaddress, start_time)VALUES('$a',now())"
psql -U $DBUSER -h $DBHOST -d $DBNAME -c "$SQLF"
done
自分んとこでは、上記シェルを"/opt/tools/asteriskchk"に実行権限をつけて格納しています。
つづいて、crontabの設定です。(詳細は割愛します)
* * * * * /opt/tools/asteriskchk
自分んとこはasteriskをrootで動かしているので、rootのcrontabに作成していますが、構成により変更することになるでしょう。
なお、手動でブロックするアドレスを追加、削除するシェルも用意しておくとよいでしょう。
以下は、ブロック追加のシェル。
#!/bin/sh
DBNAME="asterisk"
DBUSER="postgres"
DBHOST="192.168.0.21"
DBNAME="asterisk"
ALOG="/var/log/asterisk/messages"
LANG=C
a=$1
cnt=$(psql -t -U $DBUSER -h $DBHOST -d $DBNAME -c "SELECT COUNT(*) FROM drop_ip WHERE ipaddress='$a'")
if [ $cnt -eq 1 ];then
psql -U $DBUSER -h $DBHOST -d $DBNAME -c "UPDATE drop_ip SET start_time = NOW() WHERE ipaddress='$a'"
else
psql -U $DBUSER -h $DBHOST -d $DBNAME -c "INSERT INTO drop_ip (ipaddress, start_time)VALUES('$a',now())"
iptables -A SIPFILTER -j DROP -s $a
fi
上記を適当なファイルに実行権限付きで格納します。
自分んとこでは、"/opt/tools/asteriskdropip"として格納しました。
手動で"192.168.0.10"のアドレスを追加する場合は以下によりコマンド打鍵してください。
# /opt/tools/asteriskdropip 192.168.0.10
つづいて、ブロック削除のシェル。
#!/bin/sh
DBNAME="asterisk"
DBUSER="postgres"
DBHOST="192.168.0.21"
DBNAME="asterisk"
ALOG="/var/log/asterisk/messages"
LANG=C
a=$1
for a in $(psql -t -U $DBUSER -h $DBHOST -d $DBNAME -c "SELECT ipaddress FROM drop_ip WHERE ipaddress = '$a'");do
psql -U $DBUSER -h $DBHOST -d $DBNAME -c "DELETE FROM drop_ip WHERE ipaddress='$a'"
iptables -D SIPFILTER -j DROP -s $a
done
※cronで実行させるためにはコマンドに対するpassが通っていることが前提
asteriskの招かざるユーザの対応に苦慮している方々の一助になれば幸いです。
上記ソースは自分のところでは動作確認をしていますが、なんら保証はなく、各人の責任においてご利用ください。
著作権は放棄していませんがご利用は自由に行ってください。ただし、出展は明らかにしてください。利用料、ご連絡は不要です。