SIP-Fail2ban

提供: VoIP-Info.jp
移動先: 案内検索

IAXでのFail2banはIAX-Fail2banを参照してください。

fail2ban

ログファイルとiptablesを利用したファイアウォールの一種。Brute Forceアタックの対策に使いやすい。

http://www.fail2ban.org/
http://sourceforge.net/projects/fail2ban/

iptablesおよびpythonを必要とします。最新のFail2banではAsteriskにも対応しています。

CentOS

CentOSではepelからyumでインストールすることが可能です。

yum install -y epel-release
yum --enablerepo=epel -y install fail2ban

yumでインストールした最新のFail2BanはAsteriskのログフォーマットを変更し、ローカル設定ファイルを作成するだけで使用することができます。
まずAsteriskのlogger.confを修正し

[general]
; Customize the display of debug message time stamps
; this example is the ISO 8601 date format (yyyy-mm-dd HH:MM:SS)
; see strftime(3) Linux manual for format specifiers
dateformat=%F %T

dateformatの箇所のコメント(;)を外して有効にし、Asteriskを再起動します。
/etc/fail2ban/に以下の内容のjail.localというファイルを作成します。ファイルがある場合には修正します。

[DEFAULT]
ignoreip =
backend = polling
bantime = 3600  ; 1hour
maxretry= 5
usedns = no

[asterisk]
enabled = true

fail2banを起動すると監視が始まります。


起動の確認

iptables -Lでiptablesの状態を確認するとfail2banでbanされたIPアドレス等が確認できます。

Chain f2b-asterisk-tcp (1 references)
target     prot opt source               destination
REJECT     all  --  46.17.42.180         anywhere            reject-with icmp-port-unreachable
RETURN     all  --  anywhere             anywhere

Chain f2b-asterisk-udp (1 references)
target     prot opt source               destination
REJECT     all  --  46.17.42.180         anywhere            reject-with icmp-port-unreachable
RETURN     all  --  anywhere             anywhere

デフォルトではban時間は1時間です。

INVITEによるBrute force攻撃への対策

REGISTERメッセージによる攻撃以外に、INVITEによるBrute force攻撃も確認されています。

この攻撃時に出力されるログメッセージは以下のようなものになります。

Failed to authenticate user "Anonymous" <sip:anonymous@192.168.1.2>;tag=as105e401c 

このログメッセージの攻撃元IPアドレスが、FROMヘッダに記載されているIPアドレスになっています。

このままでは、NAT配下のサーバーからの攻撃や、FROMヘッダが偽装された場合にfail2banで対応することができません。

そこで、Asteriskへパッチを当てて、実際の攻撃元IPアドレスを表示するように修正します。

Asteriskへパッチを当てる

次のようなパッチをAsteriskに適用します。

このパッチはAsterisk-1.4.40を対象にしていますが、1.6系、1.8系にも同様の修正で対応できます。

--- asterisk-1.4.40.orig/channels/chan_sip.c    2011-01-05 02:11:48.000000000 +0900
+++ asterisk-1.4.40/channels/chan_sip.c 2011-03-10 17:59:26.000000000 +0900
@@ -15456,7 +15456,7 @@
                                ast_log(LOG_NOTICE, "Sending fake auth rejection for user %s\n", get_header(req, "From"));
                                transmit_fake_auth_response(p, SIP_INVITE, req, XMIT_RELIABLE);
                        } else {
-                               ast_log(LOG_NOTICE, "Failed to authenticate user %s\n", get_header(req, "From"));
+                               ast_log(LOG_NOTICE, "Failed to authenticate user %s (%s:%d)\n", get_header(req, "From"), ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
                                transmit_response_reliable(p, "403 Forbidden", req);
                        }
                        p->invitestate = INV_COMPLETED;

こちらは Asterisk-1.8.23.0 用です。Asterisk-11.5.1 でもほぼ同じコードが使えます。

--- asterisk-1.8.23.0/channels/chan_sip.c.orig  2013-08-02 11:41:03.233638321 +0900
+++ asterisk-1.8.23.0/channels/chan_sip.c       2013-12-06 14:51:08.698990909 +0900
@@ -22673,7 +22673,7 @@
                        return 0;
                }
                if (res < 0) { /* Something failed in authentication */
-                       ast_log(LOG_NOTICE, "Failed to authenticate device %s\n", get_header(req, "From"));
+                       ast_log(LOG_NOTICE, "Failed to authenticate device %s (%s)\n", get_header(req, "From"), ast_sockaddr_stringify(addr));
                        transmit_response(p, "403 Forbidden", req);
                        sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
                        return 0;
@@ -23334,7 +23334,7 @@
                        goto request_invite_cleanup;
                }
                if (res < 0) { /* Something failed in authentication */
-                       ast_log(LOG_NOTICE, "Failed to authenticate device %s\n", get_header(req, "From"));
+                       ast_log(LOG_NOTICE, "Failed to authenticate device %s (%s)\n", get_header(req, "From"), ast_sockaddr_stringify(addr));
                        transmit_response_reliable(p, "403 Forbidden", req);
                        p->invitestate = INV_COMPLETED;
                        sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
@@ -25164,7 +25164,7 @@
                p->lastinvite = seqno;
                return 0;
        } else if (auth_result < 0) {
-               ast_log(LOG_NOTICE, "Failed to authenticate device %s\n", get_header(req, "From"));
+               ast_log(LOG_NOTICE, "Failed to authenticate device %s (%s)\n", get_header(req, "From"), ast_sockaddr_stringify(addr));
                transmit_response(p, "403 Forbidden", req);
                sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
                ast_string_field_set(p, theirtag, NULL);
@@ -25384,7 +25384,7 @@
                if (res == AUTH_CHALLENGE_SENT) /* authpeer = NULL here */
                        return 0;
                if (res != AUTH_SUCCESSFUL) {
-                       ast_log(LOG_NOTICE, "Failed to authenticate device %s\n", get_header(req, "From"));
+                       ast_log(LOG_NOTICE, "Failed to authenticate device %s (%s)\n", get_header(req, "From"), ast_sockaddr_stringify(addr));
                        transmit_response(p, "403 Forbidden", req);

                        pvt_set_needdestroy(p, "authentication failed");

Asterisk 11.23.1用パッチです。

--- channels/chan_sip.c.orig    2016-09-09 01:28:35.000000000 +0900
+++ channels/chan_sip.c 2016-10-28 23:26:38.985774935 +0900
@@ -18751,7 +18751,7 @@ static void receive_message(struct sip_p
                        return;
                }
                if (res < 0) { /* Something failed in authentication */
-                       ast_log(LOG_NOTICE, "Failed to authenticate device %s\n", sip_get_header(req, "From"));
+                       ast_log(LOG_NOTICE, "Failed to authenticate device %s (%s)\n", sip_get_header(req, "From"), ast_sockaddr_stringify(addr));
                        transmit_response(p, "403 Forbidden", req);
                        sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
                        return;
@@ -24963,7 +24963,7 @@ static int handle_request_options(struct
                        return 0;
                }
                if (res < 0) { /* Something failed in authentication */
-                       ast_log(LOG_NOTICE, "Failed to authenticate device %s\n", sip_get_header(req, "From"));
+                       ast_log(LOG_NOTICE, "Failed to authenticate device %s (%s)\n", sip_get_header(req, "From"), ast_sockaddr_stringify(addr));
                        transmit_response(p, "403 Forbidden", req);
                        sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
                        return 0;
@@ -25798,7 +25798,7 @@ static int handle_request_invite(struct
                        goto request_invite_cleanup;
                }
                if (res < 0) { /* Something failed in authentication */
-                       ast_log(LOG_NOTICE, "Failed to authenticate device %s\n", sip_get_header(req, "From"));
+                       ast_log(LOG_NOTICE, "Failed to authenticate device %s (%s)\n", sip_get_header(req, "From"), ast_sockaddr_stringify(addr));
                        transmit_response_reliable(p, "403 Forbidden", req);
                        p->invitestate = INV_COMPLETED;
                        sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
@@ -27788,7 +27788,7 @@ static int handle_request_publish(struct
                p->lastinvite = seqno;
                return 0;
        } else if (auth_result < 0) {
-               ast_log(LOG_NOTICE, "Failed to authenticate device %s\n", sip_get_header(req, "From"));
+               ast_log(LOG_NOTICE, "Failed to authenticate device %s (%s)\n", sip_get_header(req, "From"), ast_sockaddr_stringify(addr));
                transmit_response(p, "403 Forbidden", req);
                sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
                ast_string_field_set(p, theirtag, NULL);
@@ -28003,7 +28003,7 @@ static int handle_request_subscribe(stru
                if (res == AUTH_CHALLENGE_SENT) /* authpeer = NULL here */
                        return 0;
                if (res != AUTH_SUCCESSFUL) {
-                       ast_log(LOG_NOTICE, "Failed to authenticate device %s for SUBSCRIBE\n", sip_get_header(req, "From"));
+                       ast_log(LOG_NOTICE, "Failed to authenticate device %s (%s) for SUBSCRIBE\n", sip_get_header(req, "From"), ast_sockaddr_stringify(addr));
                        transmit_response(p, "403 Forbidden", req);

                        pvt_set_needdestroy(p, "authentication failed");


パッチを当てて、Asteriskをコンパイルし直し、再起動します。

すると、先ほどの攻撃時のログは以下のように出力されるようになります。

Failed to authenticate user "Anonymous" <sip:anonymous@192.168.1.2>;tag=as105e401c (123.45.67.89:5060)

ログの( )内に攻撃元の実IPアドレスが表示されるようになり、これを元にfail2banで攻撃を検知することができます。

fail2banへ設定を追加

修正したログに合わせたフィルタ設定をfail2banに追加します。

/etc/fail2ban/filter.d/asterisk.conf の failregex の項目に以下を追加します。

NOTICE.* .*: Failed to authenticate user .* \(<HOST>:.*\)

フィルタ追加後、fail2banを再起動し設定完了です。

securityログの設定

/etc/asterisk/logger.confに

[logfiles]
security => security

を追加(コメント解除)して、/var/log/asterisk/securityにできるログをfail2banに監視させる方法もあります。

ステータス確認

fail2ban-client コマンドで問い合わせるのがいいです。

# コマンド一覧
fail2ban-client

# 有効なjail一覧
fail2ban-client status

# jail名"asterisk"のステータス 
fail2ban-client status asterisk

# 手動でban/unban
fail2ban-client set asterisk banip 11.22.33.44
fail2ban-client set asterisk unbanip 11.22.33.44

banされてることになっていても、actionが正しく書けていないと実際にはfirewalldやiptablesなどに反映されず、攻撃されっぱなしもあり得るので、挙動確認が大事です。
fail2ban-server コマンドは直接叩いてはいけないようです。
または、ログファイル /var/log/fail2ban.log を見てもいいです。

CentOS 8

Asterisk16, CentOS 8でも同様にepelからdnfでインストールすることが可能です。
CentOS 8では、iptablesではなく、nftables及びfirewalldが使用されていますが、問題なく対応しています。

dnf install -y epel-release
dnf --enablerepo=epel -y install fail2ban fail2ban-systemd

Asteriskのlogger.confの変更、/etc/fail2ban/jail.localの作成は上記、以前のCentOSと同様にしてください。
しかし、2020年8月7日時点では、ここでfail2banを起動しても

2020-08-07 16:39:54,981 fail2ban.transmitter    [58697]: WARNING Command ['server-stream', [['set', 'syslogsocket', 'auto']
(略)
Received ValueError('Action firewallcmd-rich-rules already exists',)
2020-08-07 16:39:54,981 fail2ban                [58697]: ERROR   NOK: ('Action firewallcmd-rich-rules already exists',)

というエラーが出て動きません。さしあたりの回避策として/etc/fail2ban/jail.d/00-firewalld.confを次のように変更します。

(略)
[DEFAULT]
banaction = firewallcmd-ipset
banaction_allports = firewallcmd-ipset
#banaction = firewallcmd-rich-rules[actiontype=<multiport>]
#banaction_allports = firewallcmd-rich-rules[actiontype=<allports>]

これでfail2banを起動すると監視が始まります。

systemctl enable fail2ban
systemctl start fail2ban

稼働状態の確認としては、上記fail2ban-clientコマンドまたは、次のコマンドが使用できます。

ipset --list

Asterisk 16及びpjsipを使用していると、Asteriskにパッチを当てる等しなくてもINVITEによるBrute force攻撃も防御できるようです。