VPS基本安全措施

2026-04-29 09:062阅读0评论SEO基础
  • 内容介绍
  • 文章标签
  • 相关推荐
问题描述:

前言

当我们拿到 VPS 之后,我们都需要做些什么呢?

如果选择将网站 / 服务放在知名厂商虚拟主机上,虚拟主机的厂商会负责基本的服务器安全措施。但如果放在 VPS 上,那么你就是服务器的安全负责人了。更多的权限代表着更多的义务,VPS 在具有更高的自由度的情况下自然有更高的风险。而我们要做到的不是绝对安全,而是比大多数人安全。 只要做到没那么容易被攻破那就是胜利。

本文使用的环境为 Ubuntu 24.04 LTS,其他发行版可大致参照思路。

在继续阅读本文之前,建议提前阅读由 LUG@USTC 编写的:https://101.lug.ustc.edu.cn/

[!note]
本文与 GitHub - ustclug/Linux101-docs: Linux 101 学生课程教材与讲义 · GitHub 项目无关

佬友们可以看看本文有没有什么需要补充的(

安全管理系统

有些 VPS 厂商默认提供的是 root 账户。众所周知,root 账户拥有整个系统最高的权限,这么高的权限自然是不安全的。正确的做法是创建一个非 root 账户,在必须使用 root 权限时使用 sudo 提权。

创建非 root 账户

使用以下命令创建一个具有提权能力的账户:

useradd -m -G sudo -s /bin/bash 用户名

然后我们给这个用户设置一个至少为 16 位的随机大小写字母 + 数字的密码(个人建议的最低安全性需求):

passwd 用户名 wordpure:

建议把 root 用户的密码也改掉,云服务器的默认密码强度还是差点,而且有些服务商会通过邮件来发送默认密码,不太安全。

禁用 root SSH 密码登陆

先不提 root 登录本身就是危险的行为,root 账户的用户名固定为”root”,如果允许其通过密码登录,攻击者只需进行密码穷举即可尝试攻破系统。之前我们已经创建了非 root 账户,在这里我们只需要禁用 root 账户的 SSH 登录即可。

执行以下命令编辑 SSH 配置文件:

sudo vim /etc/ssh/sshd_config

进行如下设置:

Oganneson:

# 禁止 Root 用户通过密码远程登录 PermitRootLogin prohibit-password

之后重启 SSH 服务生效:

sudo systemctl restart ssh

为什么不设置成no:

庄sir:

另,直接禁掉root登录PermitRootLogin no我也不常用,更习惯和其他sudoer一样配密钥然后禁止密码登录仅允许密钥登录。我是一般能操做线下集群的机子才会这么配,不然出个故障没root用不了了,比如存储满了远程ssh session都建立不了。

Oganneson:

理论上PermitRootLogin是好的,但在服务器寄掉需要救援时就会变麻烦,我其实更推荐只关闭远程密码登录,保留本地密码登录,出问题时可以通过vnc或者ipmi本地登录root进行救援。

修改 SSH 端口号

章北海:

首先就要把22端口改了。

中文网络寻呼机🔰:

这是参照原帖里的基础上补充~
实际端口一般登上去就会改掉

Lao Qian:

登上去第一件事就是改SSHD的 端口并修改防火墙,22全是猜密码的请求。
有个小技巧,就是改端口并重启 sshd后当前的连接并没有断,向新端口发请求,能连上就是修改正确,要是不能建立新连接,还可以改回来,或查下防火墙的配置。

正常情况下,直接通过sudo vim /etc/ssh/sshd_config修改SSH端口,然后再使用sudo systemctl restart ssh.service重启SSH服务应用更改是可行的:

Oganneson:

# 设置 SSH端口 Port 自拟

但是在Ubuntu 22.10 或更高版本中各位可能发现这是无效的,各位会发现SSH服务在重启后依然监听原端口。

因为在Ubuntu 22.10 或更高版本中,ssh 默认通过套接字激活。

在 Ubuntu 22.10、Ubuntu 23.04 和 Ubuntu 23.10 中进行修改的方法是:

sudo mkdir -p /etc/systemd/system/ssh.socket.d sudo vim /etc/systemd/system/ssh.socket.d/listen.conf sudo systemctl daemon-reload sudo systemctl restart ssh.socket sudo systemctl restart ssh.service

listen.conf的参考配置为:

[Socket] ListenStream= ListenStream=2233

在 Ubuntu 24.04 中进行修改的方法是:

sudo vim /etc/ssh/sshd_config sudo systemctl daemon-reload sudo systemctl restart ssh.service

如果不在乎通过套接字激活节省的内存,可以通过以下命令恢复到非套接字激活:

[!caution]
警告: 务必确认配置文件正常。

sudo systemctl disable --now ssh.socket sudo systemctl enable --now ssh.service

如有配置迁移(Ubuntu 22.10及以上,Ubuntu 24.04以下):

sudo systemctl disable --now ssh.socket rm -f /etc/systemd/system/ssh.service.d/00-socket.conf rm -f /etc/systemd/system/ssh.socket.d/addresses.conf sudo systemctl daemon-reload sudo systemctl enable --now ssh.service wordpure:

建议在 /etc/ssh/sshd_config.d/ 创建一个 conf 文件来自定义 sshd 配置,而不是直接编辑 /etc/ssh/sshd_config,防止 OpenSSH 更新后配置冲突。

参考: ssh - PasswordAuthentication no, but I can still login by password - Unix & Linux Stack Exchange

图片1454×1325 193 KB


有些云服务商为了启用远程密码登录(sshd 默认禁用 ),会在 /etc/ssh/sshd_config.d/ 自定义一个 conf 文件,修改 sshd 配置前先要排除它们的干扰。

# 查看 sshd_config.d 是否存在其他 conf 文件 sudo ls /etc/ssh/sshd_config.d/*.conf # 如果存在,重命名,防止后续自定义配置被覆盖 sudo mv /etc/ssh/sshd_config.d/xxx.conf /etc/ssh/sshd_config.d/xxx.conf.bak

比如 CloudCone 就有一个 /etc/ssh/sshd_config.d/50-cloud-init.conf

图片1028×305 11.3 KB


sshd 配置修改完,先用 sudo sshd -T 看一下有效配置,免得被覆盖了都不知道。

# Root 用户登录方式 sudo sshd -T | grep -i "PermitRootLogin" # 密码认证 sudo sshd -T | grep -i "PasswordAuthentication" # ssh 端口 sudo sshd -T | grep -i "Port"

值得一提的是,prohibit-passwordwithout-password 的别名,所以看到下面的输出是正常现象。

参考:

  • Permit root to login via ssh only with key-based authentication - Unix & Linux Stack Exchange
  • OpenSSH 7.0 更新日志

Fail2ban 防暴力破解 SSH

执行以下命令安装 Fail2ban:

sudo apt install fail2ban

官方推荐的做法是利用 jail.local 来进行自定义设置:

sudo vim /etc/fail2ban/jail.local

可以参照以下配置文件来进行自己的配置(记得删注释):

[sshd] ignoreip = 127.0.0.1/8 # 白名单 enabled = true filter = sshd port = 22 # 端口,改了的话这里也要改 maxretry = 5 # 最大尝试次数 findtime = 300 # 多少秒以内最大尝试次数规则生效 bantime = 600 # 封禁多少秒,-1是永久封禁(不建议永久封禁) action = %(action_)s[port="%(port)s", protocol="%(protocol)s", logpath="%(logpath)s", chain="%(chain)s"] # 不需要发邮件通知就这样设置 banaction = iptables-multiport # 禁用方式 logpath = /var/log/auth.log # SSH 登陆日志位置

通知服务器SSH登录

kendo:

很有用,我还加了一个在登录 ssh 时候自动发通知到企业微信机器人,以防偷家不知道

可以通过 PAM 模块在每次ssh登录时触发脚本来实现。

编辑/etc/pam.d/sshd,在文件末尾添加:

session optional pam_exec.so 脚本路径

对于提到的用例,脚本大致如下:
参照:消息推送配置说明 - 文档 - 企业微信开发者中心

2024年11月26日注:修复了示例脚本的一些问题。

# 创建脚本存放路径 mkdir -p /opt/scripts # 写入脚本内容 cat > /opt/scripts/login_notify.sh <<'EOF' #!/bin/bash # 只在会话建立时触发,过滤掉 close_session 等类型 if [ "$PAM_TYPE" != "open_session" ]; then exit 0 fi # 1. 获取基础变量 user_name=$PAM_USER remote_ip=$PAM_RHOST login_time=$(date +"%Y-%m-%d %H:%M:%S") # 2. 获取本机信息 local_hostname=$(hostname) # 获取本机公网IP (增加3秒超时,防止获取失败导致登录卡顿) local_ip=$(curl -s --connect-timeout 3 ifconfig.me || echo "未知/内网") # 3. 配置 Webhook 地址 webhook_url="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=b14bc0dc-xxxx" # 4. 发送通知 # --connect-timeout: 连接超时 # --max-time: 整个请求超时 curl -s -X POST "$webhook_url" \ --connect-timeout 5 \ --max-time 10 \ -H "Content-Type: application/json" \ -d "{ \"msgtype\": \"markdown\", \"markdown\": { \"content\": \"### 🖥️ 服务器登录提醒\n> **登录机器**: $local_hostname ($local_ip)\n> **登录用户**: <font color=\\\"info\\\">$user_name</font>\n> **客户端IP**: $remote_ip\n> **登录时间**: $login_time\" } }" > /dev/null 2>&1 EOF chmod +x /opt/scripts/login_notify.sh cat >> /etc/pam.d/sshd <<'EOF' session optional pam_exec.so /opt/scripts/login_notify.sh EOF

请根据自己的用例替换api及调用方式。

用root用户执行上述脚本一键部署。

【严格】使用绝对路径运行命令

使用绝对路径可以精准指定要运行的程序或文件,可避免因环境变量被篡改等原因误执行潜在的恶意程序。

libook:

使用 su 的完整路径可以避免运行潜在的攻击者植入的 su。

使用密钥登录

[!note]
如果 VPS 厂商提供了 SSH 密钥绑定功能,可以忽略本节内容并按照 VPS 厂商提供的方法绑定。

在 powershell 中运行:

ssh-keygen -t ed25519

直接使用默认的密钥路径即可。密码可以留空,也可以设置。

Generating public/private ed25519 key pair. Enter file in which to save the key (C:\Users\<user>/.ssh/id_ed25519): # 直接回车 Enter passphrase (empty for no passphrase): # 可以留空,也可以设置 Enter same passphrase again: # 和上一个一样

然后我们在 VPS 上编辑 SSH 授权密钥文件:

vim ~/.ssh/authorized_keys

之后打开 C:\Users<user>/.ssh/id_ed25519.pub,复制其内容并粘贴过去。

执行以下命令编辑 SSH 配置文件:

sudo vim /etc/ssh/sshd_config

进行如下设置:

PubkeyAuthentication yes AuthorizedKeysFile .ssh/authorized_keys PasswordAuthentication no

之后重启 SSH 服务生效:

sudo systemctl restart ssh

启用 UFW 防火墙

[!note]
如果 VPS 厂商提供了防火墙功能,且没有复杂的需求,可以忽略本节内容并使用 VPS 厂商提供的防火墙。

在正式启用 UFW 之前,我们需要先设置规则。我们首先来设置 UFW 的默认行为:

sudo ufw default allow outgoing # 默认允许所有数据出站 sudo ufw default deny incoming # 默认禁止所有数据入站

我们可以通过以下命令查看 UFW 当前生效的规则:

sudo ufw status sudo ufw status numbered # 加上数字编号

我们可以通过以下命令允许或拒绝某端口的传入 / 传出流量(部分以 22、80、443 端口为例):

# 允许22端口的proto协议的流量入站 sudo ufw allow in 22/proto #允许22端口的proto协议的流量出站 sudo ufw allow out 22/proto # 在未指定in/out的情况下,默认为in sudo ufw allow 22/proto # 在未指定proto的情况下,默认为tcp和udp sudo ufw allow 22 # 拒绝的话就把allow改成deny sudo ufw deny 22 # 允许从start_port到end_port的端口 sudo ufw allow start_port:end_port # 允许复数个端口,以英文逗号分隔 sudo ufw allow port1,port2 # 允许来自于特定ip或cidr段的流量 sudo ufw allow from ip/cidr # 允许来自于特定ip或cidr段端口22的流量 sudo ufw allow from ip/cidr to any port 22 # 允许来自于特定ip或cidr段端口22的tcp协议的流量 sudo ufw allow from ip/cidr to any proto tcp port 22 # 如果指定复数个端口,则必须指定协议 sudo ufw allow from ip to any proto tcp port 80,443 # comment用于注释 sudo ufw allow from ip to any proto tcp port 80,443 comment "hello"

我们可以通过以下命令删除生效的规则:

sudo ufw delete allow 22 # 在规则前面加个delete sudo ufw delete 1 # 按照numbered的编号删除也行

在确定所有规则均成功设置后,通过以下命令启动 \ 关闭 \ 重启 UFW

[!caution]
启动防火墙前务必保证 22 端口(或者其他 SSH 端口)被放行。

sudo ufw enable|disable|reload

如果需要重置规则,请使用:

[!caution]
重置规则前务必保证 UFW 处于关闭状态。

sudo ufw reset

本人建议仅放行正在使用的端口,比如22、80、443。

默认情况下,UFW仅记录不符合规则的被拒绝的数据包。如果需要记录与该服务相关的每个详细信息,可以在allow后加上log以进行记录。

中文网络寻呼机🔰:

# 成功连接 ssh 的也记一下日志备查比较好 ufw allow log 22/tcp

禁止ping服务器

De:

我之前还会进行禁止ping的设置。

1. 使用 iptables 禁止ping - 查看当前iptables规则: - iptables -L -n - 这条命令可以列出当前iptables的规则,-L表示列出规则,-n表示以数字形式显示 IP 地址和端口号,而不是解析为主机名和服务名。 - 禁止 ping(入站 ICMP Echo Request): - iptables -A INPUT -p icmp --icmp - type 8 -j DROP - 这里-A INPUT表示在INPUT链(用于处理进入本机的数据包)的末尾添加一条规则。-p icmp指定协议为 ICMP,--icmp - type 8表示 ICMP 类型为 8(即 Echo Request),-j DROP表示将匹配的数据包丢弃。 - 保存iptables规则(如果需要永久生效): - 对于不同的 Linux 发行版,保存iptables规则的方式不同。 - 在 CentOS 等基于 RHEL 的系统中,可以使用service iptables save命令来保存规则。这条命令会将当前的iptables规则保存到/etc/sysconfig/iptables文件中,这样在系统重启后规则依然生效。 - 在 Ubuntu 等 Debian 系系统中,需要安装iptables - persistent软件包。安装后可以使用iptables - save > /etc/iptables/rules.v4(对于 IPv4 规则)来保存规则,这样在系统重启后也能恢复规则。 - 恢复 ping 功能 如果要恢复 ping 功能,可以删除刚才添加的禁止 ping 的规则。使用iptables -D INPUT -p icmp --icmp - type 8 -j DROP命令。其中-D INPUT表示从INPUT链中删除规则,其他参数和添加规则时相同。

保守起见,与destination-unreachable、time-exceeded、parameter-problem相关的规则已移除。

鸦羽:

ufw本身没有直接支持阻止icmp协议的命令。ufw在/etc/ufw/before.rules中定义了针对ping的允许规则,我们可以修改ACCEPT为DROP来达成禁止ping的目的:

-A ufw-before-input -p icmp --icmp-type echo-request -j DROP -A ufw-before-forward -p icmp --icmp-type echo-request -j DROP

永恒:

佬,这个好像只能禁v4的,发现v6还可以ping

如果需要设置ipv6的禁ping规则,可以修改/etc/ufw/before6.rules

-A ufw6-before-output -p icmpv6 --icmpv6-type echo-request -j DROP -A ufw6-before-output -p icmpv6 --icmpv6-type echo-reply -j DROP

注意:其余icmpv6规则应保持不变

限定SSH登录IP

Motor:

我办公室的网络是固定 ip,VPS 运营商的防火墙开 22 端口然后指定 IP 地址,这样以后需要别的 IP 访问的话,直接添加 IP 即可

如果拥有动态公网IP且厂商支持通过接口修改防火墙规则,可以直接使用厂商的接口。

seamee:

如果是家里没有固定ip的可以使用类似腾讯云的orcaterm终端来访问,基础功能是永久免费的,这是orcaterm的IP段,然后用ipset把这些IP段添加进去,设置为仅这些IP可以访问ssh端口即可,记得设置之前先测试是否可以正常使用连接。如果是腾讯云用户,安全组里甚至可以把ssh端口直接关掉,之前用腾讯云的时候就只开了80和443端口

也可以使用ufw进行设置。

还可以使用VPS厂商提供的防火墙(如果支持),如果出现连接问题更方便更改配置。

orcaterm添加其他厂商云服务器步骤:

image238×140 6.66 KB

image261×128 3.71 KB

image626×609 34 KB

保证软件更新

日常更新系统

个人建议定期登录 VPS 运行sudo apt update && sudo apt upgrade来保证 VPS 内所有软件包均为最新。

不过 Ubuntu 默认会每天自动安装系统的安全更新,所以说这个频率没必要太勤。

开启 Ubuntu Pro

同样出色的操作系统,更多的安全更新
将平均 CVE(通用漏洞披露)暴露时间从 98 天减少到 1 天
具备扩展的 CVE 补丁、十年的安全维护、可选的支持和对整个开源应用程序堆栈的维护。

上面是 Ubuntu 官方的广告词。到底多有用我不知道,有修复总比没修复好,而且个人免费 5 台机器,开了不亏。

我们先来创建一个 Ubuntu One 帐户:https://ubuntu.com/login

注册结束之后,转到 https://ubuntu.com/pro/dashboard 查看 Token。

1024×647 44.9 KB

得到了Token之后,前往我们的VPS,运行sudo pro attach [YOUR_TOKEN]。等待一段时间,我们的VPS就成功开启Ubuntu Pro了。建议在开启之后再运行一次sudo apt update && sudo apt upgrade以确保系统安装了最新的安全更新。

隐藏公网 IP

庄sir:

隐藏公网IP并不是所有VPS使用者的共同安全需求,有一个胡诌的针对未来(指ipv6广泛使用)的方案就是只暴露源站v6地址给CDN用,这样Censys这样强扫的工具耗时会很长,不过也还是要配白名单。

防止 SSL 证书泄露 IP

[!note]
本节 “防止 SSL 证书泄露 IP” 引自 “如何避免证书泄露源站 IP”,作者为秋未萌,根据 CC BY-SA 4.0 授权协议发布。本文其他部分根据 CC BY-NC-SA 4.0 授权协议发布,但本节内容使用 CC BY-SA 4.0 授权协议。

申请并下载证书

  • 注册并且登录 ZeroSSL
  • 到 Dashboard 找到 Create SSL Certificate,点击 New Certificate 蓝色按钮
  • 在 Enter Domains 处输入源站 IP
  • 其实过期也无妨,总之不让 censys 扫描到真正的域名证书就可以。因此选择 90 天证书
  • CSR & Contat 保持不变
  • 验证域名的办法选择 HTTP File Upload
    • 使用 NGINX 的话,如果你保持原先设置不变,即 /etc/nginx/sites-available/default 不变就没问题。当然,如果你改变了,记得保留 server {listen 80; root /var/www/html;} 就可以
    • 然后下载 Auth File,并且把它上传到 /var/www/html/.well-known/pki-validation。如果没有文件夹,就新建,记得让 NGINX 有权访问这些文件,否则还是会失败
    • 按照提示,点击一下.txt 文件是不是可以访问。成功的话,就验证好了

配置证书并且设置禁止 IP 80/433 的 HTTP 访问

  • 下载 *.zip 文件,解压它。解压好的文件夹里面有好申请到的证书
  • ca_bundle.crtcertificate.crt 合并,方法是用 notepad3 或者 vscode 等可靠的编辑器打开 certificate.crt。然后把 ca_bundle.crt 内容复制进去。格式是:

-----BEGIN CERTIFICATE----- certificate.crt内容 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- ca_bundle.crt内容 -----END CERTIFICATE-----

  • 上传合并好的文件和 private.key
  • 上传证书到一个 Nginx 有权限的文件夹,咱放在 /etc/nginx/ip-certificate/
  • 设置 /etc/nginx/sites-available/default 文件。参考设置如下:

server { #HTTP Server Default Set listen 80; listen 443 ssl http2 default_server; server_name ip; #HTTP_TO_HTTPS_END ssl_certificate /etc/nginx/ip-certificate/certificate.crt; ssl_certificate_key /etc/nginx/ip-certificate/private.key; ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; #Server ROOT index index.html; root /var/www/html/; index index.html; return 444; #NGINX HTPP Code 444 }

服务器后台输入 nginx -t 检查是否有误,正确即可 sudo systemctl restart nginx 重启。

直接输入 IP 作为网址检查是不是连接后立刻变成空白页

Nginx 1.19.4 之后的新方法

如果各位的 Nginx 版本大于等于 1.19.4,可以直接使用 ssl_reject_handshake 配置项,通过简单的配置就能拒绝所有未匹配到域名的 TLS 握手:

server { listen 443 ssl default_server; server_name _; ssl_reject_handshake on; }

如果出现如nginx: [emerg] unknown directive “ssl_reject_handshake”的报错,首先请检查nginx版本是否大于等于1.19.4。ssl_reject_handshake on; 在nginx 1.19.4 主线版加入。
如果是通过源代码编译安装的,请确认ngx_http_ssl_module模块是否启用。 该模块不是默认构建的,需要通过 --with-http_ssl_module 配置参数来启用。

真的安全了吗

前文中,我们只能确保攻击者无法通过直接访问 ip 获取默认证书来推断域名信息。然而又没有规定说攻击者只能用这种方式获取 IP 与域名的对应关系。可以看出,前文的规则依赖于 server_name 的匹配。攻击者完全可以携带正确的 server_name 握遍所有可能的非已知 CDN 的 IP 段,记录正确响应的目标。下面是判断(不包含遍历)的简单实现:

# https://gist.github.com/Raven95676/39ffdba22144e39d7155ad9dc1bcca55 import ssl import socket def check(ip, domain): try: context = ssl.create_default_context() with socket.create_connection((ip, 443)) as sock: with context.wrap_socket(sock, server_hostname=domain) as ssl_sock: request = f"GET / HTTP/1.1\nHost: {domain}\nConnection: close\n\n" ssl_sock.sendall(request.encode()) ssl_sock.recv(4096) return True except Exception: return False print(check("192.168.0.256", "example.com"))

于是我们打个补丁

如果 VPS 厂商提供了防火墙功能,可以直接使用 VPS 厂商提供的防火墙。

各位可以直接用这个方法

我们可以仅允许 CDN 的 CIDR 段访问服务器的 80/443 端口。先来添加允许的规则:

sudo ufw allow from "cidr段" to any proto tcp port 80,443 comment "CDN服务商"

comment 不是必须的,只是为了方便日后能认出来这规则是干什么的才加上的。

CDN 的 CIDR 段去找 cdn 服务商要,有的在官网也有公示。比如 Cloudflare 的CIDR 段可以从这个网页获取:https://www.cloudflare.com/ips-v4
上面那个是 ipv4 的,ipv6 的可以从这个网页获取:https://www.cloudflare.com/ips-v6

对于 Cloudflare ,还有佬友编写的脚本可以在执行后更新 CF IP 到 ufw 443。

避免网站被攻击的低成本想法

RULES=$(sudo ufw status numbered | grep 'Cloudflare IP' | awk -F"[][]" '{print $2}' | sort -nr) for RULE in $RULES; do echo "Deleting rule $RULE" echo "y" | sudo ufw delete $RULE done for cfip in `curl -sw '\n' https://www.cloudflare.com/ips-v{4,6}`; do ufw allow proto tcp from $cfip to any port 443 comment 'Cloudflare IP'; done ufw reload > /dev/null

由于特殊情况的存在,本人建议在部署该脚本之前先执行以下命令查看是否正确输出。

for cfip in `curl -sw '\n' https://www.cloudflare.com/ips-v{4,6}`; do echo $cfip; done

如果输出正常(如下),则可以部署:

173.245.48.0/20 103.21.244.0/22 …略 2400:cb00::/32 2606:4700::/32 …略

添加完了之后我们使用以下命令查看防火墙现有规则列表:

sudo ufw status numbered

使用以下命令删除之前可能存在的针对 80 和 443 端口允许所有流量的规则:

sudo ufw delete 序号

为什么不直接使用 Nginx 的 deny 配置项呢?因为 deny 会返回 403 Forbidden 状态码,而在此之前必须完成 TLS 握手。只有在 TLS 握手成功后,客户端才能发送 HTTP 请求并接收到响应。如果直接使用 deny,我们相当于在做无用功。

真的安全了……吗?

这其实算是 Cloudflare 特辑,不过如果使用其他 CDN 提供商,为了增强安全性也可以参考。众所周知,Cloudflare 不仅提供 CDN 服务,还有一系列其他产品,比如 Workers 和 WARP。而这些服务有一些需要注意的特点:

  • 能对外发出请求
  • 用的是 Cloudflare 的 IP 段

虽然 Cloudflare 对于滥用肯定是限制的,但是为了以防万一,我们还可以再做点安全措施 —— 经过身份验证的源服务器拉取。

必须确保SSL/TLS加密模式为完全或者完全(严格)

下载Cloudflare证书并进行配置即可。

ssl_client_certificate 证书地址; ssl_verify_client on;

然后在 SSL/TLS→源服务器这里开启经过身份验证的源服务器拉取。

雷池WAF社区版目前暂时无法稳定使用此方法,除非乐意在雷池前面再叠一层反代。

迈向全面容器化

Oganneson:

我的建议是善用容器化技术进行隔离

[!note]
本节暂不涉及1panel等运维面板或portainer等docker面板。

Docker基本使用

[!note]
发现成熟的文档,不再重复造轮子。
本文与 GitHub - yeasy/docker_practice: 最新Docker容器技术,从真实案例中学习最佳实践!| Learn and understand Docker&Container technologies, with real DevOps practice! · GitHub 项目无关
小小的偷个懒

附注:如果docker-compose提示找不到命令,试试docker compose。docker compose现已弃用version字段,现有的compose配置文件中version字段将被忽略并显示一条警告。

yeasy.gitbook.io

前言 | Docker 从入门到实践

更新:添加Docker官中文档。

https://docs.docker.net.cn/manuals/

UFW管不了Docker的解决方案(推荐)

Oganneson:

再写一点关于docker的问题吧,其实没必要用ufw来管理docker的端口开放,docker会自己写入iptables规则用以管控端口,以docker compose为例:

1. 对于数据库或者redis之类仅在应用内使用的服务:

  • 仅在容器内部网络内开放,compose拉起时会创建一个名为第一个服务名_default的bridge,可以用docker network ls查看。
  • 容器默认对该内部网络开放所有端口。
  • 同一网络内的其他容器可以通过容器名和端口访问该服务。
  • 没有加入该网络的容器无法访问任何端口。
  • 对于仅在内部网络中暴露端口的服务,无需ports 下指定任何映射

2. 对于需要进行反代的服务:

  • 仅在127.0.0.1监听即可防止外部端口访问,在对应容器处使用

ports: - 127.0.0.1:端口:端口

3. 对于需要外部直接访问端口的服务:

  • 直接使用

ports: - 端口:端口

同样的,贴一个示例compose.yml

services: rsshub: image: diygod/rsshub:latest restart: always ports: - 127.0.0.1:1200:1200 environment: NODE_ENV: production CACHE_TYPE: redis REDIS_URL: "redis://redis:6379/" PUPPETEER_WS_ENDPOINT: "ws://browserless:3000" env_file: - .env healthcheck: test: ["CMD", "curl", "-f", "http://localhost:1200/healthz"] interval: 30s timeout: 10s retries: 3 depends_on: - redis - browserless browserless: image: browserless/chrome restart: always ulimits: core: hard: 0 soft: 0 healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/pressure"] interval: 30s timeout: 10s retries: 3 redis: image: redis:alpine restart: always volumes: - ./data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 30s timeout: 10s retries: 5 start_period: 5s sb: image: ghcr.io/sagernet/sing-box container_name: sb restart: always volumes: - ./sing-box:/etc/sing-box/ command: -D /var/lib/sing-box -C /etc/sing-box/ run

此外,如果需要阻止外部端口访问,除去仅在127.0.0.1监听外还可以:

有容:

需要Nginx/Caddy等反代的服务,在Compose文件里可以配置一个Docker专用的172开头的内网ip,且不配置端口。这样Docker就不会暴露公共端口,反代地址写 内网ip:默认端口即可。


比如MySQL的Compose文件配置,指定了使用内网IP为172.20.0.15,注释了端口映射,在配置反向代理时 直接反代 172.20.0.15:3306 即可。

networks: default: external: true name: ${DOCKER_MY_NETWORK} services: mysql: container_name: mysql8 image: mysql:8 # ports: # - "3306:3306" environment: TZ: Asia/Shanghai networks: default: ipv4_address: 172.20.0.15 restart: unless-stopped

UFW管不了Docker的解决方案(不太推荐)

本节 “UFW管不了Docker的解决方案(不太推荐)” 引自项目 ufw-docker 的README,作者为 chaifeng,根据 GPL-3.0 license 授权协议发布。本文其他部分根据 CC BY-NC-SA 4.0 授权协议发布,但本节内容使用 GPL-3.0 license 授权协议。

目前新的解决方案只需要修改一个 UFW 配置文件即可,Docker 的所有配置和选项都保持默认。

修改 UFW 的配置文件 /etc/ufw/after.rules,在最后添加上如下规则:

# BEGIN UFW AND DOCKER *filter :ufw-user-forward - [0:0] :ufw-docker-logging-deny - [0:0] :DOCKER-USER - [0:0] -A DOCKER-USER -j ufw-user-forward -A DOCKER-USER -j RETURN -s 10.0.0.0/8 -A DOCKER-USER -j RETURN -s 172.16.0.0/12 -A DOCKER-USER -j RETURN -s 192.168.0.0/16 -A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN -A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16 -A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8 -A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12 -A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 192.168.0.0/16 -A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 10.0.0.0/8 -A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 172.16.0.0/12 -A DOCKER-USER -j RETURN -A ufw-docker-logging-deny -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW DOCKER BLOCK] " -A ufw-docker-logging-deny -j DROP COMMIT # END UFW AND DOCKER

然后重启 UFW,sudo systemctl restart ufw。现在外部就已经无法访问 Docker 发布出来的任何端口了,但是容器内部以及私有网络地址上可以正常互相访问,而且容器也可以正常访问外部的网络。可能由于某些未知原因,重启 UFW 之后规则也无法生效,请重启服务器。

如果希望允许外部网络访问 Docker 容器提供的服务,比如有一个容器的服务端口是 80。那就可以用以下命令来允许外部网络访问这个服务:

ufw route allow proto tcp from any to any port 80

这个命令会允许外部网络访问所有用 Docker 发布出来的并且内部服务端口为 80 的所有服务。

请注意,这个端口 80 是容器的端口,而非使用 -p 0.0.0.0:8080:80 选项发布在服务器上的 8080 端口。

如果有多个容器的服务端口为 80,但只希望外部网络访问某个特定的容器。比如该容器的私有地址为 172.17.0.2,就用类似下面的命令:

ufw route allow proto tcp from any to 172.17.0.2 port 80

如果一个容器的服务是 UDP 协议,假如是 DNS 服务,可以用下面的命令来允许外部网络访问所有发布出来的 DNS 服务:

ufw route allow proto udp from any to any port 53

同样的,如果只针对一个特定的容器,比如 IP 地址为 172.17.0.2

ufw route allow proto udp from any to 172.17.0.2 port 53

长亭雷池WAF+Cloudflare单节点部署

思来想去没啥可写的

Oganneson:

对安全要求更高的话可以像我一样再套上WAF+蜜罐。

部署

有一键部署命令:

bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/setup.sh)"

或部署LTS版本:

RELEASE=lts bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/setup.sh)"

由于本用例在雷池WAF前套了一层Cloudflare,所以说需要将防护站点→全局配置→源 IP 获取方式设置为“取 X-Forwarded-For 中上一级代理的地址”

如果出现其他问题,可前往官方文档常见问题排查 | 雷池 SafeLine 查询。

使用域名访问雷池控制台的问题

最佳解决方案:不用域名

如果我们需要通过域名访问雷池控制台,会发现启用 Cloudflare 小黄云后,域名访问无法实现。这是因为 Cloudflare 的端口转发仅支持其指定的几个端口:

【已解决】部署在cloudflare的域名开启代理之后就无法连接到vps

Cloudflare 支持的 HTTP 端口: 80,8080,8880,2052,2082,2086,2095
Cloudflare 支持的 HTTPS 端口: 443,2053,2083,2087,2096,8443

解决方案也很简单,打开 规则→Origin Rules,然后创建规则。传入请求匹配表达式大致如下:

(starts_with(http.request.full_uri, "https://域名"))

目标端口设置重写到9443即可

为了隐藏公网IP,请设定限定仅cloudflare cdn cidr段访问9443端口的防火墙规则。

重定向问题

如果重定向时 URL 中意外出现端口号,可通过防护站点→站点详情→自定义 NGINX 配置添加以下内容进行解决:

proxy_redirect https://$host:[port] https://$host;

使用 WireGuard 隐藏管理面板

修行魔法的麻瓜:

对于VPS的管理面板,不要将管理面板(例如长亭WAF、宝塔面板等)暴露到公网
使用Wireguard等VPN访问是更加安全的选择
Wireguard可以在Installation - WireGuard
查看到详细的安装方式

引用

  • 开始使用 Ubuntu Pro 来自 Ubuntu 官网
  • 如何避免证书泄露源站 IP 作者:秋未萌
  • Ufw 使用指南 来自 Ubuntu 中文
  • Key-based authentication in OpenSSH for Windows 来自 Microsoft Learn
  • Install Docker Engine on Ubuntu 来自docker官网
  • VPS基本安全措施 - #2,来自 edwardzcn
  • VPS基本安全措施 - #3,来自 Oganneson
  • 【已解决】部署在cloudflare的域名开启代理之后就无法连接到vps - #11,来自 MatsuzakaSato
  • VPS基本安全措施 - #18,来自 OICQ
  • GitHub - chaifeng/ufw-docker: To fix the Docker and UFW security flaw without disabling iptables · GitHub
  • VPS基本安全措施 - #23,来自 Oganneson
  • VPS基本安全措施 - #42,来自 libook
  • VPS基本安全措施 - #44,来自 Staleness
  • VPS基本安全措施 - #20,来自 kendo
  • 消息推送配置说明 - 文档 - 企业微信开发者中心
  • VPS基本安全措施 - #21,来自 alertsc
  • VPS基本安全措施 - #22,来自 OICQ
  • VPS基本安全措施 - #57,来自 waqian
  • Bug #2069041 “Changing Port in sshd_config requires calling syst...” : Bugs : openssh package : Ubuntu
  • 避免网站被攻击的低成本想法 - #29,来自 Bee
  • VPS基本安全措施 - #71,来自 pama
  • VPS基本安全措施 - #87,来自 sevendays
  • VPS基本安全措施 - #97,来自 wordpure
  • VPS基本安全措施 - #99,来自 wordpure
  • VPS基本安全措施 - #108,来自 pama
  • VPS基本安全措施 - #121,来自 systemoutprintlnhell
  • VPS基本安全措施 - #122,来自 steve5wutongyu6
  • VPS基本安全措施 - #135,来自 seamee
  • VPS基本安全措施 - #175,来自 Eternal

本文在本人博客首发,二次发布于LinuxDO,于IDCFlare第三次发布。
除特别声明外均采用CC BY-NC-SA 4.0 许可协议。
此副本已去除所有可能涉及引流的站外链接。
如需转载,请您在非商业使用的情况下标明转载自本帖并附上本贴链接。
如本文采纳诸位佬友的意见,相关帖子链接将会放在引用部分(所有由本人亲自转载的平台同步更新)。

网友解答:
--【壹】--:

感谢大佬分享,很有用,收藏了


--【贰】--: 鸦羽:

UFW 防火墙

成功连接 ssh 的也记一下日志备查比较好
ufw allow log 22/tcp


--【叁】--:

感觉把自己的ip藏好很重要


--【肆】--:

感谢 分享 点赞


--【伍】--:

理论上PermitRootLogin是好的,但在服务器寄掉需要救援时就会变麻烦,我其实更推荐只关闭远程密码登录,保留本地密码登录,出问题时可以通过vnc或者ipmi本地登录root进行救援。贴一个我自用的sshd_config。

# 禁止 Root 用户通过密码远程登录 PermitRootLogin prohibit-password # 关闭远程密码登录,允许密钥登录 PasswordAuthentication no PubkeyAuthentication yes AuthorizedKeysFile .ssh/authorized_keys # 允许本地密码登录 UsePAM yes # 禁用键盘交互认证 KbdInteractiveAuthentication no # 允许 X11 转发 X11Forwarding yes # 禁止显示登录消息 PrintMotd no # 允许环境变量传递 AcceptEnv LANG LC_* # 启用 SFTP 子系统 Subsystem sftp /usr/lib/openssh/sftp-server # 防止超时断开 ClientAliveInterval 60 ClientAliveCountMax 3 # 禁用 DNS 反向解析,加快登录速度 UseDNS no # 设置 SSH端口 Port 自拟

至于web服务的安全问题,我再补充一点,我的建议是善用容器化技术进行隔离,尽可能把应用放进docker。如非必要,容器只在本地监听,直接断绝ip+端口访问的可能。nginx拒绝ip访问,对安全要求更高的话可以像我一样再套上WAF+蜜罐。WAF可以挡住大部分自动化脚本的扫描,有预谋的入侵也更容易先触动蜜饵,给防御留出时间。
image1667×815 91 KB
无论如何,不要在公网裸奔!!


--【陆】--:

感谢分享。!!!


--【柒】--:

很好的帖子。隐藏公网IP并不是所有VPS使用者的共同安全需求,有一个胡诌的针对未来(指ipv6广泛使用)的方案就是只暴露源站v6地址给CDN用,这样Censys这样强扫的工具耗时会很长,不过也还是要配白名单。

另,直接禁掉root登录PermitRootLogin no我也不常用,更习惯和其他sudoer一样配密钥然后禁止密码登录仅允许密钥登录。我是一般能操做线下集群的机子才会这么配,不然出个故障没root用不了了,比如存储满了远程ssh session都建立不了。


--【捌】--:

首先请检查nginx版本是否大于等于1.19.4。ssl_reject_handshake on;在nginx 1.19.4 主线版加入(参见:https://www.oschina.net/news/119594/nginx-1-9-4-released)
如果是通过源代码编译安装的,请确认ngx_http_ssl_module模块是否启用。 该模块不是默认构建的,需要通过 --with-http_ssl_module 配置参数来启用。


--【玖】--:

感谢佬友分享


--【拾】--: 鸦羽:

ssl_reject_handshake on;

试了一下报错。nginx: [emerg] unknown directive “ssl_reject_handshake”


--【拾壹】--:

很有用,我还加了一个在登录ssh时候自动发通知到企业微信机器人,以防偷家不知道


--【拾贰】--:

感谢,现已加入我的收藏,太有用了


--【拾叁】--:

谢谢分享,一直在琢磨安全问题


--【拾肆】--:

感谢大佬科普


--【拾伍】--:

感谢分享,vps安全很重要


--【拾陆】--:

感谢大佬科普


--【拾柒】--:

谢谢。才注意到ubuntu默认的源Nginx版本很老,停留在1.18了。直接添加官方源还不能upgrade,还有依赖需要处理。


--【拾捌】--:

感谢分享大佬厉害啊


--【拾玖】--:

感谢大佬教程