frps 防 ssh 暴力破解

最近发现老集群上的 fail2ban 把我的办公电脑IP给禁了,无奈只能设置忽略该ip,然后在集群上运行 lastb 可以发现大量来自我电脑的尝试 ssh 登录,各种用户名。真是奇怪,难道我的办公电脑被入侵了?查找了很久也没有发现问题所在。今天重启了这台电脑,在集群上可以发现重启的那几分钟没有了尝试登录,可是当电脑重启后又出现了尝试登录,看来问题确实在这台电脑上。由于是刚重启,我重复执行命令 netstat -nap | grep 集群ip进行搜索,时不时出现的只有一个 frpc 进程。最初我一直以为这个进程是正常的,因为它就是为登录集群而设的,所以与集群有连接最初并没有引起我的注意。但是我突然想到,如果外部没有人在通过 frp 连接我的集群的话,是不会出现这个连接的。于是恍然大悟,这些尝试登录的连接都是通过暴露在外网的 frps 端口连进来的!但是对集群来说,它并不知道 frp 的存在,它只知道是从我的电脑上过来的连接。于是去查看 frps 的日志,果然有来自各处的许多 ip 频繁的在尝试登录!

问题找到了,接下来如何解决该问题呢?通过搜索发现了这个帖子,这位仁兄写了一个 frptables 程序,https://github.com/zngw/frptables,其功能是分析 frps 的日志文件(0.36版本之后才有日志文件),然后统计连接 frps 的 ip 数据,设定规则当给定时间内连接数超过设定值时则设置防火墙禁止该 ip,防火墙支持 iptables 和 firewalld。Github 上下载下来的zip压缩包里就是一个单独的 linux 可执行文件frptables,只需要自己设好相应 .yml 配置文件即可使用了。

一、安装 iptables

我的 frps 是在 nas 上的 docker 容器里运行的(snowdreamtech/frps),进入容器终端运行 iptables -L 发现根本没有该命令,cat /etc/issue 发现容器是 alpine linux,其包管理器是 apk,于是通过如下命令方便的安装 iptables

apk update
apk add iptables

装完后发现运行 iptables -L 依然不行,提示如下

iptables v1.8.7 (legacy): can't initialize iptables table `filter': Permission denied (you must be root)                                                                                                    
Perhaps iptables or your kernel needs to be upgraded.  

通过搜索发现是容器的权限问题,只需要给容器 NET_RAW 和 NET_ADMIN 权限即可,而默认情况下前者是有的,只需要再加上后者即可。当然如果图省事打开所有权限也行。

二、手动运行 frptables

把 frptables 拷到 nas 上的 /mypath/to/frps 并把该路径挂载为容器的 /etc/frp,设置 frptables-config.yml 如下

# frps日志文件
frps_log: /etc/frp/frps.log

# frptables 的输出日志目录
logs: /etc/frp/banlog/

# frps 名字端口对应配置
name_port:
  "myssh1": 12345
  "myssh2": 12346
  "ssh00": 12347

# 启用防火墙类型 iptables / firewall / md (Microsoft Defender)
tables_type: iptables

# ip白名单:
allow_ip:
  - 127.0.0.1

# 端口白名单
allow_port:
  - 7500
#  - 443

# 规则访问
rules:

  # 按数组顺序来,匹配到了就按匹配的规则执行,跳过此规则。
  # 地区 country-国家, regionName-省名,名字中不带省字, city-市名,名字中也不带市字
  # 端口: -1 所有端口
  # time: 时间区间
  # count: 访问次数,-1不限,0限制。其他为 time时间内访问count次,超出频率就限制

  - # 20分钟内访问15次则加入防火墙
    port: -1
    country:
    regionName:
    city:
    time: 1200
    count: 15

#  - # 中国上海IP允许
#    port: -1
#    country: 中国
#    regionName: 上海
#    city: 上海
#    time: 1
#    count: -1
#
#  - # 中国地区IP 10分钟3次,超出这频率添加防火墙
#    port: -1
#    country: 中国
#    regionName: 浙江
#    city:
#    time: 600
#    count: 3
#
#  - # 其他地区IP 直接加入防火墙
#    port: -1
#    country:
#    regionName:
#    city:
#    time: 1
#    count: 0

然后手动运行 ./frptables -c ./frptables-config.yml,一开始并不成功。原来是程序内部是通过 bash 来调用命令行的,然而容器默认没有装 bash,只有 sh,所以需要先安装 bash(只需命令 apk add bash 即可)。安装完 bash,一开始运行时还是出错,发现问题竟然是 "ssh00": 12347 前多了一个空格。最初的时候为了让它与上一行的冒号对其,"ssh00": 12347 前有三个空格,结果出错,改成两个空格就正常了。然后查看日志 banlog/frptables.info.2022-08-15,会有类似如下输出

2022-08-15 23:05:25.192 [TRACE] - [Tag:link] [allow: [username.myssh1]206.189.226.38:-1 ->1, 美国,XX,XX]                                                                                                     
2022-08-15 23:10:06.952 [TRACE] - [Tag:link] [allow: [username.myssh1]206.189.226.38:-1 ->2, 美国,XX,XX]                                                                                                     
2022-08-15 23:11:10.475 [TRACE] - [Tag:link] [allow: [username.myssh1]180.69.254.177:-1 ->1, 韩国,XX,XX]                                                                                                     
2022-08-15 23:11:21.461 [TRACE] - [Tag:link] [allow: [username.myssh1]206.189.226.38:-1 ->3, 美国,XX,XX]                                                                                                     
2022-08-15 23:12:38.525 [TRACE] - [Tag:link] [allow: [username.myssh1]206.189.226.38:-1 ->4, 美国,XX,XX]                                                                                                     

-1 -> 4 这类的计数超过设定的 15 后,allow 会变为 refuse,再查看 iptables -L 则会发现该 ip 被禁止了。

三、网络设为 macvlan

最初的 frps 容器的网络设的是 host,当在容器中运行 iptables -L 时发现输出的结果和在宿主机(nas)里运行 iptables -L 的结果一样,可见容器在 host 模式下与宿主机共享网络设置,所以如果容器里的 frptables 修改了 iptables 那宿主机的应该也同样改变了,当然也可以直接在宿主机里运行 frptables。但是我不想修改宿主机的 iptables 设置,那就不能对该容器是 host 网络。

换成 bridge 网络试试,发现映射 7000 端口时 tcp 和 udp 都得打开才行,否则连不上,此外还要映射 7500 端口以及需要穿透的端口(比如上面例子中的 12345、12346、12347)。映射好端口并能够正常连接后运行 frptables,为了测试,最初的设置是所有连接直接禁掉。结果发现 frps.log 里的 ip 都成了 172.17.0.1,也就是和容器桥接的宿主机ip,这时由于进行了一次封装,容器里已无法区分与 frps 建立连接的真实 ip 了,看到的只有 172.17.0.1。可见该方案也不行。

最后只能使用 macvlan 网络模式,因为这样会分配给容器一个与宿主机同网段 192.168.0.xxx 的 ip,而此时容器的的网络和宿主机的网络在逻辑上以及完全无关,相当于两台独立的电脑,这时便可以在容器里设置 iptables 而不影响宿主机的 iptables 配置,而容器也能识别真实的连接 ip。

四、设置 frptables 自启动

1、尝试 openrc 失败

最初尝试在 /etc/rc.local 中设置运行 frptables 的命令,然而容器启动时该文件并不生效,与是否设成可执行属性无关。由于容器用的是 alpine linux,网上说 alpine linux 不是通过 /etc/rc.local 来设置开机启动,而是通过 local 服务运行 /etc/local.d/ 下的脚本来实现,而服务则是通过 openrc 来控制,于是尝试如下操作

apk add openrc  # 安装 openrc,容器里默认是没有安装的
rc-update add local default
# 把运行 frptables 的命令写成脚本,命名为 xxx.start 并放到 /etc/local.d/ 目录下
rc-service local start

在执行完 rc-service local start之后,xxx.start 脚本确实执行了,但问题是我尝试重启容器后却并不执行 xxx.start,而且重启后查看 rc-service local status 依然是 started。尝试了很多次都不行,容器启动时 xxx.start 不会自动运行,只有手动执行 rc-service local startrestart 时才会执行,我猜或许是 docker 对其进行了限制,比较这是容器里的 alpine 系统而不是实体机上的。

2、尝试修改 config.v2.json 也失败

在 nas 里运行 docker ps 找到 frps 容器所对应的 id,进而进到其目录 /volume1/@docker/containers/56b415b0a5xxxxx,其目录字符串 56b415b0a5xxxxx 的前面部分即该容器的 id。编辑 config.v2.json,修改如下两部分

"Path":"/bin/sh", "Args":["-c","/usr/bin/frps -c /etc/frp/frps.ini"]
"Entrypoint":["/bin/sh","-c","/usr/bin/frps -c /etc/frp/frps.ini"]

"Path":"/bin/sh", "Args":["-c","/etc/frp/mystart.sh"]
"Entrypoint":["/bin/sh","-c","/etc/frp/mystart.sh"]

结果并没用,重启容器后又自动恢复原来的样子了。即使我用了 systemctl restart pkg-Docker-dockerd 重启了 docker 服务(当然所有容器也都重启了)也不行。

3、无奈只能修改 /usr/bin/frps

既然默认的启动命令是 /usr/bin/frps -c /etc/frp/frps.ini,那干脆通过文件映射把自己写的启动脚本 mystart.sh 映射为容器里的 /usr/bin/frps,这样就可以在容器启动时执行 mystart.sh 里的内容了。

五、保存被禁 ip 列表

首先是 iptables 设置的导入导出,标准做法是用 iptables-save > save.txt 进行保存设置,用 iptables-restore < save.txt来恢复设置。那么我想到的是通过 cron 任务定义保存设置,而恢复设置的命令则应写在开机运行脚本里。谁知默认的 crontab -e 里指定的 run-parts /etc/periodic/hourly 等命令并不管用。/etc/periodic/ 目录下有 15min、hourly、daily、weekly、monthly目录,默认都是空的,我把可执行脚本放入其中一个,比如15min里,结果并不管用。直接手动运行 run-parts /etc/periodic/15min 也会报错。干脆不用 run-parts 相关的目录设置,直接在 crontab -e 里设置运行命令,发现依然不运行!原来是 crond 守护进程根本没有启动! 只需运行一下 crond 命令即可启动,且自动后台运行了。于是按上面方案保存和恢复 iptables 设置,为测试,事先将已经有三个被禁ip的设置手动保存,结果重启后发现在容器里运行 iptables -L 时相当慢,三个被禁的ip半天出一个。而且发现 frptables 的日志也似乎不正常,本来 -1 -> 3-> 后面的计数都成了 0,按说 0 是强制封禁,但是也没有对相应 ip 封禁,且日志中相应 ip 后的地区识别也没有,都是空的。

于是尝试不使用 iptables-restore 恢复,因为 iptables-save 的输出中除了含有 DROP 命令的相关行是真正要封禁的 ip 外还有好多其他设置,而这些其他的设置中的有些数值在每次重启容器时还会有所不同。于是想到只提取 iptables-save 里含 DROP 的行(下面 -A 及之后的部分),并在前面加上 iptables 就成了可以运行的导入命令,如

iptables -A INPUT -s 119.167.99.194/32 -j DROP                                                                                                                                                              
iptables -A INPUT -s 149.129.250.12/32 -j DROP                                                                                                                                                              
iptables -A INPUT -s 154.221.19.143/32 -j DROP  

然后在启动脚本中运行上述命令即可导入。实测表明这样导出导入没有出现问题,一切运行正常。

六、最终设置

容器没有设时区,导致默认时间差着八小时。于是把宿主机的 /etc/localtime 拷为 /mypath/to/frps/etc-localtime,并映射为容器的 /etc/localtime,然后时间就正常了。容器里的 /usr/bin/frps 是 0.43 版本,把它拷为宿主机的 /mypath/to/frps/frps-0.43。/mypath/to/frps 目录下 ls -F 有如下内容(其中*为可执行文件):

banlog/  etc-localtime  frps-0.43*  frps.ini  frps.log  frptables*
frptables-config.yml  iprules.txt*  mystart.sh*  save-iprules.cron*

其中启动脚本 mystart.sh 内容为

#!/bin/sh
/bin/sh /etc/frp/iprules.txt
/usr/sbin/crond
/etc/frp/frptables -c /etc/frp/frptables-config.yml &
/etc/frp/frps-0.43 -c /etc/frp/frps.ini 

save-iprules.cron 内容如下

# map this file to /var/spool/cron/crontabs/root 
# min   hour    day     month   weekday command 
*/15    *       *       *       *        /sbin/iptables-save |grep DROP|sort -u|sed 's/^/iptables /' >/etc/frp/iprules.txt 

最后是容器的文件映射,如下:

#    宿主机                            容器装载路径
/mypath/to/frps/                     /etc/frp/
/mypath/to/frps/etc-localtime        /etc/localtime
/mypath/to/frps/mystart.sh           /usr/bin/frps
/mypath/to/frps/save-iprules.cron    /var/spool/cron/crontabs/root

最后提示

如果某个 ip 被误封禁,只能手动修改 iptables,可参考如下命令:

iptables -L --line-numbers  #以编号形式列出所有规则
iptables -D INPUT 3   #删除编号为3的规则

你可能感兴趣的:(frps 防 ssh 暴力破解)