前言

我的服务器2次被攻击,一次是我端口映射到windows的远程桌面,但是居然被人在不停的爆破。第二次是我的后台管理页面,经历了 ACK Flood,让我甚至不能正常的ssh连接上服务器。于是我学习了linux怎么去配置防火墙,如何自动去拉黑IP。但是这个工作太过于繁琐,防火墙的配置非常复杂,线性的匹配各个规则,互相耦合。而且针对每个端口不同的用途,需要不同的防火墙设置。fail2ban的配置也比较复杂,尽管我之前熟悉linux的日志管理,但是每个应用的日志在不同的地方,还是带来了很多的麻烦。

我这才意识到,原来nginx是非常伟大的项目,只通过80和443端口,反向代理其他的本地端口,极大的减少了攻击面。而且速率控制等高级功能,可以在nginx上配置,不用繁琐的去配置各个端口的防火墙,我只要简单配置这两个端口的防火墙即可。Nginx 提供了一种单入口、多服务分发的方案,解决了下面的核心问题:

  • 安全风险:暴露的端口越多,越容易成为攻击目标(如 DDoS、Flood)。
  • 配置复杂:每个端口都需要单独配置防火墙规则、限速、日志记录等。
  • 管理成本高:难以对所有端口进行统一监控和审计。

另外原生的 iptables 配置起来也很麻烦,建议使用 UFW(Uncomplicated Firewall)或者其他有图形界面的防火墙。

核心思想:减小攻击面是最重要的做法。一台云服务器,开放的端口会包括基于HTTP的web(包括内容分发和后台管理),SSH 控制端口,以及一些通信的TCP和UDP端口。

原则1:如果不提供公网服务,只是个人使用,那么只考虑SSH隧道和组网

所有的管理后台,都不应该暴露在公网,而应该用隧道访问,或者组虚拟局域网网。但是SSH隧道的性能不如wireguard,而且需要在terminal中配置端口转发,我还是建议虚拟组网的方式。

原则2:使用nginx代理web,并且设置cloudflare WAF。TCP 和UDP服务,可以通过 Nginx Stream 模块处理

简单的说安全策略应该做好:

  1. 能用私钥文件就不要用口令,比如ssh。口令用随机口令,不要自己手动设置常用密码。
  2. 非公网服务就不应该允许外部访问,比如管理端口等。
  3. 登陆要加入双重验证以及字典爆破的保护。

组网+Nginx代理+端口敲门(可选)。

端口和流量管理

防火墙原理

iptables 是基于 Linux 内核的防火墙工具,其工作原理是通过内核中的 Netfilter 框架来处理和管理网络流量。iptables 的规则按照 匹配优先级操作链 的逻辑工作。

包匹配的基本流程

  1. 数据包进入 Netfilter:
    • 数据包从网卡或其他网络接口接收后,会进入 Netfilter 进行处理。
    • 数据包按 网络栈的不同阶段 进入不同的 iptables 链。
  2. 匹配规则的顺序:
    • 链内规则按顺序执行,从上到下逐条匹配,直到命中一条规则。
    • 如果没有匹配到规则,则执行链的默认策略(如 ACCEPT 或 DROP)。
  3. 动作类型:
    • 如果数据包匹配规则,iptables 可执行以下操作:
      • ACCEPT: 允许数据包通过。
      • DROP: 丢弃数据包,不做任何回应。
      • REJECT: 丢弃数据包,并向源发送一个 ICMP 错误消息。
      • LOG: 记录日志,但继续匹配后续规则。

设置防火墙

安装和配置 iptables

1
2
3
4
5
sudo apt update
sudo apt install iptables iptables-persistent xtables-addons-common -y
sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
sudo systemctl start netfilter-persistent
sudo systemctl enable netfilter-persistent

iptables-persistent: 保存和恢复防火墙规则。
xtables-addons-common: 提供扩展模块支持,如 PSD(Port Scan Detection)。
查看和清空现有规则

1
2
sudo iptables -L -n -v
sudo iptables -F

iptables -L -n -v: 查看当前防火墙规则。
iptables -F: 清空所有规则,确保配置从干净状态开始

防火墙默认策略

设置默认策略为丢弃所有流量,只允许规则显式放行的流量。

1
2
3
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT ACCEPT

拒绝无效连接

1
2
sudo iptables -A INPUT -m conntrack --ctstate INVALID -j LOG --log-prefix "INVALID-CONN: " --log-level 4
sudo iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

检测无效连接并记录日志,同时丢弃数据包。防止 ACK Flood 和异常数据包。

限制 Ping(ICMP 类型 8)

1
sudo iptables -A INPUT -p icmp --icmp-type echo-request -j DROP

禁止 ping 请求,防止 Ping Flood 攻击。

限制单 IP 的并发连接数

1
2
sudo iptables -A INPUT -p tcp -m connlimit --connlimit-above 100 -j LOG --log-prefix "CONNLIMIT-EXCEEDED: "
sudo iptables -A INPUT -p tcp -m connlimit --connlimit-above 100 -j DROP

限制单个 IP 的并发连接数,记录超限日志。 防止单 IP 恶意占用服务器资源。

限制SSH端口请求数

1
2
3
sudo iptables -A INPUT -p tcp --dport SSH端口 --syn -m conntrack --ctstate NEW -m limit --limit 10/s --limit-burst 20 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport SSH端口 --syn -j LOG --log-prefix "PORT-SSH端口: "
sudo iptables -A INPUT -p tcp --dport SSH端口 --syn -j DROP

限制端口 SSH端口 每秒新连接请求的数量。超限时记录日志并丢弃。

限制特定端口速率

1
2
3
sudo iptables -A INPUT -p tcp --dport 网站端口 --syn -m limit --limit 10/s --limit-burst 20 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 网站端口 --syn -j LOG --log-prefix "PORT-网站端口: "
sudo iptables -A INPUT -p tcp --dport 网站端口 --syn -j DROP

限制端口 网站端口 的连接速率,防止暴力破解。

检测和防御端口扫描

1
2
3
sudo modprobe xt_psd
sudo iptables -A INPUT -m psd --psd-weight-threshold 21 --psd-hi-ports-weight 3 -j LOG --log-prefix "PORT-SCAN-DETECTED: "
sudo iptables -A INPUT -m psd --psd-weight-threshold 21 --psd-hi-ports-weight 3 -j DROP

日志记录所有被丢弃的数据包

1
sudo iptables -A INPUT -m limit --limit 5/min -j LOG --log-prefix "my-iptables-dropped: " --log-level 4

限制日志频率,记录被丢弃的异常流量。

保存防火墙配置

1
2
sudo netfilter-persistent save
sudo netfilter-persistent reload

save 将规则保存到 /etc/iptables/rules.v4 文件中。

验证防火墙规则

1
sudo iptables -L -n -v --line-numbers

拉黑IP

安装和配置

Fail2Ban 是一个入侵防御软件,通过监控系统日志来阻止可疑的活动。它的工作原理如下:

  • 监控系统日志:持续扫描指定的日志文件(如 SSH、Apache 等服务的日志)
  • 识别攻击模式:根据预设的规则识别可疑行为,比如多次登录失败
  • 自动封禁:当某个 IP 地址在指定时间内触发规则达到阈值时,自动将其加入防火墙黑名单
  • 自动解封:被封禁的 IP 在设定的封禁时间后会自动解除限制

主要配置参数说明:

  • enabled:是否启用该规则(true/false)
  • mode:检测模式,可选 normal(默认)、ddos、extra 或 aggressive
  • maxretry:允许的最大尝试次数,超过后触发封禁
  • findtime:检测时间窗口(秒),在此时间内达到 maxretry 次数则封禁
  • bantime:封禁时长(秒)

Fail2Ban 通过这种方式可以有效防止暴力破解和 DOS 攻击等恶意行为。它不仅可以保护 SSH 服务,还可以配置保护其他服务,如 Web 服务器、邮件服务器等。

Fail2Ban 的默认行为是基于 action 配置的规则,如果设置了端口,那么只是不允许某个IP访问特定的端口,但是可以访问其他端口。

安装 Fail2Ban

1
2
3
sudo apt install fail2ban -y
sudo systemctl start fail2ban
sudo systemctl status fail2ban

确保已经运行之后,可以查看已经开启的服务。

1
2
3
4
➜ ~ sudo fail2ban-client status
Status
|- Number of jail: 1
`- Jail list: sshd

详细查看每一类的服务的情况

1
2
3
4
5
6
7
8
9
10
➜  ~ sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- File list:
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:

配置文件的地址所在 /etc/fail2ban/jail.conf复制默认配置到 jail.local,,Fail2Ban 会优先读取该文件中的配置。

1
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

新的配置文件中找到这一部份

1
2
3
4
5
6
7
8
9
[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

然后修改成这样,注意端口请修改成自己的。

1
2
3
4
5
6
7
8
9
[sshd]
enabled = true
mode = ddos
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
maxretry = 5
bantime = 3600
findtime = 600

随后重启服务:

1
2
sudo systemctl restart fail2ban
sudo fail2ban-client reload

测试

找一台机器,多次测试

1
ssh -p PORT fakeuser@IP

然后查看是否已经拉黑这个IP了 sudo fail2ban-client status sshd

自定义

既然 Fail2Ban 是根据日志的,那么就可以根据日志输出,如果出现多条某种日志,就拉黑对应的IP,我们开始来解决利用TLS耗尽资源的案例。下面是我遇到的 TLS 扫描,还有暴力攻击。

  • EOF:客户端与服务器建立连接后,直接关闭了连接。可能是恶意工具测试你的服务是否运行 TLS。
  • tls: first record does not look like a TLS handshake:客户端发送的数据不符合 TLS 协议。通常是恶意流量(例如 HTTP 请求尝试连接到 HTTPS 端口,或发送随机数据包)。

这里说明攻击者在进行TLS 握手探测,并且还在发送二进制垃圾数据。由于我们过滤掉了无效的数据包,这里就少了很多了。

1
2
3
4
5
6
7
8
var/log/syslog:Dec  1 11:28:29 服务器主机名 x-ui[86955]: 2024/12/01 11:28:29 http: TLS handshake error from 206.168.34.116:43752: EOF
/var/log/syslog:Dec 1 11:28:29 服务器主机名 x-ui[86955]: 2024/12/01 11:28:29 http: TLS handshake error from 162.142.125.206:59192: EOF
/var/log/syslog:Dec 1 12:06:42 服务器主机名 x-ui[88405]: 2024/12/01 12:06:37 http: TLS handshake error from 194.29.186.112:51720: unexpected EOF
/var/log/syslog:Dec 1 12:06:42 服务器主机名 x-ui[88405]: 2024/12/01 12:06:38 http: TLS handshake error from 194.29.186.112:52326: tls: first record does not look like a TLS handshake
/var/log/syslog:Dec 2 00:20:39 服务器主机名 x-ui[819]: 2024/12/02 00:20:39 http: TLS handshake error from 218.250.154.217:12927: read tcp IP:网站端口->218.250.154.217:12927: use of closed network connection
/var/log/syslog:Dec 2 00:21:41 服务器主机名 x-ui[819]: 2024/12/02 00:21:41 http: TLS handshake error from 218.250.154.217:26355: EOF
/var/log/syslog:Dec 2 14:31:27 服务器主机名 x-ui[287150]: 2024/12/02 14:31:27 http: TLS handshake error from 185.147.124.202:63273: tls: first record does not look like a TLS handshake
/var/log/syslog:Dec 2 14:43:21 服务器主机名 x-ui[287307]: 2024/12/02 14:43:21 http: TLS handshake error from 147.45.112.8:65265: tls: first record does not look like a TLS handshake

在 /etc/fail2ban/filter.d/ 目录下创建名为 x-ui.conf 的文件:

1
2
3
[Definition]
failregex = ^.*http: TLS handshake error from <HOST>:.*$
ignoreregex =

这样会过滤得到攻击者的IP。由于服务器只有我自己用,所以错误率其实非常低的。

编辑 /etc/fail2ban/jail.local ,出现5次同样的IP后拉黑。这里用上了设置好的过滤器。

1
2
3
4
5
6
7
8
[x-ui]
enabled = true
port = 网站端口
filter = x-ui
logpath = /var/log/syslog
maxretry = 5
bantime = 3600
findtime = 600

最后 fail2ban-client reload ,测试是否成功:

1
2
➜  ~ echo "Dec  3 09:44:56 服务器主机名 x-ui[287307]: 2024/12/03 09:44:56 http: TLS handshake error from 165.154.43.179:40674: local error: tls: unexpected message" >> /var/log/syslog
➜ ~ sudo fail2ban-client status x-ui

手动操作

下面是一些常用的 fail2ban 命令:

手动封禁 IP:

1
sudo fail2ban-client set sshd banip 192.168.1.100

手动解封 IP:

1
sudo fail2ban-client set sshd unbanip 192.168.1.100

查看 fail2ban 日志:

1
sudo tail -f /var/log/fail2ban.log

解封所有被封禁的 IP:

1
sudo fail2ban-client unban --all

使用Nginx代理

从安全的角度,直接将服务端口暴露在公网,就增加了攻击面。攻击者可以轻松扫描端口和指纹信息,尝试漏洞攻击。而且一些开发测试的端口,本身缺乏高级访问控制,无法限制来源 IP、速率或特定的访问策略。特别是这样可以隐藏端口了。

从性能的角度,直接服务需要每次处理完整请求,无法缓存静态内容,增加资源开销。高并发请求可能导致服务线程或进程资源耗尽。

主配置文件 (/etc/nginx/nginx.conf)

1
2
3
4
5
6
7
8
http {
# 定义速率限制和并发限制
limit_req_zone $binary_remote_addr zone=xui_limit:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=panel_limit:10m rate=2r/s;
limit_conn_zone $binary_remote_addr zone=xui_conn:10m;

# 其他 http 配置...
}

自行配置站点配置文件 /etc/nginx/sites-available/proxy-services.conf ,需要链接到 sudo ln -s /etc/nginx/sites-available/proxy-services.conf /etc/nginx/sites-enabled/

确保 Nginx 配置无语法错误:

1
2
3
➜  ~ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

重启服务

1
sudo systemctl restart nginx

检查端口是否开启

1
sudo netstat -tuln | grep 443

查看https是否设置成功。200表示请求成功,服务支持 HTTP/2 协议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
➜  ~ curl -I DOMAIN
HTTP/2 200
server: nginx/1.18.0 (Ubuntu)
date: Mon, 02 Dec 2024 08:06:50 GMT
content-type: text/html
content-length: 20307
cache-control: no-store
content-security-policy: default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; script-src 'self' 'unsafe-inline'; object-src 'none'; form-action 'self'; frame-ancestors 'self';
referrer-policy: same-origin
set-cookie: SID=iXIj/7ObNpar0pJ4oxD5ZxdcyuLH2xzj; HttpOnly; path=/; SameSite=Strict
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
x-xss-protection: 1; mode=block
strict-transport-security: max-age=31536000; includeSubDomains

端口敲门

实现端口敲门技术的主要目标是隐藏服务器上的关键端口(如 SSH),仅允许合法用户通过特定的访问顺序来临时打开该端口。这通常通过一个守护程序(如 knockd)来完成。以下是完整的实现步骤:

sudo apt install knockd -y 安装之后,配置文件通常位于 /etc/knockd.conf

编辑文件 /etc/knockd.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[options]
UseSyslog

[openSSH]
sequence = 7000,8000,9000 # 敲门的端口顺序
seq_timeout = 15 # 敲门的时间间隔,单位秒
command = /sbin/iptables -A INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
tcpflags = syn

[closeSSH]
sequence = 9000,8000,7000 # 关闭 SSH 的端口顺序
seq_timeout = 15
command = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
tcpflags = syn

sequence 是敲门的端口顺序。例如,用户必须按顺序访问 7000 -> 8000 -> 9000,才能打开 SSH 端口。seq_timeout 指的是完成整个敲门顺序的最大允许时间。

编辑 /etc/default/knockd 文件,将 START_KNOCKD 设置为 1,实现自启动。

1
2
sudo systemctl start knockd
sudo systemctl enable knockd

用knock工具,就能够实现啦。注意敲门的端口都是要打开的。

1
knock <服务器IP> 7000 8000 9000

虚拟局域网

方案比较

虚拟局域网(VPN)是一种安全的网络解决方案,可以让远程用户安全地访问服务器资源。以下是几种常用的VPN方案:

VPN方案 特点 优势 缺点
WireGuard 现代VPN协议 配置简单,代码量少。性能优秀,延迟低。内置Linux内核 多设备管理复杂,每个节点都必须配置所有其他节点的公钥,才可能完成点对点通信。.
Tailscale 基于WireGuard的商业方案 零配置,自动NAT穿透。 个人用户免费 自定义中继服务器复杂
ZeroTier 虚拟网络方案 支持点对点连接。 可创建多个虚拟网络。跨平台支持良好。提供免费套餐 初始连接较慢。网络延迟相对较高。免费版节点数量有限

wireguard 缺少公钥分发,导致每个节点要配置其他节点的公钥,很复杂。

对于个人用户来说,Tailscale 是最简单的选择,几乎不需要配置就能使用。但是中国大陆地区缺少中继服务器,导致路由延迟太高了。而且搭建derper中继很麻烦,所以不建议使用。

Tailscale 基础教程:部署私有 DERP 中继服务器

虽然还有其他的便利化的工具,用于生成wireguard的配置,比如netmaker。但是zoretier还是非常方便的。

ZeriTier

任何运行 ZeroTier 客户端的设备都可以成为一个 节点。节点可以是个人电脑、服务器、手机或物联网设备。每个节点都通过其唯一的 40 位地址(Node ID)进行标识。这些地址通过加密算法生成,并且是 ZeroTier 网络中的唯一标识符。

基础使用看这里:https://makefile.so/2022/05/27/install-zerotier/

Planet 节点是 ZeroTier 的根目录服务,全球只有一个,项目方建议使用它维护的planet节点。它用于管理网络的分布式地址映射(ID到IP和端口的解析)。它的功能类似于 DNS 系统,但作用于 ZeroTier 网络。并且还允许传递控制器配置给客户端节点。

Moon Peer 是一种特殊类型的节点,用于优化节点之间的连接。类似于DHT(分布式哈希表),节点通过Moon来定位网络中的其他节点。通过Moon建立起初始的通信路径,之后节点会尝试直接建立点对点连接。

这两类节点统称为root server。moon 节点无法代币planet节点,网络的初始配置依赖Planet 节点分发,不过如果已经配置完成,就不依赖 planet了。一般来说没必要自己部署planet节点,但是部署moon节点能提高路由的网速。但如果卫星根节点看起来更快或者没有其他可用选项时,它们会使用月球根节点。

ZeroTier的路径选择遵循以下逻辑:

  1. **优先点对点连接(P2P):**如果两个设备可以通过NAT打洞建立直接连接,ZeroTier会优先使用点对点通信。
  2. 次选MOON**作为中继:**如果点对点连接不可用,ZeroTier会选择MOON节点作为中继。
  3. 最终使用PLANET**作为中继:**如果MOON也不可用,流量会通过ZeroTier官方的PLANET节点中继。
1
2
3
curl -s https://install.zerotier.com | sudo bash # 安装
cd /var/lib/zerotier-one # 工作目录
sudo sh -c 'zerotier-idtool initmoon identity.public >> moon.json'

Private Root Servers | ZeroTier Documentation

按照这里的配置例子,修改moon.json文件。一台服务器也可以,如果你有多台服务器,记得roots里多台服务器的信息都写进去。

确保端口开放:每台Moon服务器的stableEndpoints中指定的端口(如9993)需要在防火墙中开放,确保外部可以访问。

IP地址必须是固定公网IP:stableEndpoints中的IP需要是Moon服务器的固定公网IP,否则客户端无法连接。

1
2
3
4
sudo zerotier-idtool genmoon moon.json # 签名
sudo mkdir moons.d # 移动到默认配置路径下
sudo mv 000000*.moon moons.d
sudo systemctl restart zerotier-one

客户端的配置,把签名后的文件,放在对应的文件夹即可。配置文件位置见:https://docs.zerotier.com/config/

MAC的重启命令比较特别,参考:https://docs.zerotier.com/faq-macos/

最后检查是否成功连接上了:

1
zerotier-cli listmoons

windows的配置

1
2
3
choco install zerotier-one --force
# 找到相关配置
Get-Service | Where-Object { $_.DisplayName -like "*ZeroTier*" -or $_.Name -like "*ZeroTier*" }

找到正确的服务名称之后 Restart-Service -Name "ZeroTierOneService"

一些优化

可以自定义DNS,然后就用特殊域名来代替虚拟IP,不过配置稍微麻烦,而且每个节点要单独配置,并且并非全局有效。所以不打算继续。

DNS | ZeroTier Documentation

1
2
3
4
5
6
7
➜  ~ zerotier-cli listpeers
200 listpeers <ztaddr> <path> <latency> <version> <role>
200 listpeers 778cde7190 103.195.103.66/9993;19480;95249 251 - PLANET
200 listpeers cafe04eba9 84.17.53.155/9993;19480;95270 223 - PLANET
200 listpeers cafe9ccda7 66.90.98.98/9993;19480;95095 407 - PLANET
200 listpeers cafe9efeb9 104.194.8.134/9993;19480;95296 200 - PLANET
200 listpeers de28f6de9a - -1 - LEAF

是可以看到延迟的,不过缺少显示信息的,是无法P2P连接,需要moon或者planet节点。