Linux/fail2banでアクセスログ監視

fail2ban へWebのアクセスログ監視を追加設定(2016-12-12、修正:2018-03-17)


Webサーバやメールサーバへのしつこい攻撃を、次々にban(禁止)していきたいわけです。


既に fail2ban は導入済みだけど、もっと連続404 や already banned を減らしたいという方は
アクセスログ監視 その2 の方へお進みください。

技術的な話はさておき、不正アクセスってどんななの、止まるの?という方は、
静的フィルタをかけてみた感想 をご覧ください。

概要

CentOS 7環境に置いてある Webサーバを DoS攻撃等からファイアウォールで守りたいので、
いつもお世話になっている fail2ban の設定を追加しました。*1

大まかな流れは以下です。

  1. fail2banをインストールする
    私の環境ではインストール済みだったので、ここでは省略します。
    (yum install や、systemctl enable 等をやることになる、かと)
  2. python-inotifyが無い場合、同じくインストールする。
  3. filter を追加する。
    「/etc/fail2ban/filter.d/」にfilter ファイルを新規作成。
  4. jail の設定を追記する。
    同じく「jail.d/local.conf」に追記、またはjail.d 配下へ新規ファイルの作成
  5. fail2ban を start もしくは reload する
  6. 無事、動くことを祈る

・・・私の場合は最後(祈る)が届かなかったので、
以下、そこの話がメインになります。

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などを置いてフィルタする、など)

いろいろと手間取った件

最終的に前述の設定に落ち着きましたが、そこまでの道のりが長くて・・・
もし同じようにハマる人のために(数か月後には全て忘れている自分に向けて [wink]
以下はジタバタした足跡もメモしておきます。

まず最初のエラー「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 でも十分に動作します。
私の場合はログの先頭(^)から終端($)まで気合い入れて書いたつもりが、
気合いが空回りしたようです。 [sad]

また、
GET|POST|HEAD 等のメソッド付きで絞った場合は、
408 Request Timeout 等のメソッド無しのログが拾えません。
(タイムアウトなのだから、メソッドは実行されなかったわけなので。
 通信速度が遅い利用者を救うか、わざと遅くリクエストしたDoS攻撃を防ぐのか、
 どちらを優先して備えるかはWebサーバの管理者次第ですが。
 今回は自分で「わざと遅いDoS攻撃」を仕掛けて動作確認していました。)

そんなわけで、ようやく failregex を直して、ban できました。
長く、苦しい、戦いだった。

うちのサイトの場合は css や 画像の数も少なく、使用用途もたかが知れているので、
細かい正規表現はかえって負荷にしかならないんじゃないかな~と思います。

これをベースに、各エラー(HTTP応答の 4XX、5XX)のカウントは更にシビアにして、
認証エラー(401)やセキュリティホール探し(404*2)は早期にban しようかと。


さぁ、この調子で他の設定も enable にして、ばんばん ban しまくるぞ!*3

アクセスログ監視 その2 へ続く)


*1 いつもお世話に、と言うのは fail2ban が各国からのsshd への不正ログインを止めまくっていたからです。どうりでログイン失敗が少ないな?と思っていたら暗躍してくれていたんですね・・・
*2 2018/03追記:当時は「HEAD /wp-login.php」とか、WordPress狙いのアクセスが多かったですが、その時に流行っている脆弱性に合わせて様々な404アクセスが来るのが、後日あらためて分かりました。
*3 そんなに不正アクセスが増えても困るのですが・・・。さておき、手始めは「netstat -a」で外へ LISTEN しているもの辺りから守っていこうかと。