2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

本記事はサーバのセキュリティ対策として、fail2banを用いた不正アクセスを防止する方法について記載しています。

インターネット上に公開されたサーバは、アタックサーフェスとして常にスキャンや攻撃のリスクにさらされているため、手動で対応する場合は運用が大変です。

fail2banを導入することで、このような脅威に自動で対処し、運用負荷を大幅に軽減できます。

背景

筆者はGoogle CloudのCompute Engineを利用してWebサーバを運用しています。

基本的に無料枠内で使用しているため、課金は発生していませんでしたが、ある時期から課金が発生していました。原因は既に特定していて、VM インスタンスからの下りネットワークの通信が原因です。

公式ドキュメントの無料枠の使用量上限の通りにアウトバウンドのデータ量が1GBを超えたためです。

北米から全リージョン宛ての 1 GB のアウトバウンド データ転送(1 か月あたり、中国とオーストラリアを除く)

以下は2025年4月分のレポートより、SKU ID:03A5-1B2A-3485(Network Internet Data Transfer Out from Americas to South America)でフィルタした例です。

スクリーンショット 2025-06-08 17.01.20.png

実態としては、海外からのボットを用いたクローリングなどの偵察行為による無差別なアクセスが原因と考えられますが、2024年12月頃からこうしたアクセスが顕著に増加していました。

fail2ban

fail2banは、Pythonで実装されているオープンソースのセキュリティツールです。

システムが出力するログファイルを基にフィルターを適用し、ログイン失敗回数などの条件により不正とみなされるIPアドレスを検出して、ファイアウォールのルールを自動的に更新します。

例えば、ApacheやNginxなどを利用してWebサーバを運用している場合、fail2banを導入することで、短時間に大量アクセスを行うIPアドレスからのアクセスを禁止(ban)することができます。

fail2banのインストール

Fail2banは多くのLinuxディストリビューションで公式にパッケージ化されているため、Debian系やRed Hat系などのパッケージマネージャを用いて簡単にインストールできます。

Debian系Linuxの場合は、APTコマンドでインストールを行います。

$ sudo apt install fail2ban

fail2banの設定

fail2banの設定方法を以下に記載します。

本記事ではOSとしてDebianを使用していますが、fail2banの設定ファイルは、/etc/fail2ban/ディレクトリ配下に格納されています。

.
├── action.d
├── fail2ban.conf
├── fail2ban.d
├── filter.d
├── jail.conf
├── jail.d
├── jail.local
├── paths-arch.conf
├── paths-common.conf
├── paths-debian.conf
└── paths-opensuse.conf

jail.confファイルは、デフォルトのテンプレートファイルです。パッケージのアップグレードによって上書きされる可能性があります。また、変更内容について将来のバージョンと互換性がない可能性があるため、直接変更することは推奨されていません。

新しく設定を行う場合は、jail.confファイルを直接編集せずに、jail.localファイルやjail.d/*.confファイルをカスタマイズする方法が推奨されています。

従ってjail.localファイルを新たに作成するか、/etc/fail2ban/jail.d/ディレクトリ配下に個別の.confファイルを追加することで、設定の上書きが可能です。

デフォルト動作

デフォルトでは、sshdに関するジェイルが設定されています。

sshdに関する設定は、/etc/fail2ban/jail.confに記述されています。また、当ファイルはbantime(禁止する時間)、findtime(検出する時間)、maxretry(最大失敗回数)などのデフォルト値も含まれています。

[sshd]

# To use more aggressive sshd modes set filter parameter "mode" in jail.local:
# normal (default), ddos, extra or aggressive (combines all).
# See "tests/files/logs/sshd" or "filter.d/sshd.conf" for usage example and details.
#mode   = normal
port    = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s

そして、/etc/fail2ban/jail.d/defaults-debian.confファイルによって、sshdのJailが有効化されています。

[sshd]
enabled = true

全てのジェイルに共通する設定は、[DEFAULT]セクションに記述します。

[DEFAULT]セクションは通常、/etc/fail2ban/jail.confファイルに記述されているため、bantime、findtime、maxretryなどの値は、各ジェイルの設定で明示的に指定しない限り、全てのジェイルに適用されます。

従って/etc/fail2ban/jail.localファイルの[DEFAULT]セクションを上書きすることで、ジェイルに適用されるデフォルト値を変更します。

設定ファイルの作成

本記事では、偵察行為と思われる不正アクセスを検出することを目的に、Nginxのアクセスログより、以下のルールを作成します。

設定ファイル 目的
nginx-abuse-access 過剰アクセスのリクエストを防ぐため、すべてのGETまたはPOSTリクエストを対象とする
nginx-badbots 自動化ツールや悪質なBotのアクセスを防ぐため、User-Agentにbotと思われるリクエストを対象とする

はじめにフィルター設定に関する設定ファイルを作成します。

  • /etc/fail2ban/filter.d/nginx-abuse-access.conf
[Definition]
failregex = ^<HOST> -.*"(GET|POST).*HTTP.*"
ignoreregex =
ignoreip =
  • /etc/fail2ban/filter.d/nginx-badbots.conf
[Definition]
failregex = ^<HOST> -.*"(GET|POST).*HTTP.*"(?:Mozilla|libwww|python|curl|php|wget|java|ruby|scrapy|bot).*$
ignoreregex =
ignoreip = 

ignoreregexは除外するログの条件、ignoreipについては除外するIPアドレスを記述します。これらの値は空でも問題ありません。

次にフィルター設定を適用するための設定ファイルを作成します。

以下の例では、60秒以内で10回以上のアクセスがログにあった場合、当該IPアドレスをBanにします。また、BanされたIPアドレスは1時間、ファイアウォール(iptables及びnftables)でアクセスが遮断されます。

  • /etc/fail2ban/jail.local
[nginx-abuse-access]
enabled  = true
filter   = nginx-abuse-access
port     = http,https
logpath  = /var/log/nginx/access.log
findtime = 60
maxretry = 10
bantime  = 3600
action   = iptables-multiport[name=abuse, port="http,https", protocol=tcp]
  • /etc/fail2ban/jail.d/nginx-badbots.local
[nginx-badbots]
enabled  = true
filter   = nginx-badbots
logpath  = /var/log/nginx/access.log
findtime = 60
maxretry = 10
bantime  = 3600
action   = iptables-multiport[name=badbots, port="http,https", protocol=tcp]

HTTPとHTTPSは区別されるため、actionの値についてiptables-multiportを使用しない場合は、iptablesを用いてそれぞれルールを作成する必要があります。

action = iptables[name=HTTP, port=http, protocol=tcp]

設定変更

設定変更を行う際は、サービスの再起動または設定ファイルの再読み込みを行います。

  • サービスの再起動
    $ sudo systemctl restart fail2ban
  • 設定ファイルの再読み込み
    $ sudo fail2ban-client reload

正常にサービスが起動していること及び各種ルールの状態については、以下のコマンドを実行して確認します。

  • サービスの確認
    $ systemctl status fail2ban
● fail2ban.service - Fail2Ban Service
     Loaded: loaded (/lib/systemd/system/fail2ban.service; enabled; preset: enabled)
     Active: active (running) since Tue 2025-06-10 10:14:34 JST; 50min ago
       Docs: man:fail2ban(1)
   Main PID: 478 (fail2ban-server)
      Tasks: 9 (limit: 1107)
     Memory: 47.1M
        CPU: 5.114s
     CGroup: /system.slice/fail2ban.service
             └─478 /usr/bin/python3 /usr/bin/fail2ban-server -xf start

Jun 10 10:14:34 webserver systemd[1]: Started fail2ban.service - Fail2Ban Service.
Jun 10 10:14:35 webserver fail2ban-server[478]: 2025-06-10 10:14:35,912 fail2ban.configreader   [478]: WARNING 'allowipv6' not defined in 'Definition'. Using defaul>
Jun 10 10:14:38 webserver fail2ban-server[478]: Server ready
  • fail2ban-clientの確認
    $ sudo fail2ban-client status
Status
|- Number of jail:	3
`- Jail list:	nginx-abuse-access, nginx-badbots, sshd

引数に設定したルールを付与することで、個別に状態を確認できます。

$ sudo fail2ban-client status nginx-abuse-access

Status for the jail: nginx-abuse-access
|- Filter
|  |- Currently failed:	0
|  |- Total failed:	32
|  `- File list:	/var/log/nginx/access.log
`- Actions
   |- Currently banned:	0
   |- Total banned:	0
   `- Banned IP list:

BanされたIPが発生すると、Banned IP listに表示されます。

Status for the jail: nginx-abuse-access
|- Filter
|  |- Currently failed:	0
|  |- Total failed:	214
|  `- File list:	/var/log/nginx/access.log
`- Actions
   |- Currently banned:	1
   |- Total banned:	3
   `- Banned IP list:	[REDACTED]
  • fail2ban-の設定値確認
    $ sudo fail2ban-client -d
2025-06-10 11:06:47,728 fail2ban.configreader   [1459]: WARNING 'allowipv6' not defined in 'Definition'. Using default one: 'auto'
['set', 'syslogsocket', 'auto']
['set', 'loglevel', 'INFO']
['set', 'logtarget', '/var/log/fail2ban.log']
['set', 'allowipv6', 'auto']
['set', 'dbfile', '/var/lib/fail2ban/fail2ban.sqlite3']
['set', 'dbmaxmatches', 10]
['set', 'dbpurgeage', '1d']
['add', 'sshd', 'auto']
['set', 'sshd', 'usedns', 'warn']
['set', 'sshd', 'prefregex', '^<F-MLFID>(?:\\[\\])?\\s*(?:<[^.]+\\.[^.]+>\\s+)?(?:\\S+\\s+)?(?:kernel:\\s?\\[ *\\d+\\.\\d+\\]:?\\s+)?(?:@vserver_\\S+\\s+)?(?:(?:(?:\\[\\d+\\])?:\\s+[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?|[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?(?:\\[\\d+\\])?:?)\\s+)?(?:\\[ID \\d+ \\S+\\]\\s+)?</F-MLFID>(?:(?:error|fatal): (?:PAM: )?)?<F-CONTENT>.+</F-CONTENT>$']
['set', 'sshd', 'maxlines', 1]
['multi-set', 'sshd', 'addfailregex', ['^[aA]uthentication (?:failure|error|failed) for <F-USER>.*</F-USER> from <HOST>( via \\S+)?(?: (?:port \\d+|on \\S+|\\[preauth\\])){0,3}\\s*$', '^User not known to the underlying authentication module for <F-USER>.*</F-USER> from <HOST>(?: (?:port \\d+|on \\S+|\\[preauth\\])){0,3}\\s*$', '^Failed publickey for invalid user <F-USER>(?P<cond_user>\\S+)|(?:(?! from ).)*?</F-USER> from <HOST>(?: (?:port \\d+|on \\S+)){0,2}(?: ssh\\d*)?(?(cond_user): |(?:(?:(?! from ).)*)$)', '^Failed (?:<F-NOFAIL>publickey</F-NOFAIL>|\\S+) for (?P<cond_inv>invalid user )?<F-USER>(?P<cond_user>\\S+)|(?(cond_inv)(?:(?! from ).)*?|[^:]+)</F-USER> from <HOST>(?: (?:port \\d+|on \\S+)){0,2}(?: ssh\\d*)?(?(cond_user): |(?:(?:(?! from ).)*)$)', '^<F-USER>ROOT</F-USER> LOGIN REFUSED FROM <HOST>', '^[iI](?:llegal|nvalid) user <F-USER>.*?</F-USER> from <HOST>(?: (?:port \\d+|on \\S+|\\[preauth\\])){0,3}\\s*$', '^User <F-USER>\\S+|.*?</F-USER> from <HOST> not allowed because not listed in AllowUsers(?: (?:port \\d+|on \\S+|\\[preauth\\])){0,3}\\s*$', '^User <F-USER>\\S+|.*?</F-USER> from <HOST> not allowed because listed in DenyUsers(?: (?:port \\d+|on \\S+|\\[preauth\\])){0,3}\\s*$', '^User <F-USER>\\S+|.*?</F-USER> from <HOST> not allowed because not in any group(?: (?:port \\d+|on \\S+|\\[preauth\\])){0,3}\\s*$', '^refused connect from \\S+ \\(<HOST>\\)', '^Received <F-MLFFORGET>disconnect</F-MLFFORGET> from <HOST>(?: (?:port \\d+|on \\S+)){0,2}:\\s*3: .*: Auth fail(?: (?:port \\d+|on \\S+|\\[preauth\\])){0,3}\\s*$', '^User <F-USER>\\S+|.*?</F-USER> from <HOST> not allowed because a group is listed in DenyGroups(?: (?:port \\d+|on \\S+|\\[preauth\\])){0,3}\\s*$', "^User <F-USER>\\S+|.*?</F-USER> from <HOST> not allowed because none of user's groups are listed in AllowGroups(?: (?:port \\d+|on \\S+|\\[preauth\\])){0,3}\\s*$", '^<F-NOFAIL>pam_[a-z]+\\(sshd:auth\\):\\s+authentication failure;</F-NOFAIL>(?:\\s+(?:(?:logname|e?uid|tty)=\\S*)){0,4}\\s+ruser=<F-ALT_USER>\\S*</F-ALT_USER>\\s+rhost=<HOST>(?:\\s+user=<F-USER>\\S*</F-USER>)?(?: (?:port \\d+|on \\S+|\\[preauth\\])){0,3}\\s*$', '^maximum authentication attempts exceeded for <F-USER>.*</F-USER> from <HOST>(?: (?:port \\d+|on \\S+)){0,2}(?: ssh\\d*)?(?: (?:port \\d+|on \\S+|\\[preauth\\])){0,3}\\s*$', '^User <F-USER>\\S+|.*?</F-USER> not allowed because account is locked(?: (?:port \\d+|on \\S+|\\[preauth\\])){0,3}\\s*', '^<F-MLFFORGET>Disconnecting</F-MLFFORGET>(?: from)?(?: (?:invalid|authenticating)) user <F-USER>\\S+</F-USER> <HOST>(?: (?:port \\d+|on \\S+)){0,2}:\\s*Change of username or service not allowed:\\s*.*\\[preauth\\]\\s*$', '^Disconnecting: Too many authentication failures(?: for <F-USER>\\S+|.*?</F-USER>)?(?: (?:port \\d+|on \\S+|\\[preauth\\])){0,3}\\s*$', '^<F-NOFAIL>Received <F-MLFFORGET>disconnect</F-MLFFORGET></F-NOFAIL> from <HOST>(?: (?:port \\d+|on \\S+)){0,2}:\\s*11:', '^<F-NOFAIL><F-MLFFORGET>(Connection (?:closed|reset)|Disconnected)</F-MLFFORGET></F-NOFAIL> (?:by|from)(?: (?:invalid|authenticating) user <F-USER>\\S+|.*?</F-USER>)? <HOST>(?:(?: (?:port \\d+|on \\S+|\\[preauth\\])){0,3}\\s*|\\s*)$', '^<F-MLFFORGET><F-MLFGAINED>Accepted \\w+</F-MLFGAINED></F-MLFFORGET> for <F-USER>\\S+</F-USER> from <HOST>(?:\\s|$)', '^<F-NOFAIL>Connection from</F-NOFAIL> <HOST>']]
['set', 'sshd', 'datepattern', '{^LN-BEG}']
['set', 'sshd', 'addjournalmatch', '_SYSTEMD_UNIT=sshd.service', '+', '_COMM=sshd']
['set', 'sshd', 'maxretry', 5]
['set', 'sshd', 'maxmatches', 5]
['set', 'sshd', 'findtime', '10m']
['set', 'sshd', 'bantime', '10m']
['set', 'sshd', 'ignorecommand', '']
['set', 'sshd', 'logencoding', 'auto']
['set', 'sshd', 'addlogpath', '/var/log/auth.log', 'head']
['set', 'sshd', 'addaction', 'iptables-multiport']
['multi-set', 'sshd', 'action', 'iptables-multiport', [['actionstart', "{ <iptables> -C f2b-sshd -j RETURN >/dev/null 2>&1; } || { <iptables> -N f2b-sshd || true; <iptables> -A f2b-sshd -j RETURN; }\nfor proto in $(echo 'tcp' | sed 's/,/ /g'); do\n{ <iptables> -C INPUT -p $proto -m multiport --dports ssh -j f2b-sshd >/dev/null 2>&1; } || { <iptables> -I INPUT -p $proto -m multiport --dports ssh -j f2b-sshd; }\ndone"], ['actionstop', "for proto in $(echo 'tcp' | sed 's/,/ /g'); do\n<iptables> -D INPUT -p $proto -m multiport --dports ssh -j f2b-sshd\ndone\n<iptables> -F f2b-sshd\n<iptables> -X f2b-sshd"], ['actionflush', '<iptables> -F f2b-sshd'], ['actioncheck', "for proto in $(echo 'tcp' | sed 's/,/ /g'); do\n<iptables> -C INPUT -p $proto -m multiport --dports ssh -j f2b-sshd\ndone"], ['actionban', '<iptables> -I f2b-sshd 1 -s <ip> -j <blocktype>'], ['actionunban', '<iptables> -D f2b-sshd -s <ip> -j <blocktype>'], ['port', 'ssh'], ['protocol', 'tcp'], ['chain', '<known/chain>'], ['name', 'sshd'], ['actname', 'iptables-multiport'], ['blocktype', 'REJECT --reject-with icmp-port-unreachable'], ['returntype', 'RETURN'], ['lockingopt', '-w'], ['iptables', 'iptables <lockingopt>'], ['blocktype?family=inet6', 'REJECT --reject-with icmp6-port-unreachable'], ['iptables?family=inet6', 'ip6tables <lockingopt>']]]
['add', 'nginx-abuse-access', 'auto']
['set', 'nginx-abuse-access', 'usedns', 'warn']
['set', 'nginx-abuse-access', 'addfailregex', '^<HOST> -.*"(GET|POST).*HTTP.*"']
['set', 'nginx-abuse-access', 'maxretry', 10]
['set', 'nginx-abuse-access', 'maxmatches', 10]
['set', 'nginx-abuse-access', 'findtime', '60']
['set', 'nginx-abuse-access', 'bantime', '3600']
['set', 'nginx-abuse-access', 'ignorecommand', '']
['set', 'nginx-abuse-access', 'logencoding', 'auto']
['set', 'nginx-abuse-access', 'addlogpath', '/var/log/nginx/access.log', 'head']
['set', 'nginx-abuse-access', 'addaction', 'iptables-multiport-abuse']
['multi-set', 'nginx-abuse-access', 'action', 'iptables-multiport-abuse', [['actionstart', "{ <iptables> -C f2b-abuse -j RETURN >/dev/null 2>&1; } || { <iptables> -N f2b-abuse || true; <iptables> -A f2b-abuse -j RETURN; }\nfor proto in $(echo 'tcp' | sed 's/,/ /g'); do\n{ <iptables> -C INPUT -p $proto -m multiport --dports http,https -j f2b-abuse >/dev/null 2>&1; } || { <iptables> -I INPUT -p $proto -m multiport --dports http,https -j f2b-abuse; }\ndone"], ['actionstop', "for proto in $(echo 'tcp' | sed 's/,/ /g'); do\n<iptables> -D INPUT -p $proto -m multiport --dports http,https -j f2b-abuse\ndone\n<iptables> -F f2b-abuse\n<iptables> -X f2b-abuse"], ['actionflush', '<iptables> -F f2b-abuse'], ['actioncheck', "for proto in $(echo 'tcp' | sed 's/,/ /g'); do\n<iptables> -C INPUT -p $proto -m multiport --dports http,https -j f2b-abuse\ndone"], ['actionban', '<iptables> -I f2b-abuse 1 -s <ip> -j <blocktype>'], ['actionunban', '<iptables> -D f2b-abuse -s <ip> -j <blocktype>'], ['name', 'abuse'], ['port', 'http,https'], ['protocol', 'tcp'], ['actname', 'iptables-multiport-abuse'], ['chain', 'INPUT'], ['blocktype', 'REJECT --reject-with icmp-port-unreachable'], ['returntype', 'RETURN'], ['lockingopt', '-w'], ['iptables', 'iptables <lockingopt>'], ['blocktype?family=inet6', 'REJECT --reject-with icmp6-port-unreachable'], ['iptables?family=inet6', 'ip6tables <lockingopt>']]]
['add', 'nginx-badbots', 'auto']
['set', 'nginx-badbots', 'usedns', 'warn']
['set', 'nginx-badbots', 'addfailregex', '^<HOST> -.*"(GET|POST).*HTTP.*"(?:Mozilla|libwww|python|curl|php|wget|java|ruby|scrapy|bot).*$']
['set', 'nginx-badbots', 'maxretry', 10]
['set', 'nginx-badbots', 'maxmatches', 10]
['set', 'nginx-badbots', 'findtime', '60']
['set', 'nginx-badbots', 'bantime', '3600']
['set', 'nginx-badbots', 'ignorecommand', '']
['set', 'nginx-badbots', 'logencoding', 'auto']
['set', 'nginx-badbots', 'addlogpath', '/var/log/nginx/access.log', 'head']
['set', 'nginx-badbots', 'addaction', 'iptables-multiport-badbots']
['multi-set', 'nginx-badbots', 'action', 'iptables-multiport-badbots', [['actionstart', "{ <iptables> -C f2b-badbots -j RETURN >/dev/null 2>&1; } || { <iptables> -N f2b-badbots || true; <iptables> -A f2b-badbots -j RETURN; }\nfor proto in $(echo 'tcp' | sed 's/,/ /g'); do\n{ <iptables> -C INPUT -p $proto -m multiport --dports http,https -j f2b-badbots >/dev/null 2>&1; } || { <iptables> -I INPUT -p $proto -m multiport --dports http,https -j f2b-badbots; }\ndone"], ['actionstop', "for proto in $(echo 'tcp' | sed 's/,/ /g'); do\n<iptables> -D INPUT -p $proto -m multiport --dports http,https -j f2b-badbots\ndone\n<iptables> -F f2b-badbots\n<iptables> -X f2b-badbots"], ['actionflush', '<iptables> -F f2b-badbots'], ['actioncheck', "for proto in $(echo 'tcp' | sed 's/,/ /g'); do\n<iptables> -C INPUT -p $proto -m multiport --dports http,https -j f2b-badbots\ndone"], ['actionban', '<iptables> -I f2b-badbots 1 -s <ip> -j <blocktype>'], ['actionunban', '<iptables> -D f2b-badbots -s <ip> -j <blocktype>'], ['name', 'badbots'], ['port', 'http,https'], ['protocol', 'tcp'], ['actname', 'iptables-multiport-badbots'], ['chain', 'INPUT'], ['blocktype', 'REJECT --reject-with icmp-port-unreachable'], ['returntype', 'RETURN'], ['lockingopt', '-w'], ['iptables', 'iptables <lockingopt>'], ['blocktype?family=inet6', 'REJECT --reject-with icmp6-port-unreachable'], ['iptables?family=inet6', 'ip6tables <lockingopt>']]]
['start', 'sshd']
['start', 'nginx-abuse-access']
['start', 'nginx-badbots']
  • ファイアウォール設定確認(nff
    $ sudo nft list ruleset
# Warning: table ip filter is managed by iptables-nft, do not touch!
table ip filter {
	chain f2b-abuse {
		ip saddr [REDACTED] counter packets 37 bytes 4966 reject
		counter packets 2760 bytes 255469 return
	}

	chain INPUT {
		type filter hook input priority filter; policy accept;
		meta l4proto tcp tcp dport { 80, 443 } counter packets 2842 bytes 266088 jump f2b-abuse
	}
}
  • ファイアウォール設定確認(iptables
    $ sudo iptables -L -n --line-numbers
Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination         
1    f2b-abuse  6    --  0.0.0.0/0            0.0.0.0/0            multiport dports 80,443

Chain FORWARD (policy ACCEPT)
num  target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination         

Chain f2b-abuse (1 references)
num  target     prot opt source               destination         
1    REJECT     0    --  [REDACTED]           0.0.0.0/0            reject-with icmp-port-unreachable
2    RETURN     0    --  0.0.0.0/0            0.0.0.0/0   

actionで指定するnameの値について、本記事では管理がしやすいように分けて設定しています。例えば、共通の値にすることでiptablesのチェインを減らしてルールを見やすくすることもできますが、どのジェイルのルールでbanされたかが分かりにくいなどのデメリットがあります。

fail2banの運用

fail2banのログの見方及びログの分析方法を以下に記載します。

また、ログの分析結果を踏まえて、アクセスの傾向に応じた応用的な対策について紹介します。

ログの見方

fail2banが出力するログは、デフォルトの場合、/var/log/ディレクトリ配下に出力されます。

-rw-r----- 1 root adm   281508 Jun 10 10:41 /var/log/fail2ban.log
-rw-r----- 1 root adm  1027167 Jun  8 00:00 /var/log/fail2ban.log.1
-rw-r----- 1 root adm    18159 May 31 23:54 /var/log/fail2ban.log.2.gz
-rw-r--r-- 1 root root   32064 Jun 10 10:10 /var/log/faillog

ログの確認は、/var/log/fail2ban.logファイルを参照します。/var/log/faillogファイルはバイナリファイルです。

/var/log/fail2ban.logファイルについて、監視対象のログファイルがフィルターの条件に一致した場合、Foundのメッセージが出力されます。

2025-06-10 20:25:22,273 fail2ban.filter         [478]: INFO    [nginx-abuse-access] Found [REDACTED] - 2025-06-10 20:25:21
2025-06-10 20:25:22,274 fail2ban.filter         [478]: INFO    [nginx-abuse-access] Found [REDACTED] - 2025-06-10 20:25:22
2025-06-10 20:25:22,458 fail2ban.filter         [478]: INFO    [nginx-abuse-access] Found [REDACTED] - 2025-06-10 20:25:22
2025-06-10 20:25:22,780 fail2ban.filter         [478]: INFO    [nginx-abuse-access] Found [REDACTED] - 2025-06-10 20:25:22
2025-06-10 20:25:23,091 fail2ban.filter         [478]: INFO    [nginx-abuse-access] Found [REDACTED] - 2025-06-10 20:25:23
2025-06-10 20:25:23,396 fail2ban.filter         [478]: INFO    [nginx-abuse-access] Found [REDACTED] - 2025-06-10 20:25:23
2025-06-10 20:25:23,711 fail2ban.filter         [478]: INFO    [nginx-abuse-access] Found [REDACTED] - 2025-06-10 20:25:23
2025-06-10 20:25:24,025 fail2ban.filter         [478]: INFO    [nginx-abuse-access] Found [REDACTED] - 2025-06-10 20:25:24
2025-06-10 20:25:24,332 fail2ban.filter         [478]: INFO    [nginx-abuse-access] Found [REDACTED] - 2025-06-10 20:25:24
2025-06-10 20:25:24,643 fail2ban.filter         [478]: INFO    [nginx-abuse-access] Found [REDACTED] - 2025-06-10 20:25:24
2025-06-10 20:25:24,956 fail2ban.filter         [478]: INFO    [nginx-abuse-access] Found [REDACTED] - 2025-06-10 20:25:24

その後、findtimeで設定した時間内にmaxretry回以上のしきい値を超えた場合、当該IPアドレスに関するBanのメッセージが出力されます。

2025-06-10 20:25:25,197 fail2ban.actions        [478]: NOTICE  [nginx-abuse-access] Ban [REDACTED]

設定した時間が経過すると、制限は解除されるるため、Unbanのメッセージが出力されます。

2025-06-10 21:25:24,315 fail2ban.actions        [478]: NOTICE  [nginx-abuse-access] Unban [REDACTED]

参考までにNginxのアクセスログより、上記BanされたIP addressからのアクセスは以下の通りです。

[REDACTED] - - [10/Jun/2025:20:25:21 +0900] "GET /contact-us HTTP/1.1" 404 146 "-" "-"
[REDACTED] - - [10/Jun/2025:20:25:22 +0900] "GET /.env HTTP/1.1" 404 146 "-" "-"
[REDACTED] - - [10/Jun/2025:20:25:22 +0900] "GET /contact HTTP/1.1" 404 146 "-" "-"
[REDACTED] - - [10/Jun/2025:20:25:22 +0900] "GET /contactus HTTP/1.1" 404 146 "-" "-"
[REDACTED] - - [10/Jun/2025:20:25:23 +0900] "GET /contactus.html HTTP/1.1" 404 146 "-" "-"
[REDACTED] - - [10/Jun/2025:20:25:23 +0900] "GET /contactus.php HTTP/1.1" 404 146 "-" "-"
[REDACTED] - - [10/Jun/2025:20:25:23 +0900] "GET /contact_us HTTP/1.1" 404 146 "-" "-"
[REDACTED] - - [10/Jun/2025:20:25:24 +0900] "GET /_profiler/phpinfo HTTP/1.1" 404 146 "-" "-"
[REDACTED] - - [10/Jun/2025:20:25:24 +0900] "GET /phpinfo HTTP/1.1" 404 146 "-" "-"
[REDACTED] - - [10/Jun/2025:20:25:24 +0900] "GET /phpinfo.php HTTP/1.1" 404 146 "-" "-"
[REDACTED] - - [10/Jun/2025:20:25:24 +0900] "GET /info.php HTTP/1.1" 404 146 "-" "-"

ログの分析1

fail2banが出力するfail2ban.logファイルを基にログの分析を行ないます。

以下のコマンドは実行すると、fail2ban.logファイルよりBanされたIPアドレスを抽出し、IPアドレスでソートした結果を踏まえて、IPアドレスとタイムスタンプの情報を出力します。

$ cat /var/log/fail2ban.log* | grep 'Ban' | awk '{print $NF, $1, $2}' | sort -t . -k1,1n -k2,2n -k3,3n -k4,4n

[REDACTED] 2025-06-01 18:00:31,767

コマンド結果から大きく以下の傾向が確認できました。

基本的なパターンは、同一IPアドレスからの大量アクセスです。時間帯に規則性はなく、Banが行われても連日による不定期なアクセスが確認できます。

出力例
[REDACTED] 2025-06-01 01:16:36,507
[REDACTED] 2025-06-01 03:11:53,954
[REDACTED] 2025-06-01 04:58:18,768
[REDACTED] 2025-06-01 06:58:28,149
[REDACTED] 2025-06-01 09:10:04,594
[REDACTED] 2025-06-01 10:59:44,230
[REDACTED] 2025-06-01 12:33:34,875
[REDACTED] 2025-06-01 16:43:22,353
[REDACTED] 2025-06-01 18:21:42,393
[REDACTED] 2025-06-01 20:16:25,674
[REDACTED] 2025-06-01 22:03:12,942
[REDACTED] 2025-06-01 23:56:54,248
[REDACTED] 2025-06-02 02:00:27,605
[REDACTED] 2025-06-02 10:37:51,744
[REDACTED] 2025-06-02 12:45:06,020
[REDACTED] 2025-06-02 14:18:25,681
[REDACTED] 2025-06-02 16:19:53,124
[REDACTED] 2025-06-02 17:52:00,927
[REDACTED] 2025-06-02 19:40:17,349
[REDACTED] 2025-06-02 21:26:44,324
[REDACTED] 2025-06-02 23:19:33,013
[REDACTED] 2025-06-03 01:05:32,369
[REDACTED] 2025-06-03 02:33:19,008
[REDACTED] 2025-06-03 04:28:22,373
[REDACTED] 2025-06-03 06:19:26,595
[REDACTED] 2025-06-03 08:12:12,016
[REDACTED] 2025-06-03 09:45:44,198
[REDACTED] 2025-06-03 11:31:08,914
[REDACTED] 2025-06-03 13:35:09,127
[REDACTED] 2025-06-03 15:12:22,771
[REDACTED] 2025-06-03 17:07:27,365
[REDACTED] 2025-06-03 18:55:27,159
[REDACTED] 2025-06-03 20:36:30,714
[REDACTED] 2025-06-03 22:30:24,300
[REDACTED] 2025-06-04 00:46:15,650
[REDACTED] 2025-06-04 02:40:09,902
[REDACTED] 2025-06-04 04:37:05,981
[REDACTED] 2025-06-04 06:12:36,854
[REDACTED] 2025-06-04 07:54:19,516
[REDACTED] 2025-06-04 09:38:54,252
[REDACTED] 2025-06-04 11:35:34,746
[REDACTED] 2025-06-04 13:19:11,169
[REDACTED] 2025-06-04 15:10:09,320
[REDACTED] 2025-06-04 16:46:29,451
[REDACTED] 2025-06-05 10:54:00,421
[REDACTED] 2025-06-05 12:41:49,167
[REDACTED] 2025-06-05 14:36:50,218
[REDACTED] 2025-06-05 16:37:24,671
[REDACTED] 2025-06-05 18:39:55,324
[REDACTED] 2025-06-05 20:41:53,408
[REDACTED] 2025-06-06 03:14:32,613
[REDACTED] 2025-06-06 05:27:38,586
[REDACTED] 2025-06-06 07:20:12,388
[REDACTED] 2025-06-06 09:17:20,735
[REDACTED] 2025-06-06 11:30:15,449
[REDACTED] 2025-06-06 13:23:49,431
[REDACTED] 2025-06-06 15:21:12,685
[REDACTED] 2025-06-06 17:10:25,922
[REDACTED] 2025-06-06 19:30:35,334
[REDACTED] 2025-06-06 21:17:07,349
[REDACTED] 2025-06-06 23:27:26,388
[REDACTED] 2025-06-07 01:07:09,374
[REDACTED] 2025-06-07 03:33:42,778
[REDACTED] 2025-06-07 05:15:15,442
[REDACTED] 2025-06-07 07:21:57,055
[REDACTED] 2025-06-07 09:10:49,671
[REDACTED] 2025-06-07 11:10:10,013
[REDACTED] 2025-06-07 13:08:19,036
[REDACTED] 2025-06-07 14:53:44,249
[REDACTED] 2025-06-07 17:07:20,829
[REDACTED] 2025-06-08 01:51:46,809

対策

対策として2つの方法を紹介します。

1つ目の対策は、設定ファイルのチューニングです。findtimemaxretryの値を小さくし、bantimeの値を伸ばすことで、アクセスを低減することができます。

2つ目の対策は、recidiveジェイルを併用することで、過去に何度もBanされているIPアドレスを長期的にBanすることができます。

recidiveジェイルの使用する場合は、/etc/fail2ban/jail.localファイルに以下のような設定を記述します。以下の例では24時間以内に3回以上Banされていたら1週間Banにします。

[recidive]
enabled = true
logpath = /var/log/fail2ban.log
findtime = 86400
maxretry = 3
bantime = 604800
backend = auto

設定変更後、サービスの再起動などを行います。

recidiveジェイルは、単一のジェイルに限らず、複数のジェイルによってBanされた履歴も含めて判断します。

ログの分析2

次のパターンは、IPアドレスのレンジを変えることによる大量アクセスのパターンです。

[REDACTED].12 2025-06-04 12:20:16,196
[REDACTED].16 2025-06-04 12:19:38,014
[REDACTED].17 2025-06-04 12:18:06,569
[REDACTED].21 2025-06-04 12:16:29,715
[REDACTED].24 2025-06-06 09:40:30,168
[REDACTED].29 2025-06-06 09:36:16,022
[REDACTED].32 2025-06-06 09:39:43,984
[REDACTED].35 2025-06-06 09:38:17,743
[REDACTED].41 2025-06-07 16:51:15,552
[REDACTED].45 2025-06-07 16:49:38,100
[REDACTED].46 2025-06-07 16:53:29,273
[REDACTED].49 2025-06-07 16:52:46,999
[REDACTED].68 2025-06-01 11:13:22,790
[REDACTED].74 2025-06-01 11:10:59,764
[REDACTED].76 2025-06-01 11:16:25,621
[REDACTED].79 2025-06-01 11:15:23,285

広範囲によるアクセスを低減するためには、ファイアウォールでIPアドレスの範囲を制限することが有効ですが、正当なユーザまで遮断するリスクがあるため、実際にシステム運用を行なっている場合などは十分に注意する必要があります。

なお、IPアドレスの範囲で制限を行う場合、Linuxのファイアウォールで制限を行う方法と、クラウドなどのプラットフォーム側のファイアウォールで制限を行う方法が可能ですが、それぞれ動作は異なります。

例えば、iptablesで制限する場合、iptablesはTCPのパケットを基に制御していることから、TCPのパケットは仮想マシンまで届いています。しかし、プラットフォーム側のファイアウォールなどを利用する場合は、仮想マシンまでパケットが届きません。

対策

ipsetを活用して、IPアドレスの範囲で制限を行う例を以下に紹介します。

ipsetは、IPアドレスをグループとして管理することができるツールです。ipsetをインストールしていない場合は、以下のコマンドを実行して、インストールを行います。

$ sudo apt install ipset

事前に以下のコマンドを実行して、IPアドレスのデータセットを作成します。blacklistはセットする名前になるため、任意で指定します。(timeoutの値は以下の場合、1日を指定)

$ ipset create blacklist hash:net timeout 86400

以下のコマンドを実行して、送信元がblacklistの場合は全てDROPするルールを追加します。

$ sudo iptables -I INPUT -m set --match-set blacklist src -j DROP

/etc/fail2ban/action.d/ipset-blacklist.confファイルを作成します。IPアドレスの範囲は、CIDRで指定します。

[Definition]
actionstart = ipset create blacklist hash:net timeout 86400 -exist
actionstop = ipset flush blacklist
actioncheck = ipset list blacklist

actionban = ipset add blacklist N.N.N.0/24 timeout 86400 -exist
actionunban = ipset del blacklist N.N.N.0/24

[Init]
name = blacklist

/etc/fail2ban/jail.localファイルの設定するジェイルに関するactionの値を変更します。

[nginx-abuse-access]
enabled  = true
filter   = nginx-abuse-access
port     = http,https
logpath  = /var/log/nginx/access.log
findtime = 60
maxretry = 10
bantime  = 120
action   = ipset-blacklist[name=abuse, port="http,https", protocol=tcp]

上記設定完了後、サービスの再起動などを行い設定を反映します。以降、アクセス元のIPアドレスが特定のネットワークからのアクセスであり、条件を満たす場合はblacklistに追加されることで制限されます。

制限が行われると、以下のようなN.N.N.0/24 timeout 60の行が追加されるため、blacklistに登録されたことが確認できます。

$ ipset list blacklist

Name: blacklist
Type: hash:net
Revision: 7
Header: family inet hashsize 1024 maxelem 65536 timeout 60 bucketsize 12 initval 0xade94787
Size in memory: 520
References: 1
Number of entries: 0
Members:
N.N.N.0/24 timeout 60

ipsetを併用する場合は、/etc/fail2ban/jail.localファイルで設定したbantimeの値と、/etc/fail2ban/action.d/ipset-blacklist.confファイルで指定するtimeoutの値は揃えるのが望ましいです。なぜならば、bantimeの値が長くても、ipsetのtimeoutの値が短かいと、適切にブロックできないためです。

プラットフォーム側のファイアウォールについては、Google Cloudを例にすると、VPCファイアウォールのファイアウォール ルールより、ソースフィルタのIP 範囲を制限することで、一括でアクセスを低減できます。

おわりに

fail2ban導入後、また無料枠で利用することができました。

スクリーンショット 2025-07-04 0.28.49.png

パブリッククラウドのサービスを利用してWebサーバを公開している場合、インバウンドの通信は無料であるものの、アウトバウンドの通信はデータ量に応じて課金されることがほとんどです。

従ってクラウド環境においては、不正アクセスを防ぐことは意図しない課金を抑制し、結果的にコスト削減にもつながります。

参考

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?