# 使用fail2ban自动拦截恶意注册

> wandoubaba / 2023-07-10

通常情况下，如果我们的FreeSWITCH应用是面向互联网开放的话，就要对互联网开放SIP端口，那么就避免不了来自一些“不友善”的IP发来的尝试SIP注册的请求，我们往往会在FreeSWITCH的日志中看到类似这下面这样的信息：

```log
2023-07-10 19:12:35.902557 98.23% [WARNING] sofia_reg.c:3211 Can't find user [8001@192.168.0.112] from 194.61.28.102
You must define a domain called '192.168.0.112' in your directory and add a user with the id="8001" attribute
and you must configure your device to use the proper domain in its authentication credentials.
2023-07-10 19:13:38.522558 98.27% [WARNING] sofia_reg.c:3211 Can't find user [80018001@192.168.0.112] from 194.61.28.102
You must define a domain called '192.168.0.112' in your directory and add a user with the id="80018001" attribute
and you must configure your device to use the proper domain in its authentication credentials.
2023-07-10 19:14:42.342569 98.10% [WARNING] sofia_reg.c:3211 Can't find user [80018001@192.168.0.112] from 194.61.28.102
You must define a domain called '192.168.0.112' in your directory and add a user with the id="80018001" attribute
and you must configure your device to use the proper domain in its authentication credentials.
```

> 自从1.10.7开始，FreeSWITCH的日志中都会在时间戳的后面加上空闲CPU百分比，这里的FreeSWITCH版本是1.10.8。

从上面这些内容我们不难看出，有个主机一直从`194.61.28.102`这个IP在向我们的FreeSWITCH发直SIP注册请求，想要注册分机号`8001`或者`80018001`，去网上查询一下，可以看到这个IP的地理位置位于美国的加里福尼亚洲，笔者可以肯定两点：

1. 我们的业务没有美国的客户。
2. 我们的SIP账号中没有8001或者80018001这样的账号。

因此我们可以非常肯定，这种注册行为完全是我们所不欢迎的。

通常情况下，我们可以通过将这个IP添加到系统防火墙的黑名单来拒绝来自这种恶意IP的请求，但是完全靠这种手动操作是治标不治本的，因为过不了多久就会有来自其他IP的恶意注册请求出现，靠手动处理完全是禁不绝的。

那么，能不能有一种自动化的工具能够代替人工来做这种自动捕捉和禁止的动作呢？

答案就是：**fail2ban**

## fail2ban简介

fail2ban是一款入侵防御软件，通过监控指定日志文件中的文本行，可以自动捕获到尝试操作失败的事件，进而得到入侵来源地址并自动将地址加入系统防火墙黑名单中，从而实现对系统自动入侵防御的目的。

## 用fail2ban防止FreeSWITCH被恶意注册

本文假设系统中已经正确安装、配置并运行了FreeSWITCH。

### 针对Debian上的FreeSWITCH1.10.8

版本信息

|软件/组件|版本|
|---|---|
|操作系统|Debian10.2.1-6(bullseye)|
|FreeSWITCH|1.10.8|
|fail2ban|0.11.2|

#### 安装fail2ban

```sh
sudo apt-get install fail2ban
```

#### 配置fail2ban

fail2ban默认已经内置了针对多种常见服务的入侵防御配置，但是默认都不是开启的状态，默认情况下fail2ban只是开启了针对`sshd`服务的防御，下面我们就一步一步开始打开针对FreeSWITCH的恶意注册防御。

##### 目录结构

|目录或文件|作用|说明|
|---|---|---|
|/etc/fail2ban/fail2ban.conf|fail2ban的主配置文件|一般情况下不需要修改这里|
|/etc/fail2ban/jail.conf|fail2ban的默认拦截配置文件|官方建议不要修改这个文件|
|/etc/fail2ban/filter.d/|fail2ban的默认捕获规则目录|这里包含各种服务的规则文件，如果需要修改，建议在这里基于.conf文件创建.local文件|
|/etc/fail2ban/jail.d/|fail2ban的具体拦截规则目录|在这里创建.local文件来配置拦截规则，如freeswitch.local|
|/etc/fail2ban/action.d/|fail2ban执行拦截的执行动作配置|一般情况下不需要修改这里的文件|

##### 创建拦截规则

在`/etc/fail2ban/jail.d/`目录下创建名为`freeswitch.local`的文件：

```sh
touch /etc/fail2ban/jail.d/freeswitch.local
```

编辑`/etc/fail2ban/jail.d/freeswitch.local`文件

```ini
[freeswitch]
enabled = true
filter = %(known/filter)s[_pref_line='(?: [\d\.]+%%%%)?']
banaction = ufw
maxretry = 5
findtime = 3600
bantime = 259200
ignoreip = 127.0.0.1/32
port = 5060,5061
logpath = /usr/local/freeswitch/log/freeswitch.log
```

规则说明：

- `enabled = true` 表示开启这个规则。
- `filter = %(known/filter)s[_pref_line='(?: [\d\.]+%%%%)?']` 是针对FS版本1.10.7之后版本的日志文件中，在每行时间戳后面会加下空间CPU百分比，加上这行就可以正常识别到事件，如果不加上这行就无法正确识别到事件（参考<https://github.com/fail2ban/fail2ban/issues/3143>）。
- `banaction = ufw` 表示拦截时使用ufw命令，如果不加这句，则默认在拦截时会使用iptables命令（如果系统中使用了ufw来管理防火墙，建议加上这句，否则有可能拦截无效）。
- `maxretry = 5` 表示相同事件连续发生的次数，这里设置为5表示连续尝试5次就被视为恶意行为
- `findtime = 3600` 表示监控的周期，配合maxretry使用，这里的配置表示“3600秒内发生5次尝试即被视为恶意行为”。
- `bantime = 259200` 表示拦截时间，这里设置为259200秒就是72小时，也就是3天
- `ignoreip = 127.0.0.1/32` 表示不拦截的IP地址。
- `port = 5060,5061` 是SIP服务的端口号（包括TLS）。
- `logpath = /usr/local/freeswitch/log/freeswitch.log` 是FreeSWITCH是实时日志文件的绝对路径。

上面这个拦截规则实现的效果就是，在1小时之内，如果来自同一个IP的SIP注册失败次数达到5次，就认为这个IP发起了恶意注册请求，3天内自动拒绝来自这个IP的所有请求。

##### 检查事件匹配规则

```sh
cat /etc/fail2ban/filter.d/freeswitch.conf
```

可以看到类似下面的内容

```ini
# Fail2Ban configuration file
#
# Enable "log-auth-failures" on each Sofia profile to monitor
# <param name="log-auth-failures" value="true"/>
# -- this requires a high enough loglevel on your logs to save these messages.
#
# In the fail2ban jail.local file for this filter set ignoreip to the internal
# IP addresses on your LAN.
#

[INCLUDES]

# Read common prefixes. If any customizations available -- read them from
# common.local
before = common.conf

[Definition]

_daemon = freeswitch

# Parameter "mode": normal, ddos or extra (default, combines all)
# Usage example (for jail.local):
#   [freeswitch]
#   mode = normal
#   # or with rewrite filter parameters of jail:
#   [freeswitch-ddos]
#   filter = freeswitch[mode=ddos]
#
mode = extra

# Prefix contains common prefix line (server, daemon, etc.) and 2 datetimes if used systemd backend
_pref_line = ^%(__prefix_line)s(?:(?:\d+-)?\d+-\d+ \d+:\d+:\d+\.\d+)?

prefregex = ^%(_pref_line)s \[WARN(?:ING)?\](?: \[SOFIA\])? \[?sofia_reg\.c:\d+\]? <F-CONTENT>.+</F-CONTENT>$

cmnfailre = ^Can't find user \[[^@]+@[^\]]+\] from <HOST>$

mdre-normal = %(cmnfailre)s
              ^SIP auth failure \((REGISTER|INVITE)\) on sofia profile \'[^']+\' for \[[^\]]*\] from ip <HOST>$

mdre-ddos   = ^SIP auth (?:failure|challenge) \((REGISTER|INVITE)\) on sofia profile \'[^']+\' for \[[^\]]*\] from ip <HOST>$

mdre-extra  = %(cmnfailre)s
              <mdre-ddos>

failregex = <mdre-<mode>>

ignoreregex =

datepattern = ^(?:%%Y-)?%%m-%%d[ T]%%H:%%M:%%S(?:\.%%f)?
              {^LN-BEG}

# Author: Rupa SChomaker, soapee01, Daniel Black, Sergey Brester aka sebres
# https://freeswitch.org/confluence/display/FREESWITCH/Fail2Ban
# Thanks to Jim on mailing list of samples and guidance
#
# No need to match the following. Its a duplicate of the SIP auth regex.
#  ^\.\d+ \[DEBUG\] sofia\.c:\d+ IP <HOST> Rejected by acl "\S+"\. Falling back to Digest auth\.$
```

这是fail2ban官方内容的事件匹配规则，针对本文档所使用各组件版本，这里的规则不需要修改。

##### fail2ban命令

```sh
## 启动系统服务
systemctl start fail2ban
## 重启系统服务（修改配置生效）
systemctl restart fail2ban
## 停止系统服务
systemctl stop fail2ban
## 用fail2ban-client命令启动服务
fail2ban-client start
## 用fail2ban-client命令重载全部配置（修改配置生效）
fail2ban-client reload
## 用fail2ban-client命令重载指定监禁配置（把<JAIL>换成具体的名称）
fail2ban-client reload <JAIL>
## 用fail2ban-client命令停止服务
fail2ban-client stop
## 查看当前保护状态
fail2ban-client status
## 查看当前保护的具体服务状态（把<JAIL>换成具体的名称）
fail2ban-client status <JAIL>
## 查看版本
fail2ban-client version
## 手动监禁一个IP（把<JAIL>换成具体名称，把<xx.xx.xx.xx>换成具体IP）
fail2ban-client set <JAIL> banip <xx.xx.xx.xx>
## 手动将一个IP解除监禁（把<JAIL>换成具体名称，把<xx.xx.xx.xx>换成具体IP）
fail2ban-client set <JAIL> unbanip <xx.xx.xx.xx>
```

##### fail2ban日志

位置 `/var/log/fail2ban.log`

##### 查看拦截是否生效

在本文的示例环境中，使用了`ufw`做为系统防火墙管理工具，所以我们使用ufw命令可以看到拦截是否生效：

```sh
ufw status
```

结果可能类似下面：

```sh
Status: active

To                         Action      From
--                         ------      ----
Anywhere                   REJECT      194.61.28.102
Anywhere                   REJECT      193.107.216.55
Anywhere                   REJECT      193.107.216.29
Anywhere                   REJECT      193.107.216.143
22/tcp                     ALLOW       Anywhere
80/tcp                     ALLOW       Anywhere
443/tcp                    ALLOW       Anywhere
....
```

上面被REJECT的这几个IP就是被fail2ban监禁的IP。

关于fail2ban的更多玩法可以到fail2ban的官方github上去了解：<https://github.com/fail2ban/fail2ban>