Cloudflareプロキシ環境でFail2Banする
Using Fail2ban in Proxied Environment with CloudFlare
前置き
この記事はCloudFlareのプロキシ環境でFail2Banをする方法について紹介しています。
実IPを記録する方法から知りたい場合は以下を参照してください。
今回の記事で使用するmod_security(OWASP CRS) + fail2ban環境については以下の記事を参考にしてみてください
はじめに
Webサーバーのセキュリティ対策として、ModSecurity(OWASP CRS)で攻撃を検知し、Fail2BanでBANするというのはメジャーな構成の一つだと思いますが、この構成に Cloudflareを導入した場合、機能しなくなることが多いです。
Apacheにmod_remoteipなどを正しく設定していれば、ModSecurity(OWASP CRS)は攻撃者の実IPアドレス(X-Forwarded-For)を正しく認識し、ログに記録することはできます。ここまでは問題ないです。
Fail2Banはログを読み取り、攻撃者のIPを特定すると、OSのWAFである iptables(またはfirewalld)で通信を遮断しようとします。ここが問題で、iptablesは、OSのネットワーク層(L3/L4)で動作します。Cloudflareをプロキシしている場合、サーバーに届くすべての通信パケットの送信元IPは、CloudflareのIPアドレス になっています。
Cloudflare環境での問題点
Fail2Banが攻撃者IP(111.222.333.444)をiptablesでブロックするルールを追加しても、実際にサーバーに届くパケットの送信元はCloudflareなので、iptablesはそのパケットをスルーします。
つまり、BANしても攻撃者はCloudFlareのIPとなってしまうのでCloudFlare経由で普通にアクセスができてしまうということです。
もしここで、設定を誤ってパケットの送信元(CloudflareのIP)をBANしてしまうと、正規のユーザーを含む すべてのアクセスが遮断され、サイトがダウンする可能性もあります。
実装環境
本記事の設定は以下の環境で動作を確認しています。
| OS | openSUSE Tumbleweed x86_64 |
| Kernel | 6.18.9-1-default |
| CPU | Intel Xeon E5-2650L v4 (28コア) @ 2.500GHz |
| Apache | 2.4.66 (Linux/SUSE) |
| Fail2Ban | 1.1.0 |
以下、プロキシ環境でもFail2banで攻撃者IPをBanする方法についてです。
解決策
.htaccessを使用する
iptablesはCloudflareのIPしか見えず、CloudFlareの実際のIPアドレスまでは確認できないです。 そこで今回の手法では、Fail2Banのアクション先を iptables ではなく、Apacheの .htaccessに変更します。 Webサーバー(Apache)はHTTPヘッダーの中身を解釈できるため、Cloudflare経由であっても実IPアドレスを識別できます。.htaccessにDeny from <攻撃者IP>を書き込むことで、OSレベルではなくWebサーバーレベルで、確実に特定の攻撃者を弾くことができるようになります。
全体の構成イメージ
今回構築する構成を整理すると、以下のようになります。
攻撃者
↓(HTTP/HTTPS)
Cloudflare(プロキシ)
↓ CF-Connecting-IP ヘッダーに実IPを付与
Apache(mod_remoteip で実IPを復元)
↓
ModSecurity(OWASP CRS)が攻撃を検知 → error_log に記録
↓
Fail2Ban がログを監視 → .htaccess に「Deny from 攻撃者IP」を追記
↓
次回アクセス時、Apache が .htaccess を読んで攻撃者をブロック
重要な点として、ブロックをOSレベル(iptables)ではなくWebサーバーレベル(Apache)で行うということです。ApacheはHTTPヘッダーを解釈できるため、Cloudflare経由でも攻撃者の実IPを正しく識別してブロックできます。
以下、実際の設定手順です。
実際の設定手順
Apache互換モジュールの有効化
Apache 2.4では、Deny fromディレクティブはデフォルトで使えません。mod_access_compatを有効化する必要があります。これをやらないと、.htaccessを書き込んだ際に500エラーが発生します。
a2enmod access_compat
systemctl restart apache2※a2enmodコマンドがないディストリビューションでは、/etc/sysconfig/apache2のAPACHE_MODULESにaccess_compatを追記して再起動してください。
CloudflareのIP復元(mod_remoteip)
ApacheがCloudflare経由のアクセスから攻撃者の実IPを取得できるよう、mod_remoteipを設定します。/etc/apache2/conf.d/remoteip.confを新規作成し、以下を記述します。
※このあたりに関しては冒頭の記事を参考にしてもらえると幸いです。
RemoteIPHeader CF-Connecting-IP
RemoteIPTrustedProxy 103.21.244.0/22
RemoteIPTrustedProxy 103.22.200.0/22
RemoteIPTrustedProxy 103.31.4.0/22
RemoteIPTrustedProxy 104.16.0.0/13
RemoteIPTrustedProxy 104.24.0.0/14
RemoteIPTrustedProxy 108.162.192.0/18
RemoteIPTrustedProxy 131.0.72.0/22
RemoteIPTrustedProxy 141.101.64.0/18
RemoteIPTrustedProxy 162.158.0.0/15
RemoteIPTrustedProxy 172.64.0.0/13
RemoteIPTrustedProxy 173.245.48.0/20
RemoteIPTrustedProxy 188.114.96.0/20
RemoteIPTrustedProxy 190.93.240.0/20
RemoteIPTrustedProxy 197.234.240.0/22
RemoteIPTrustedProxy 198.41.128.0/17設定後、tail -f /var/log/apache2/access_logでアクセスログを確認し、CloudflareのIPではなく自分の実IPが表示されていれば成功です。
Fail2Banの権限設定
デフォルトのFail2Banはセキュリティ上の制約により、Webディレクトリへの書き込みができません。systemctl edit fail2banでSystemdの追加ファイルを作成し、書き込み権限を与えます。
$ sudo systemctl edit fail2ban
[Service]
ProtectSystem=full
ReadWritePaths=/srv/www/※/usr/lib/systemd/system/fail2ban.serviceを直接編集しても問題ありませんが、Fail2banの更新があったときに設定が飛ぶ可能性があるので注意です。
編集後は忘れずにデーモンをリロードします。
$ sudo systemctl daemon-reload
$ sudo systemctl restart fail2banFail2Banアクションの定義
/etc/fail2ban/action.d/apache-deny.confを新規作成します。BAN時に複数のhtdocsディレクトリ全ての.htaccessへDeny fromを追記し、解除時に該当行を削除するという構成です。
※複数Webサーバを運営していない場合はforループを使わず直接パス指定でいいです。
[Definition]
actionstart = for dir in /srv/www/htdocs /srv/www/htdocs2 /srv/www/htdocs3 /srv/www/htdocs4; do
if [ ! -f "$dir/.htaccess" ]; then
printf "Order Allow,Deny\nAllow from all\n" > "$dir/.htaccess"
fi
done
actionstop =
actioncheck =
actionban = for dir in /srv/www/htdocs /srv/www/htdocs2 /srv/www/htdocs3 /srv/www/htdocs4; do
echo "Deny from <ip>" >> "$dir/.htaccess"
done
actionunban = for dir in /srv/www/htdocs /srv/www/htdocs2 /srv/www/htdocs3 /srv/www/htdocs4; do
sed -i "/Deny from <ip>/d" "$dir/.htaccess"
done</ip></ip>actionstartでOrder Allow,Denyを先頭に書き込んでおくのがポイントです。これにより、後からDeny fromを追記しても設定が正しく機能します。
Apache側で.htaccessを許可
Apacheが.htaccess内のDenyディレクティブを読み込めるよう、各ドキュメントルートのブロックでAllowOverride Allを設定します。
<directory "="" srv="" www="" htdocs"="">
AllowOverride All
Require all granted
</directory>htdocs〜htdocs4のすべてに同様の設定を行い、最後にApacheをリロードします。
systemctl reload apache2Fail2Ban Jailの設定
/etc/fail2ban/jail.localに、今回作成したアクションを使うJailを追加します。
[apache-modsecurity]
enabled = true
action = apache-deny
port = http,https
filter = apache-modsecurity
logpath = /var/log/apache2/error_log
/var/log/apache2/error_default_ssl.log
maxretry = 2
bantime = 7d
findtime = 10mこれで、ModSecurityが攻撃を2回検知すると、攻撃者IPは7日間BANされる設定になります。各自の環境や目的によってここは変わってくると思いますので、調整してみてください。
動作確認
設定が完了したら、以下のコマンドでFail2Banのステータスを確認していきます。
※Total bannedが一つ多いのはテストの際に自分の端末をBANしてみたからです。
# Jailの稼働確認
$ sudo fail2ban-client status apache-modsecurity
Status for the jail: apache-modsecurity
|- Filter
| |- Currently failed: 0
| |- Total failed: 28
| `- File list: /var/log/apache2/error_log /var/log/apache2/error_default.log /var/log/apache2/error_default_ssl.log /var/log/apache2/smabros.net-error.log /var/log/apache2/healthy-life-log.com-error.log
`- Actions
|- Currently banned: 8
|- Total banned: 9
`- Banned IP list: 134.122.184.11 2.58.56.50 103.77.107.178 160.187.211.200 170.205.31.151 54.169.210.208 82.180.146.68 45.94.31.166
# BANされたIP確認
$ sudo fail2ban-client banned
[{'apache-auth': []}, {'apache-noscript': []}, {'apache-overflows': []}, {'apache-nohome': []}, {'apache-modsecurity': ['134.122.184.11', '2.58.56.50', '103.77.107.178', '160.187.211.200', '170.205.31.151', '54.169.210.208', '82.180.146.68', '45.94.31.166']}, {'apache-shellshock': []}, {'apache-badbots': []}, {'apache-botsearch': []}].htaccessへ上手く書き込まれているか確認していきます。
cat /srv/www/htdocs/.htaccess
...
Deny from 134.122.184.11
Deny from 2.58.56.50
Deny from 103.77.107.178
Deny from 160.187.211.200
Deny from 170.205.31.151
Deny from 54.169.210.208
Deny from 82.180.146.68
Deny from 45.94.31.166実際にBanされてみた
テストとして、自分のサーバに攻撃まがいのアクセスをしかけてみます。Curlのほうが簡単ですが、今回はスマホでやってみました。
ディレクトリトラバーサル攻撃のシグネチャに一致する../etc/passwdを入力していきます。
curlで行う場合はこちらです。※同じネットワーク上でこれを行うとFWの設定によっては全て止まってしまうので注意してください
curl -v "https://example.com/?test=../etc/passwd"※example.comは自分のドメインに書き換えてください

下の画像のように、403エラーがでました。これはmod securityがうまく機能しているということです。
私のFail2banルールは二回なので、再読み込みを行ってもう一度このURLにアクセスを試みます。

その後、わたしの通常のURLにアクセスすると・・

計画通りにBanされていますね。実際にサーバ上でもブロックがされているか確認していきます。
$ sudo fail2ban-client banned
[sudo] password for root:
[{'apache-auth': []}, {'apache-noscript': []}, {'apache-overflows': []}, {'apache-nohome': []}, {'apache-modsecurity': ['134.122.184.11', '2.58.56.50', '103.77.107.178', '160.187.211.200', '170.205.31.151', '54.169.210.208', '82.180.146.68', '45.94.31.166', '240b:c020:612:a52f:0:b:234a:9301']}, {'apache-shellshock': []}, {'apache-badbots': []}, {'apache-botsearch': []}]
$ tail /srv/www/htdocs/.htaccess /srv/www/htdocs2/.htaccess
==> /srv/www/htdocs/.htaccess <==
Deny from 134.122.184.11
Deny from 2.58.56.50
Deny from 103.77.107.178
Deny from 160.187.211.200
Deny from 170.205.31.151
Deny from 54.169.210.208
Deny from 82.180.146.68
Deny from 45.94.31.166
Deny from 240b:c020:612:a52f:0:b:234a:9301
==> /srv/www/htdocs2/.htaccess <==
Deny from 134.122.184.11
Deny from 2.58.56.50
Deny from 103.77.107.178
Deny from 160.187.211.200
Deny from 170.205.31.151
Deny from 54.169.210.208
Deny from 82.180.146.68
Deny from 45.94.31.166
Deny from 240b:c020:612:a52f:0:b:234a:9301うまくBanされていたようです。スマホの場合、機内モードに切り替えるだけでIPが変わるので、まだまだ他のテストなどしたい場合は切り替えしながらチェックしてみるといいかもしれません。
Banされたまま放置でも1週間で解除されますが、面倒かもですがBanを解除していきましょう。
$ sudo fail2ban-client unban 240b:c020:612:a52f:0:b:234a:9301
1
$ sudo fail2ban-client banned
[{'apache-auth': []}, {'apache-noscript': []}, {'apache-overflows': []}, {'apache-nohome': []}, {'apache-modsecurity': ['134.122.184.11', '2.58.56.50', '103.77.107.178', '160.187.211.200', '170.205.31.151', '54.169.210.208', '82.180.146.68', '45.94.31.166']}, {'apache-shellshock': []}, {'apache-badbots': []}, {'apache-botsearch': []}]
$ tail /srv/www/htdocs/.htaccess /srv/www/htdocs2/.htaccess
==> /srv/www/htdocs/.htaccess <==
RewriteRule ^ - [L]
Deny from 134.122.184.11
Deny from 2.58.56.50
Deny from 103.77.107.178
Deny from 160.187.211.200
Deny from 170.205.31.151
Deny from 54.169.210.208
Deny from 82.180.146.68
Deny from 45.94.31.166
==> /srv/www/htdocs2/.htaccess <==.htaccessでBanされていたスマホのIPが消えていますね。
Banされていたスマホでアクセスして、無事にみえていれば成功です。

うまく動作しない場合
/var/log/apache2/error_logや/var/log/fail2ban.logなどでエラーログを確認してください。
- Invalid command ‘Deny’:
access_compatモジュールが無効です。a2enmod access_compatを実行してください。 - not allowed here:
AllowOverride Allが設定されていないかルールが上書きされています。対象のブロックを見直してください。 - Permission Denied :systemdの権限設定忘れなどです。確認してみてください。
- BANしてもサイトが見える:
.htaccessの末尾にDeny fromが追記されているか確認してください。また、ブラウザキャッシュの影響を排除するため、シークレットモードで確認することをおすすめします。
まとめ
Cloudflareをプロキシとして使う環境では、従来のiptablesベースのFail2Ban構成は機能しません。しかし、Fail2Banのアクション先を.htaccessに変更することで、OSレベルではなくWebサーバーレベルで確実に攻撃者をブロックできます。
Fail2BanからCloudflare APIを叩いてWAFルールに直接IPを登録する手法もありますが、APIトークンをサーバーに持たせるセキュリティリスク、Freeプランでの登録上限、Cloudflareをバイパスした直接アクセスへの無力さ、API仕様変更への依存など気になる点が多かったため、今回はサーバー側で完結する.htaccess方式を採用しています。
(個人的に自分のサーバだけで完結させたかっただけです)
一度設定してしまえば安定して動作しますが、お使いの環境によって最適な設定方法などが違うと思いますので、この記事はあくまで実装の一例程度に考えてもらえればと思います。
おわり
使用したツール
CloudFlare(https://www.cloudflare.com/ja-jp)
Fail2ban(https://github.com/fail2ban/fail2ban)
OWASP CRS(https://owasp.org/www-project-modsecurity-core-rule-)
関連書籍(Amazonアフィリエイト)
Apacheの設定は少し古いですが、Webサーバのセキュリティ全般について理解することができる書籍です。Modsecurityの作者が書いておりますので、考え方なども勉強になります。中古も安く出回ってますのでよかったら参考までに。
