fail2banでWordPressを守る 2019年1月

以前にfail2banでWordPressを守るというのを書いたが、それの新しい版。
fail2banというのはUNIX系のOSで利用できるツールで、ログファイルを監視して不正なログイン試行が行われた・繰り返されたなどを検知するとその不正ログインを行ったIPアドレスをファイアウォールレベルで遮断するもの。WordPressもウェブサイトのオーナーやユーザーがログインして使用するものなのでログを監視して不正なログイン元を遮断するという仕組みは利用したいところ。ただし、いろいろ面倒。
「がとらぼ」ではWordPressのWP fail2banプラグインを使っていた。機能が絞られていてシンプル簡単だったので。
そのWordPressのWP fail2banプラグインが、ここ最近バージョンアップを重ねて機能が肥大した挙句、有料ライセンスの機能拡張が誕生し「そんなの求めてないよ」というものの塊になってしまった。

WP fail2banプラグイン 1
管理画面で見るWP fail2ban。機能は増えに増えて、もうよくわからないものに。なのに、無料版は管理画面では何もできない?

WP fail2banプラグイン 2
有料ライセンスは意外と高い。そして不必要な付加価値を付けすぎ。以前からの機能は引き続き無料で利用できるようだが、こうなると使い続けたくはなくなるよね。

しばらく愛用していたWP fail2banだが、見切りをつけることに。

で、ウェブサーバのアクセスログから認証時のエラーコード200を見つけてログイン失敗として検知するという以前の記事のやり方に戻そうかとも思ったけど、エラーコードを監視というのもどうだかなぁと思うし、それでは進歩がゼロだわね。

WordPress側に追加

WordPressのフック機能を使ってログイン失敗だけを専用ログに吐き出して、そのログをfail2banに監視させたら良くね?つまり、以前のシンプルだった頃のWP fail2banプラグインのお仕事を自分で書いてみようと。簡単なものなのでプラグインを新しく作るのではなくテーマのfunctions.phpにちょこっと書く。

使用中のテーマのfunctions.php (最後辺りにでも追記)
1
2
3
4
5
6
7
8
add_action('wp_login_failed', 'login_failure_log');
function login_failure_log($intruder) {
    $msg = date('[Y-m-d H:i:s T]') ." login failure from " . $_SERVER['REMOTE_ADDR'] . " for $intruder\n";
    $authlog = "/var/log/wp_auth_failure.log";
    $log_append = fopen($authlog, "a");
    fwrite($log_append, $msg);
    fclose($log_append);
}

4行目が出力されるログファイルだが、存在しない状態では出力エラーになる。ファイルを作成してパーミッションをウェブサーバのユーザーが書き込める状態にする。

ちなみに1行目のwp_login_failedをwp_loginに書き換えればログイン成功の記録を採る機能に変わる。(もちろん、関数名やメッセージは書き換えてやらないとログイン成功なのか失敗なのかわからなくなる。)
失敗・成功の両方を書いておけば認証記録になる。

ログファイルの作成とファイルオーナー変更の実行例(自分の環境に読み替えて)
# touch /var/log/wp_auth_failure.log
# chown www:www /var/log/wp_auth_failure.log
これで、WordPressにログインしようとして失敗すると作成したログファイルに下のように記録される
[2019-01-25 06:25:17 UTC] login failure from 192.168.4.211 for userhoge

サーバによって出力される時刻が日本時間かUTCかわからないのでタイムゾーンを時刻の後に出力するようにしている。 上の例ではUTCなので9時間足したものが日本時間になる。このようにタイムゾーンが書いてあればfail2banの側で時刻を認識するので後で時刻を変換するとかは要らないはず。 他はアクセス元のIPアドレスとログインしようとしたユーザー名程度の簡単なログ。

fail2banの設定

メインの設定ファイルとなるjail.localに設定追加。(これは以前の記事と同様)
/usr/local/etc/fail2ban/jail.local (追記または変更)
1
2
3
4
5
6
[wordpress-auth]
enabled  = true
filter   = wordpress-auth
logpath  = /var/log/wp_auth_failure.log
timepattern = %Y-%b-%d %H:%M:%S %Z
maxretry = 2

4行目は先に作成したログファイルを指定する。

フィルターファイルを作成する。

/usr/local/etc/fail2ban/filter.d/wordpress-auth.conf (新規作成)
1
2
3
4
[Definition]                                                                    
failregex = login failure from <HOST> for

ignoreregex =

検知しなければならない文字列とその中で遮断対象のIPアドレス (fail2banでは<HOST>と書く)が何処に記入されるかというのをfailregexに書く。つまり(既存のor 追記された)ログの中から login failure from IPアドレス for を見つけたらjail.localで指定された処理を行う。
「指定された処理」というのは上の設定例では書いていないが、[wordpress-auth]でbanactionが指定されていなければ[default]で指定されているbanactionが実行される。

設定ができたらfailban-regexを実行して意図したとおりにログファイルから不正アクセスレコードが検知されるか確認する。

$ fail2ban-regex /var/log/wp_auth_failure.log /usr/local/etc/fail2ban/filter.d/wordpress-auth.conf

Running tests
=============

Use   failregex filter file : wordpress-auth, basedir: /usr/local/etc/fail2ban
Use      datepattern : Default Detectors
Use         log file : /var/log/wp_auth_failure.log
Use         encoding : UTF-8


Results
=======

Failregex: 5 total
|-  #) [# of hits] regular expression
|   1) [5] login failure from <HOST> for
`-

Ignoreregex: 0 total

Date template hits:
|- [# of hits] date format
|  [5] {^LN-BEG}ExYear(?P<_sep>[-/.])Month(?P=_sep)Day(?:T|  ?)24hour:Minute:Second(?:[.,]Microseconds)?(?:\s*Zone offset)?
`-

Lines: 5 lines, 0 ignored, 5 matched, 0 missed
[processed in 0.00 sec]

今回はログ中の該当する5行がただしく認識できていた。ログ中の該当数と認識されている数が違えばNG。

ファイアウォールの設定はこの記事では省略。
ファイアウォールにFreeBSD/OpenBSDのpfを使っているならfail2ban (0.10系)をファイアウォールpfとの組み合わせで使うを参照下さい。

ファイアウォールの準備ができたらfail2banを再起動するか再読込するか。

FreeBSDでfail2banの設定再読込
# service fail2ban reload