保护 VPS 安全的讨论与分享
本文仅供参考,文内提到的
Docker 干预 iptables 导致异常暴露端口的问题
,本博客有更为详尽的解决办法
前言
以下教程涉及的命令均基于 Debian 12. 此处约定: {}
及其括起来的内容为根据你实际情况需要替换的文本内容, 括起来的内容为说明, 如 ssh {user}@{server ip}
为 ssh 连接服务器的命令, 假设用户为 root
, 服务器 IP 为 114.5.1.4
, 则为 ssh [email protected]
系统更新
新机器到手, 应当立即进行软件包更新, 下面的教程将默认在系统所有软件包处于最新状态
1
apt update && apt upgrade -y && apt dist-upgrade -y && apt full-upgrade -y && apt autoremove -y
更新完成后建议重启一下:
reboot
仍然使用 Debian 10 或更旧版本的, 建议升级到 Debian 11 / 12. Debian 12 在我自己的 1c0.5g 的机器上也能流畅运行, 所以大可放心, 或者只升级到 Debian 11 问题不大. Debian 10 的 LTS 支持将在 June 30th, 2024 结束, 建议在此之前升级.
注意:
- 升级大版本/更新内核是有一定机率导致 GRUB 加载失败进而启动失败的, 除非你是物理服务器, 以及没有用过奇奇怪怪定制或修改的内核的 KVM 构架的 VPS 和云主机. 切记备份重要数据!
- OpenVZ 6 和 LXC 构架的 VPS 是无法升级的,因为没有自己独立的内核(所以, 除非灵车别碰 LXC, 老老实实买 KVM 虚拟化的机器)
- 再强调一遍: 一定要备份重要数据!
- 不要跨大版本升级, 一步步来, 例如从 Debian 9 升级 Debian 11, 先升级到 10, 再升级到 11, 依此类推.
Ref: Debian 10 升级 Debian 11
Ref: Debian 11 升级 Debian 12
完成系统更新后, 就可以下一步了.
SSH 安全篇
TL,DR: 务必配置密钥登陆, 避免使用密码登陆, 更不用说弱密码!
1. 配置密钥登陆
建议本地生成公私钥再手动上传公钥, 不要把 SSH 登陆私钥扔服务器上!
下列操作在本地 Powershell 执行
执行
ssh-keygen -o -a 256 -t ed25519
, 此处 ed25519 是加密算法的一种, 不用理会. 提示输入将私钥保存在哪里, 默认保存在C:\Users\{your user name}/.ssh/
, 文件名id_ed25519
1
2Generating public/private ed25519 key pair.
Enter file in which to save the key (C:\Users\Hantong/.ssh/id_ed25519):
随后提示输入密钥密码, 最好设置一下, 输入密码后回车, 能保证私钥不泄露也可以直接回车留空. 输入密码时, 不会有显示:
1
Enter passphrase (empty for no passphrase):
提示再输入一遍, 再输入一遍密码就行, 前面留空这儿也留空
1
Enter same passphrase again:
生成完毕, 提示公钥私钥的保存位置. 如下例子,
C:\Users\Hantong/.ssh/id_ed25519.pub
就是接下来将保存到服务器的公钥,C:\Users\Hantong/.ssh/id_ed25519
就是登陆用的私钥1
2
3
4
5
6Your identification has been saved in C:\Users\Hantong/.ssh/id_ed25519.
Your public key has been saved in C:\Users\Hantong/.ssh/id_ed25519.pub.
The key fingerprint is:
***
The key's randomart image is:
***用 Notepad 打开公钥文件
C:\Users\Hantong/.ssh/id_ed25519.pub
并复制内容到剪贴板备用下列操作在 VPS 执行
1
2
3
4cd ~/.ssh # 文件夹不存在就 mkdir ~/.ssh
nano authorized_keys # 编辑 authorized_keys, 将刚刚复制的公钥文件内容粘贴进去, 保存即可
chmod 600 authorized_keys # 配置文件权限
systemctl restart sshd # 重启 SSH 服务尝试用私钥登陆, 登陆成功证明前面的操作没问题
Powershell 执行
ssh -i "{/entire/path/to/your/privkey/with/filename}" {user}@{server ip}
, 按提示来就行, 提示保存到 known_host 就 yes, 前面给私钥设置了密码提示输密码就输密码.配置 sshd_config
此处命令配置 sshd_config, 新机器无脑照搬就行, 有自定义设置的就别直接执行,
cat <<'TEXT' > /etc/ssh/sshd_config
后TEXT
前的内容为 sshd_config 的内容, 自行手动编辑就行. 编辑前建议备份一下原来的.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57mv /etc/ssh/sshd_config /etc/ssh/sshd_config.b
cat <<'TEXT' > /etc/ssh/sshd_config
Include /etc/ssh/sshd_config.d/*.conf
# 端口, 默认22
#Port 22
# 监听地址相关, 不需修改
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::
# Ciphers and keying
#RekeyLimit default none
# 日志
# 指定将日志消息通过哪个日志子系统(facility)发送
SyslogFacility AUTH
# 指定日志等级
LogLevel INFO
# 鉴权
# 限制用户必须在指定的时限(单位秒)内认证成功
LoginGraceTime 2m
# 允许root用户登录
PermitRootLogin yes
# 指定是否要求sshd(8)在接受连接请求前对用户主目录和相关的配置文件进行宿主和权限检查
StrictModes yes
# 指定每个连接最大允许的认证次数
MaxAuthTries 6
# 最大允许保持多少个连接。默认值是 10
MaxSessions 16
# 是否开启公钥认证, 仅可以用于SSH-2. 默认值为"yes"
PubkeyAuthentication yes
# 是否允许密码验证
PasswordAuthentication no
# 是否允许空密码
PermitEmptyPasswords no
# 是否允许质疑-应答(challenge-response)认证
ChallengeResponseAuthentication no
# 是否通过PAM验证
UsePAM yes
# 是否允许X11转发
X11Forwarding yes
#X11DisplayOffset 10
#X11UseLocalhost yes
# 指定sshd是否在每一次交互式登录时打印 /etc/motd 文件的内容
PrintMotd no
# 指定sshd是否在每一次交互式登录时打印最后一位用户的登录时间
PrintLastLog yes
# 配置超时
TCPKeepAlive yes
ClientAliveInterval 120
ClientAliveCountMax 10
# 配置Pid
PidFile /var/run/sshd.pid
# Allow client to pass locale environment variables
AcceptEnv LANG LC_*
# override default of no subsystems
Subsystem sftp /usr/lib/openssh/sftp-server
TEXT
service sshd restart
到这 SSH 就非常安全啦~ 也有人建议用 fail2ban, 其实没啥必要, 密钥登陆除非服务器早已经被攻陷或者私钥泄露在量子计算机成熟前不可能被破解哈哈哈. 下一步, 我们配置防火墙~
防火墙篇
腾讯云等大厂的服务器都带有安全组, 主机就基本不用另外安装防火墙了, 其余商家默认认为没有安全组, 需要我们自己在 VPS 上配置防火墙, Debian 系列使用 ufw.
基本命令(新机到手执行):
1
2
3
4
5
6
7
8
9
10apt update && apt upgrade -y
apt install ufw -y
# 安装完毕后, 开始配置 ufw
ufw default deny incoming # 默认阻止入站
ufw default allow outgoing # 默认允许出站
# ufw allow 9993 # 如果要用 Zerotier, 记得允许 9993 进出站
ufw allow 22 # SSH 端口, 根据实际情况填写端口
ufw allow 443 # HTTPS
# 立即启用 ufw, 提示可能中断当前 SSH 连接, 按 y 继续即可
ufw enable常用命令参考(更多此处没列出的命令请直接执行
ufw
, ufw 会告诉你用法, 或者 Google 一下)启用 UFW:
ufw enable
禁用 UFW:
ufw disable
列出当前活动规则(详细地):
ufw status verbose
列出当前活动规则(带序号):
ufw status numbered
# 此处序号标识了此条规则允许某 CIDR 访问本机所有端口:
ufw allow from {CIDR}
阻止某 CIDR 访问本机所有端口:
ufw deny from {CIDR}
允许访问某端口:
1
ufw allow {PORT}
- 对某一 CIDR 暴露某端口:
ufw allow from {CIDR} to any port {PORT}
- 对某一 CIDR 暴露某端口:
阻止访问某端口:
1
ufw deny {PORT}
- 对某一 CIDR 阻止访问某端口:
ufw deny from {CIDR} to any port {PORT}
- 对某一 CIDR 阻止访问某端口:
删除某规则:
1
ufw delete {RULE}|{NUM}
- 可以是你原来执行的命令 RULE, 如原来执行了
ufw allow 443
, 要删除此规则, 就是ufw delete allow 443
, 其他依此类推 - 还可以是前面提到的规则序号 NUM
- 可以是你原来执行的命令 RULE, 如原来执行了
将规则 RULE 插入规则列表位置 NUM:
1
ufw insert {NUM} {RULE}
, 如
1
ufw insert 1 allow 443
表示将规则
1
allow 443
插入到规则列表第一位
- 注意: 此处的规则列表位置 NUM 应当区分于前面提到的
序号
, 因为 ufw 规则包括 v6 规则, 而使用ufw status numbered
列出规则列表时, v6 的规则的规则是接着 v4 的继续列下去的.
- 注意: 此处的规则列表位置 NUM 应当区分于前面提到的
更多的
- 建站用途, 且使用 Cloudflare CDN, 完全可以不暴露 HTTPS 端口, 服务器只对外暴露个 SSH 端口, 安全得一批, 具体参见后面将要提到的
Cloudflared
. - 服务器只对少数人提供服务, 不面向公网, 或者部分服务不希望暴露到公网, 可使用虚拟内网方案, 参见后面.
- 建站用途, 且使用 Cloudflare CDN, 完全可以不暴露 HTTPS 端口, 服务器只对外暴露个 SSH 端口, 安全得一批, 具体参见后面将要提到的
Docker 干预 iptables 导致异常暴露端口的问题
安装 Docker 后, 务必编辑 /etc/docker/daemon.json
(没有就新建一个), 设置 ip
为 127.0.0.1
, 防止 docker 自己修改了 iptable 导致 ufw 失效. 修改完毕后执行 systemctl daemon-reload && systemctl restart docker
重启 Docker 服务.
注意, /etc/docker/daemon.json
应当是有效的 json. 下面是个参考(更多地: 开启了 IPv6 支持, 配置了 DNS. 国外服务器自行替换为 1.1.1.1
, 8.8.8.8
):
1 | { |
注意: 亲测不能修改 /lib/systemd/system/docker.service
加上 --iptables=false
, 否则 Docker 无法启动. 修改 /etc/docker/daemon.json
加上 { "iptables" : false }
本质一样.
Ref: https://webcache.googleusercontent.com/search?q=cache:QqmiCta8mNwJ:https://blog.gujiakai.top/2023/03/will-docker-container-be-limited-by-firewall.html&cd=14&hl=zh-CN&ct=clnk
Ref: https://askubuntu.com/questions/652556/uncomplicated-firewall-ufw-is-not-blocking-anything-when-using-docker
Ref: https://docs.docker.com/network/packet-filtering-firewalls/
Nginx 篇
Nginx 泄露源站证书, 导致源站暴露是相当常见问题了. 自 1.19.4 起, Nginx 支持 ssl_reject_handshake
参数, 设置为 on, 当客户端传过来的 SNI 与已配置的 server name 都不匹配时, 会拒绝 SSL 握手, 进而避免证书泄露.
以下示例基于 Nginx 1.25.1, 配置文件保存在 /etc/nginx/conf.d 目录下, 这也是通过 apt 安装 nginx 的默认位置. cat <<'TEXT' > nxdomain.conf
后 TEXT
前即为配置文件内容. 使用非正常途径安装的 nginx 请自行找配置文件目录, 新建个 nxdomain.conf 文件把内容贴进去就行.
注意, 在此之前在别的配置文件配置了 default_server 的, 记得去删掉, 虽然没删掉 nginx 也启动不了.
1 | cd /etc/nginx/conf.d |
使用旧于 1.19.4 的 nginx, 自签证书吧, 此处按下不表 (虽然我也在配置文件里体现了)
DNS 及 SSL 篇
TL,DR: 不要设置 DNS 直接指向源站, 使用泛域名证书
帮助好几个 UP 排查源站泄露的原因, DNS 泄露是个相当常见的原因. 所以, 不要设置 DNS 直接指向源站! 就算后面改为 CNAME 到 CDN 的域名, DNS 记录是可以查历史的!
其次, 子域名爆破是查源站常用方法了, 有个非常好用的查子域名的方法是 crt.sh, 原理是查 SSL 证书颁发记录, 所以, 推荐使用泛域名证书.
还有 RDNS, 不过一般没人会将自己服务器 IP 的 RDNS 配置为自己的域名, 许多商家也没提供这个功能, 此处按下不表.
虚拟内网篇(Zerotier)
本处只介绍 Zerotier 组建虚拟内网, tailscale 等其他虚拟内网方案我没用过.
为什么要虚拟内网? 原因很简单, 搭建非公开的服务, 如个人的 emby 媒体库服务只给认识的人用, 还有自己管理的服务器间通过 socks5 等非加密代理协议访问对方, 使用虚拟内网服务器无需暴露相应端口, 大大降低安全风险. 好处多多可以说了. 使用虚拟内网后, 服务器只需要暴露 9993 端口(zerotier), SSH 都不用暴露在公网, 除非 zerotier 出了致命零日漏洞, 否则安全的很.
当然, 多提一嘴, 由于国内 v4 普遍为 NAT4, P2P 不可能打洞, 导致客户端间互联只能 relay 模式通过国外服务器中转, 速度可想而知. 同时, zerotier 等虚拟内网方案普遍走 UDP (有 TCP 的欢迎踢我一脚我看看), 部分运营商对 UDP 的 QoS 相当严重, 或者服务器线路差, 虚拟内网连接质量也会很差, 所以也非万金油方案. 值得高兴的是, v6 的普及让 NAT4 的影响没那么大了, 只要双方有 v6, 通过 zerotier 轻松 P2P 直连. 这就是题外话了.
回归正题, 如何组建 zerotier 内网? 此处仅介绍一般流程, 自建 moon / planet 等高级用法不做介绍.
官网 注册账号登陆
点击
1
Create A Network
, 下面就会出现一个 network, 点进去
Basics
设置 - `IPv4 Auto-Assign` 建议选择和家里内网错开的内网网址段, 别用 `192.168.**` 这种用烂了的. - `IPv6 Auto-Assign` 没必要, 不用管1
2
3
4
5
6
7
8
9
设置
- Network ID 网络 ID, 等下会用
- Name 网络名称, 你可以设置为自己喜欢的名字
- Access Control 配置网络是私有的还是公开的, 我们组自用的虚拟内网当然是 Private 啦
- ```
Advanced
在电脑/ VPS 上安装 zerotier 客户端, 官方 tutorial
Windows 管理员权限打开 Powershell, Linux 用 root 登陆 SSH, 然后执行
zerotier-cli join {Network ID}
, Network ID 就是前面提到的网络 ID.打开管理控制台 https://my.zerotier.com/network/{Network ID}, 在 Members 处你会看到刚刚加入的设备, 勾上 Auth 栏的复选框, 这个设备就正式成功加入这个虚拟内网中了~ VPS 侧执行一下
ifconfig
就能看到分配的 IP 了, 当然你也可以自己在控制台手动分配一个.防火墙记得允许 9993 端口进出站:
ufw allow 9993
P.S. Zerotier 官方版限制最多 25 设备, 如果设备非常多, 只能自建 zerotier, 此处不涉及, 一般也用不到.
假设 VPS 是虚拟内网 IP 是 10.10.0.1, 8000 端口建了个 Alist, 试试访问 10.10.0.1:8000? 能打开就是成功啦. SSH 也试试?
Cloudflare ZeroTrust Tunnel 篇
只能说, Cloudflare 是最伟大的 CDN 厂商 (暴论!) 免费扛 DDoS, 一堆免费好用的功能, 除了国内访问速度慢点而已…
此处介绍通过 Cloudflare ZeroTrust 里面的 Tunnel 功能做到服务器不暴露 HTTPS 端口建站, 安全性 UP 一大截. 我们后面默认你已经注册并用过了 Cloudflare.
- 安装 cloudflared, 官方 Tutorial: https://pkg.cloudflare.com/index.html, 此处只给出 Debian 12 的安装命令
1 | sudo mkdir -p --mode=0755 /usr/share/keyrings |
打开
Cloudflare 仪表板
, 左侧找到进入 ZeroTrust 控制台, 左侧依次点击
1
Access
->
1
Tunnel
, 点击
1
Create a tunnel
创建 Tunnel
- Tunnel name 填个名字, 下一步
- Choose an operating system, 点击 Debian, 复制右下方
If you already have cloudflared installed on your machine:
处的命令sudo cloudflared service install ***
到 VPS 终端执行. 连接成功后下面的 Connectors 就会显示出来, 下一步 - 填写 Subdomain, 我个人按照
国家-地区-VPS商家-cf-tunnel
来规范命名, 如cn-hk-ali-cf-tunnel
; Type 选择 HTTP 或者 HTTPS (用 Cloudflare Tunnel 连接源站也没啥必要 SSL 了, 直接用 HTTP 没问题, HTTPS 还有证书问题挺麻烦的), Url 按实际情况填, 如127.0.0.1:80
或127.0.0.1:443
. 使用 HTTPS 时, 源站若使用自签证书务必在下面TLS
处把No TLS Verify
勾上, 顺带也可以开个 HTTP2:HTTP2 connection
- 特殊地, 若服务器为 v6 only, 需要编辑
/etc/systemd/system/cloudflared.service
文件, 找到ExecStart=/usr/bin/cloudflared --no-autoupdate tunnel run --token ***
这行,/usr/bin/cloudflared
后面加个参数--edge-ip-version 6
, 即:ExecStart=/usr/bin/cloudflared --edge-ip-version 6 --no-autoupdate tunnel run --token ***