fail2ban へWebのアクセスログ監視を追加設定(2016-12-12、修正:2018-03-17)
Webサーバやメールサーバへのしつこい攻撃を、次々にban(禁止)していきたいわけです。
既に fail2ban は導入済みだけど、もっと連続404 や already banned を減らしたいという方は
アクセスログ監視 その2 の方へお進みください。
技術的な話はさておき、不正アクセスってどんななの、止まるの?という方は、
静的フィルタをかけてみた感想 をご覧ください。
CentOS 7環境に置いてある Webサーバを DoS攻撃等からファイアウォールで守りたいので、
いつもお世話になっている fail2ban の設定を追加しました。*1
大まかな流れは以下です。
- fail2banをインストールする
私の環境ではインストール済みだったので、ここでは省略します。
(yum install や、systemctl enable 等をやることになる、かと)
- python-inotifyが無い場合、同じくインストールする。
- filter を追加する。
「/etc/fail2ban/filter.d/」にfilter ファイルを新規作成。
- jail の設定を追記する。
同じく「jail.d/local.conf」に追記、またはjail.d 配下へ新規ファイルの作成
- fail2ban を start もしくは reload する
- 無事、動くことを祈る
・・・私の場合は最後(祈る)が届かなかったので、
以下、そこの話がメインになります。
pyinotify のインストール†
後述するjailの「backend」パラメータで、ログ監視方法を選ぶのですが、
「backend = pyinotify」とする場合には事前にインストールが必要です。
後述の backend の話になりますが、
私の環境だと、polling を選べばこの事前インストールは要らなかったようです。
(各パラメータの意味は「man jail.conf」で確認できます)
何を選ぶかによって、監視の効率や性能が変わるのだと思うのですが、
まずはpyinotify でも gamin でも polling でもお好みで良いかと。
# yum install python-inotify
これで事前準備は終わったので、次は設定に入ります。
filter の設定†
私のCentOS7環境だと、コンフィグ周りは
「/etc/fail2ban/」にあります。
まずは仮で、このような設定にしています。後日調整。
# vi filter.d/apache-dos.conf
[Definition]
failregex = ^[\d:/]* <HOST>
ignoreregex =
[Init]
datepattern = %%y%%m%%d/%%H:%%M:%%S
まず、datepattern ですが、
私の場合は、apache-httpd の LogFormat の日付 %t を変えているので、
それに合わせて 日付書式datepattern を設定し直しています。(%%が二重である点に注意)
以下のようなログが出たら、datepattern を変える必要があるかもしれません。
WARNING Found a match for u
(中略)
Please try setting a custom date pattern (see man page jail.conf(5)).
If format is complex, please file a detailed issue on (以下省略)
続いて、failregex ですが、
アクセス禁止したい条件を書くので、現時点では「すべて」を拾うようにします。
(後述のjailで頻度を指定して、
1秒間に100回このfailregexにかかったらアクセス禁止、とかにします)
すべて拾うのだから、良く分からない場合は最初はもっと無難に
failregex = <HOST>
とした方が早いかもしれません。
私の環境のアクセスログだと「2016/12/12:00:00:00 127.0.0.1」というような出力なので、
それに合わせて「^[\d:/]* <HOST>」という正規表現にしていますが、
いずれにせよ、送信元(<HOST>)は指定しなければなりません。
(ちゃんとした記載例は、
他の既存の filter.d/apache-*.conf が参考になります)
jail の設定†
デフォルト値は jail.conf で、自分の設定は jail.d/ 配下に追加します。
今回はとりあえず、jail.d/local.conf ファイルへ追記してしまっています。
[apache-dos]
enabled = true
port = http,https
usedns = no
logpath = %(apache_access_log)s tail
filter = apache-dos
maxretry = 任意の数字、意味については後述
findtime = 任意の数字
bantime = 任意の数字
backend = pyinotify
enabled でフィルターを有効化します。
port で、apache が使うポートを指定。
usedns は、うちのapacheは「HostnameLookups Off」なので no にして負荷を減らします。
logpath は 変数では無くアクセスログの場所を直接書いてもOKです。
末尾の「 tail 」は man jail.conf によると、これで追記分のみを読めるらしいです。
filter は先ほど作ったフィルターファイルを指定。
「任意の数字」は要調整ですが、動作確認時は全て極端に小さい数値にした方が楽です。
findtime 秒の間に、maxretry 回のアクセスが来たら ban します。bantime 秒後に復旧。
backend というのがログ監視方式で、ものによってはパッケージのインストールが必要です。
動作確認†
fail2ban を開始(start)、あるいは再読み込み(reload)します。
うちの環境の場合は既に動いており、再読み込みするだけです。
# service fail2ban reload
動作確認としては、前述の通りに
findtime、maxretry、bantimeの「任意の数字」を小さく(3とかに)して、
「tail -f /var/log/fail2ban.log」などしつつ、
Webブラウザからアクセス&更新(F5キー連打)しましょう。
即、アクセスできなくなる筈です(ban されます)。
確認できたら、数字を適切な値に変更して再度 reload。
数字は、Webコンテンツの内容によるので、(サイト内に画像が多いと数も増える、とか)
まず一回のリクエストで何アクセスあるのか、アクセスログから確認しましょう。
最後に、必要に応じて loglevel も変えましょう。
私の場合は fail2ban.conf でloglevel が INFO に設定されていて、
ログ出力が多すぎました。
まずはどのくらい 攻撃&ban されるか様子を見て、
不味そうなら他のパラメータも含めて適宜変更・調整していきましょう。
余談ですが、
Webアクセスを止める際にブラウザにエラーメッセージが欲しい場合には、
(「只今アクセスが集中しており・・・」など表示させたい場合)
別の手を使います。apache だと、mod_dosdefector があるようです。
- メリット:エラーメッセージがあれば利用者には親切。
特に悪意が無い利用者に正しいメッセージを伝えることができる。
fail2ban だとWebブラウザがいきなり無言になるので、何が起こったのか分からない。
- デメリット:メッセージ表示自体が apache-httpdや各リソースへの負荷になる。
もし負荷が許容値を超えるなら、fail2ban 等の併用も検討が必要。
悪意のある負荷が、正規の利用者をも巻き添えにしかねないので。
目下、色々試しているのですが、
fail2ban のようなログ監視だと、最初のアクセスは必ず受けることになりますし
(あくまでログの「出力後」にbanするので。詳細はその2の方で(banできないケースについて))
複数の手段を重ねるのが現実的なのかもしれません。
(上記の apache、fail2ban、その前に更に プロキシやWAFなどを置いてフィルタする、など)
いろいろと手間取った件†
最終的に前述の設定に落ち着きましたが、そこまでの道のりが長くて・・・
もし同じようにハマる人のために(数か月後には全て忘れている自分に向けて )
以下はジタバタした足跡もメモしておきます。
まず最初のエラー「journalmatch」の件、
最初に filter と jail を作って起動したらこんなログが出ました。
デフォルトだと fail2ban のログは 「/var/log/fail2ban.log」 です。
NOTICE Jail started without 'journalmatch' set.
Jail regexs will be checked against all journal entries,
which is not advised for performance reasons.
私の場合ですが、
jail のデフォルト設定が「backend = systemd」になっており、
かつ fileter の設定に「journalmatch」が無かったから、このNOTICE が出ました。
つまり、journalmatchを正しく設定するか、
backend を systemd 以外にすることで解決できるようです(今回は後者です)。
そもそも journal って何!?
上手く説明する自信が無いので、ここでは割愛させて頂きますが、
「journalctl -e」と打って「・・・」と暫く(私が)フリーズして、
systemd やら journal やらを色々とググって、結果、今回は関係ない、と。
その結論に至る前に、systemd なのか!?と勘違いして、
(/etc/init.d/httpd からの起動を止めるために)
/run/systemd/generator.late/httpd.service から見よう見まねで
/etc/systemd/system/httpd.service を作って、
chkconfig --del httpd した後に、systemctl daemon-reload して、
systemctl enable httpd して、 systemctl start httpd して
systemd-cgls 打って、なんかそれっぽい気がしたので満足しましたが、
全く関係ありませんでした。
ようやく backend を設定しなければならないことに気がついて、
「backend = pyinotify」に変えたり、
python-inotify がインストールされてない事に気づいたり
他の apache-*conf を参考にしてみたものの、
logpath の %(apache_access_log)s って何だよ、って思ったり
(うちの環境の場合は jail.conf の「before = paths-fedora.conf」で、
paths-fedora.conf の apache_access_log が読まれてました)
七転八倒して、ようやく次のエラーメッセージ(datepattern の件)へ、
datepattern を書くときは、「man jail.conf」に
%を二重にしろ(エスケープしろ)って書いてあるのに、後でその説明に気が付きました。
書式エラーは直したつもりが、まだ上手くいきません。
そもそも、こいつ生きているのか? と思って、
試しに、手動で ban する IPを加えると、
ban が機能する&時刻経過で復旧することが確認できました。
(以下、apache-dos は自分で作ったフィルターです)
(まず手動でapache-dosフィルターに加えて、強制的にbanしてみます)
# fail2ban-client set apache-dos banip xxx.xxx.xxx.xxx
(そして、いまのステータスを確認してみます)
# fail2ban-client status apache-dos
Status for the jail: apache-dos
|- Filter
| |- Currently failed: 0
| |- Total failed: 2
| `- File list: access_logのパス(=正しいことを確認)
`- Actions
|- Currently banned: 1
|- Total banned: 1
`- Banned IP list: xxx.xxx.xxx.xxxx (ここが時間経過で消えることも確認)
# ipset --list とかでも確認できます。
ban自体は正常。う~ん・・・
設定を片っ端から fail2ban-client で調べるも、間違ってなさそうなので、
ちゃんとアクセスログを見ていない可能性が濃厚になります。
(ここでようやく、fail2ban-regex コマンドの存在に気づく)
(アクセスログがapache-dos.confの正規表現にマッチするか調べます)
# fail2ban-regex access_log /etc/fail2ban/filter.d/apache-dos.conf
Running tests
(中略)
Lines: 22653 lines, 0 ignored, 0 matched, 22653 missed
[processed in 2.55 sec]
ぎぃやぁぁ・・・1つも matched していない(ぜんぶ missed)。
これは正規表現の誤りだ・・・
極端な話、答えはこう
failregex = <HOST>
これが実際どういうフィルタになるかは、以下で確認できます。
# fail2ban-client get フィルタ名 failregex
「<HOST>」の部分は、それらしい正規表現に変換されるので、
上記の大雑把すぎる failregex でも十分に動作します。
私の場合はログの先頭(^)から終端($)まで気合い入れて書いたつもりが、
気合いが空回りしたようです。
また、
GET|POST|HEAD 等のメソッド付きで絞った場合は、
408 Request Timeout 等のメソッド無しのログが拾えません。
(タイムアウトなのだから、メソッドは実行されなかったわけなので。
通信速度が遅い利用者を救うか、わざと遅くリクエストしたDoS攻撃を防ぐのか、
どちらを優先して備えるかはWebサーバの管理者次第ですが。
今回は自分で「わざと遅いDoS攻撃」を仕掛けて動作確認していました。)
そんなわけで、ようやく failregex を直して、ban できました。
長く、苦しい、戦いだった。
うちのサイトの場合は css や 画像の数も少なく、使用用途もたかが知れているので、
細かい正規表現はかえって負荷にしかならないんじゃないかな~と思います。
これをベースに、各エラー(HTTP応答の 4XX、5XX)のカウントは更にシビアにして、
認証エラー(401)やセキュリティホール探し(404*2)は早期にban しようかと。
さぁ、この調子で他の設定も enable にして、ばんばん ban しまくるぞ!*3
(アクセスログ監視 その2 へ続く)