はじめに
FreeBSDで blacklistd
が導入されて早3年1。全く新しく環境構築を行う機会を得たので、この際だから乗り換えてみようと検証してみた。
結論から言うと微妙かな。半日検証してみての結論なので判断が早すぎかも知れないけど。とは言え定石的なものは見えたのでメモがてら残しておく。とりあえず長期的に運用してみて判断かな。
検証環境
- OS: FreeBSD(
12.2-R
) - ファイアウォール: IPFW2バックエンド
- サーバーソフトウェア: SSHd(
OpenSSH_7.9p1, OpenSSL 1.1.1h-freebsd 22 Sep 2020
)3
設定項目
- IPFWの設定
-
blacklistd
の設定 - SSHdの設定
IPFWの設定
取り急ぎIPFWを使用「していない」ものとして、ゼロからセットアップする。既にIPFWを運用中で、そのルールの中に入れたい場合は、本説明を参考に組み込んで欲しい。
/etc/rc.conf
/etc/rc.conf
に下記の設定を追加する。
firewall_enable="YES"
firewall_type="/etc/ipfw.conf"
あとはお好みで firewall_quiet="YES"
を加えてもいい。
また色々と解説しているサイトを見てると firewall_type="OPEN"
を指定してる例もあるが、それでも十分ではある。今回はそれらの半歩先ゆく設定ということで。
/boot/loader.conf
/boot/loader.conf
に下記の設定を追加する。
net.inet.ip.fw.default_to_accept="1"
本設定後再起動するまでは、下記コマンドを実行しておくこと。
kenv net.inet.ip.fw.default_to_accept="1"
この設定はファイアウォールのデフォルトルール(ルール番号65535)を「全てのアクセスを許可する」というものである。本設定はIPFW有効後には変更できないので注意。
一応IPFWが有効になった直後にアクセスできなくなることを防ぐための設定だが、ポリシー上分かって設定してるなら無視してもよい。
以後の説明ではこの設定があることを前提に話を進める4。
/etc/ipfw.conf
add 1 check-state :default
add 3000 allow tcp from any to any 22 in via インターフェース名 setup keep-state :default
ラベル(:default
)はサービス毎に定義してもいいかもしれないけど、そこまでのファインチューニングは不要でしょう。
このファイルの中身を空っぽにすれば firewall_type="OPEN"
と同等になる4。
- ルール番号1: 後に指定する
keep-state
のルールで許可した通信を【最速】処理する(以後のルールの解釈をさせない)。 - ルール番号3000: 指定された「インターフェース名」に「in」してくる、SSH(ポート22)にセットアップパケット(TCP SYN)をステートを維持してアクセスを許可する5。
ルール番号1はともかく、ルール番号3000なのは後に説明する。また、blacklistd
が要求するIPFWのルールについても、この時点では取り上げない。
blacklistd
の設定
/etc/rc.conf
/etc/rc.conf
に下記の設定を追加する。
blacklistd_enable="YES"
/etc/ipfw-blacklist.rc
/etc/ipfw-blacklist.rc
の中身はとりあえず空でもいいので、ファイルを作成する6。
touch /etc/ipfw-blacklist.rc
この設定ファイルは /usr/libexec/blacklistd-helper
というシェルスクリプトより参照され、ipfw_offset
という設定が行える。この設定はデフォルトで 2000
と定義されている。
blacklistd
ではIPFWルール番号「2000+ポート番号」にアクセス不許可(パケットをドロップさせる)のルールを追加する7。よってSSHd(ポート22)の場合、ルール番号は2022となる。
また拒否するIPアドレステーブルとして portポート番号
という名前のテーブルが使用される。SSHd(ポート22)の場合、テーブル port22
という名前になる7。
# ipfw show
:
02022 0 0 deny tcp from table(port22) to any 22
:
# ipfw table port22 list
/etc/blacklistd.conf
いよいよ本機能が期待されるアタック避けの設定である。どのようにチューニングするかが腕の見せ所。
取り急ぎデフォルト設定をベースに解説する。問題を限定するために、SSHdに限定する。
# $FreeBSD: releng/12.2/usr.sbin/blacklistd/blacklistd.conf 336977 2018-07-31 16:39:38Z brd $
#
# Blacklist rule
# adr/mask:port type proto owner name nfail disable
[local]
ssh stream * * * 3 24h
ftp stream * * * 3 24h
smtp stream * * * 3 24h
submission stream * * * 3 24h
# 6161 stream tcp6 christos * 2 10m
* * * * * 3 60
# adr/mask:port type proto owner name nfail disable
[remote]
# 129.168.0.0/16 * * * = * *
# 6161 = = = =/24 = =
# * stream tcp * = = =
[local]
と [remote]
本設定ファイルは [local]
と [remote]
のスタンザ(節)に分かれる。マニュアルによればそれぞれ以下の役割となっている。
-
[local]
スタンザ(節)はデフォルト設定を記述する。 -
[remote]
スタンザ(節)はカスタム(例外)設定を記述する。
そこで [local
] スタンザ(節)の ssh stream * * * 3 24h
に注目する。
ssh stream * * * 3 24h
という設定
ロケーション | ソケット型 | プロトコル | オーナー | 名前 | 失敗回数 | 無効期間 |
---|---|---|---|---|---|---|
ssh |
stream |
* |
* |
* |
3 |
24h |
-
*
: デフォルトまたはワイルドカード一致を意味する。 -
=
:[local]
の設定を踏襲する。 - ロケーションは
[<アドレス>|<インターフェース>][/<マスク値>][:<ポート>]
という書式89で記述する。- 「アドレス」はIPv4の場合は数字表記(
XXX.XXX.XXX.XXX
)で、IPv6の場合は[]
にくくられた数字表記([XXXX:XXXX::XXXX]
)である。 - 「インターフェース」はインターフェース名(
re0
、vmx0
等)。 - 「マスク値」は数字表現(0~32または0~128)。
- 「ポート」はポート番号ないしはポート名。ポート名は
/etc/services
をもってポート番号を解釈する。
- 「アドレス」はIPv4の場合は数字表記(
- ソケット型は「
stream
」、「dgram
」が指定可能である。 - プロトコルは「
tcp
」、「udp
」、「tcp6
」、「udp6
」が指定可能である。 - ソケットストリーム型とプロトコルは基本的10に「
stream *
」と指定するのが無難かな。 - オーナーはイベントを報告してくるデーモン(ここではSSHd)の実行ユーザー名(またはユーザーID)である。
- 名前はパケットフィルター規則の名前ですが、IPFWでは使用してない(NPFとPFだけ)ので無視でいいです。
- 失敗回数はアクセスがブロックされる前に試行した失敗した回数である。
*
の場合無限回許される。 - 無効期間は
*
(永遠)または時間(単位が無い場合は秒数)で、単位としては「s
」(秒)、「m
」(分)、「h
」(時)、「d
」(日)が指定できる。
アロー(許可)リストの作成
[remote]
スタンザ(節)に #129.168.0.0/16 * * * = * *
という例があるが、こういう形(もちろんコメントインで)でトラステッドネットワークからのアクセスに対し、blacklistd
がアクセス拒否しないように設定する。
特にコンソールにアクセスできないような環境の場合、間違ってフィルター登録され、24時間もアクセスできないとなると被害が大きいこともあり、注意深く設定することが要求される。
SSHdの設定
カジュアルに設定するなら /etc/rc.conf
に下記の設定をする。
sshd_flags="-o UseBlackList=yes"
運用に問題無いようであれば、/etc/ssh/sshd_config
に反映する。
--- /etc/sshd_config.orig 2020-12-17 08:21:52.421625000 +0900
+++ /etc/sshd_config 2020-12-26 16:40:21.836724000 +0900
@@ -104,7 +104,7 @@
#MaxStartups 10:30:100
#PermitTunnel no
#ChrootDirectory none
-#UseBlacklist no
+UseBlacklist yes
#VersionAddendum FreeBSD-20200214
# no default banner path
運用開始
再起動すれば全て立ち上がるといえば立ち上がるのだけど、設定ミスがあったりすると目も当てられないこともあり、手動で立ち上げる手順を示す。
IPFWの起動
service ipfw start
起動直後は ipfw show
で設定確認すればOK。
# ipfw show
00001 0 0 check-state :default
00100 0 0 allow ip from any to any via lo0
00200 0 0 deny ip from any to 127.0.0.0/8
00300 0 0 deny ip from 127.0.0.0/8 to any
00400 0 0 deny ip from any to ::1
00500 0 0 deny ip from ::1 to any
00600 0 0 allow ipv6-icmp from :: to ff02::/16
00700 0 0 allow ipv6-icmp from fe80::/10 to fe80::/10
00800 0 0 allow ipv6-icmp from fe80::/10 to ff02::/16
00900 0 0 allow ipv6-icmp from any to any icmp6types 1
01000 0 0 allow ipv6-icmp from any to any icmp6types 2,135,136
03000 0 0 allow tcp from any to any 22 in via インターフェース setup keep-state :default
65535 0 0 allow ip from any to any
なおルール番号100から1000までは自動挿入である1112。
blacklistd
の起動
service blacklistd start
SSHdの再起動
service sshd restart
モニタリング
稼動状態をモニタリングするのであれば ipfw show
と ipfw table port22 list
を実行することで確認できる。
IPFWの場合「ルール番号」「パケット数」「バイト数」「ルール」という並びになるので、SSH接続があればルール番号3000のパケット数(例では578)を数えればよい。実際にアクセス拒否した回数はルール番号2022となる(例では216)。
# ipfw show
:
02022 216 12960 deny tcp from table(port22) to any 22
03000 578 99786 allow tcp from any to any 22 in via インターフェース setup keep-state :default
65535 125299 44785979 allow ip from any to any
実際にアクセス拒絶となったIPアドレスは下記の通り確認できる。
# ipfw table port22 list
XXX.XXX.XXX.XXX/32 0
XXX.XXX.XXX.XXX/32 0
blacklistd
が認識している状態(ブロック状態についても含む)は blacklistctl
コマンドを使用して確認する。
# blacklistctl dump -a
address/ma:port id nfail last access
XXX.XXX.XXX.XXX/32:22 OK 2/2 YYYY/mm/Dd HH:MM:SS
XXX.XXX.XXX.XXX/32:22 OK 2/2 YYYY/mm/dd HH:MM:SS
マニュアルにはヘッダーの意味が書いてないため調べたところ、id
は無視していいです(NPFのみ使用)。今のところ「OK
」と「」(ヌル)しか見てません。
チューニングポイント
サンプルでは3回間違えると24時間アクセスを拒否する設定になっているが、これを2回にして1分程度で解除するのも面白い。
# Blacklist rule
# adr/mask:port type proto owner name nfail disable
[local]
ssh stream * * * 2 60s
一つには本当に間違ってアクセスしてしまった時のリカバリが1分で済む点と、アタックはそう長時間に渡って行われないためである。
実際に試してみるとフィルタリング能力としては、半日様子見て高々0.5%未満くらいである。なお24時間設定にして、2時間程度様子を見た場合は高々16%未満なので、短期間にアクセスして、またしばらくしてアクセスするといったパターンなのが分かる。
正直短期間のペナルティと長期間のペナルティを分けて設定できるようにして欲しいところ。 orz
また他のツールと違い、ポートスキャンレベルでは失敗にアタックと解釈されないため、鍵認証を必須にしているような環境では、ある程度回数を絞り込める余地はある。それでも流石に1回までしか許さないのはキツイと思うけど。
また拒否時間も24時間は長すぎる。確実に解除問い合せが来るので、長くても3分程度に抑えるべきであると考える。
よくある質問とその答え
Q.半歩先行く設定って?他の説明と何が違うの?
A.ステートフルインスペクション機能により、SSHのTCPセッションが生きてる間は、blacklistd
の登録の影響を受けない点が違います。
実験中に誤って自分が接続しているIPがブロックされても、今生きてるSSHのセッションが生きてる間はリカバリできる余地があります。
また通信を遮断するにしても、TCP RSTパケットを送信しきれることから、TCPセッションタイムアウトを待たなくていいメリットがあります。
これは逆に、攻撃者側にとってもメリットを有することでもあるのですが、そもそも認証に失敗すればTCPセッションが切れることから、言うほどのメリットは無いです。むしろTCPセッションが確実に切れるメリット(リソース的に)の方が高いです。
Q.「微妙」という評価はなんで? blacklistd
はよろしくない?
A.この手のツール13としては悪くないと思います。むしろ良い。
まず何よりも、従来のツールが各種ログを拾いながら、ブロックリストを生成するのと違い、各種サーバーソフトウェアが blacklistd
に即座に通知する仕組みであるため、極めて迅速14にブロックリストが作られます。この点が他のツールとは大いに違うところになります。
それ故に期待値が上がってしまっているのですが、いわゆるログインエラーの類いについては上手く機能しています。ポートスキャンレベルの攻撃?は上手く拾えていません。もちろん拾えてない原因はSSHd側にあるのですが、この結果、あまりブロックしてる感じはしません。
またSSHdが全面的に悪いか、というとそうでも無く、マニュアルによれば、認証結果(成功・失敗)しか拾わないようです。
まあポートスキャンレベルで…という話はあるのでしょう。実際、ssh-keyscan
や各種監視ツールではポートスキャンレベルといえばそうではありますが。
もう一つ期待外れな点としては、認証成功の記録を活用していない点です。認証成功をもってペナルティに対するガードとして機能してくれれば、アローリストの作成的にも嬉しいのですが。
Q.ブラックリスト…ホワイトリスト…
A.君のような勘の良いry。この変更は極めて影響が大きいので、メジャーバージョンアップ時(13-R)に変わるのではないかな、とみてます。
Q.ファイアウォールは必要ですか?
A.blacklistd
そのものはフィルタリング処理はしないです。その手の処理はファイアウォールにアウトソースする仕組みになっています。
また対応しているファイアウォールも、IPFW、NPF、PF、IPFに限られています。優先順位も下記の通りとなります。
-
/etc/ipfw-blacklist.rc
というファイルがある場合、IPFWをバックエンドに使う。 -
/etc/npf.conf
というファイルがある場合、NPFをバックエンドに使う。 -
/etc/pf.conf
というファイルがある場合、PFをバックエンドに使う。 -
/etc/ipf.conf
というファイルがある場合、IPFをバックエンドに使う。
この辺りは /usr/libexec/blacklistd-helper
とマニュアルの -C
オプションの記述を参考に、ガチカスタマイズすることは可能です15。
繰り返しますが、IPFW、NPF、PF、IPFしか対応してないのはベースシステムの制限であって、他のファイアウォールの選択を拒絶するものではありません。それ以外のファイアウォール(外部を含む)を使用したい場合は、上手く作り込んでください。
Q.ipfw_offset
とか 2000
とか 3000
ってなんなんだよ。
A.IPFWのルール番号の指定です。blacklistd
に対応を謳ってるサーバーが対象とするプロトコルはおおよそ3桁ポート番号なので、ipfw_offset
に対して+1000した値で許可設定を入れてみました。ある意味ルール番号2999というのもありですが。
blacklistd
がルールを挿入する位置とバランスを取ってルール番号を決めてください。
逆にガッチガチに制御したい向けは、blacklistd
に -C
オプションを指定して、完全に自前で運用するしか無いです。
Q.やっべ、自分の接続元IPアドレスがブロックリストに登録されてしまった!
A.大丈夫だ。問題無い。現在 blocklistctl
に解除する機能が無いので ipfw table port22 delete XXX.XXX.XXX.XXX/32
とコマンドを打って消そう!
なお拒否期限が残ってる間は blacklistd
を再起動する度に登録されるのでよろしく!16
Q.何か改善点ありますか?
色々課題(blacklistctl
の充実など)はあると思いますが、最低でもベイジアンフィルター、ナウでヤングには機械学習の実装が期待されるかと。
とりあえず仕組みは簡単なので、LL言語でプロトタイピングするのが吉かな。
参考文献
- blacklistd を使ってみる
- Blacklistd (FreeBSDハンドブック)
- blacklistd.conf(5)
- blacklistctl(8)
- blacklist(3)
-
2020年末現在。2017年07月26日リリースの11.1-Rが初出。 ↩
-
実際のフィルタリングにはIPFW、NPF、PF、IPFのどれかをバックエンドで指定する。 ↩
-
取り急ぎSSHdのみで。他にも
blacklistd
対応謳ってるサーバーソフトウェアあるけど、今回は詳細追わず。 ↩ -
日本語でOK。 ↩
-
設定の意味とファイアウォールバックエンドの選択については後述する。 ↩
-
指定不可能な組み合わせ(
re0/24
など)があるみたいだけど未調査。 ↩ -
ssh
がポート名というのは書式に合わない気がす。:
はどうした。:
は。微妙にマニュアルの表現が甘い気が。 ↩ -
原則とは言わない。
blacklistd
に対応したサーバーソフトウェアとプロトコル考えると、大抵はTCP/IPだとは思うので。 ↩ -
/etc/rc.firewall
由来。setup_loopback
とsetup_ipv6_mandatory
による。 ↩ -
強制的に入るので順番を変えたい等、何かしら調整したいなら
/etc/ipfw.conf
の最初に-f flush
入れるしかない。 ↩ -
拒絶要件に達した直後、次の接続ではフィルタリングされているくらい早い。 ↩
-
具体的なカスタマイズ例(外部ファイアウォールへのアウトソース)を考えてみたのだけど、意外と大変そうなのでアイデアのみで。 ↩