纸上得来
← BACK
2026-09-03 · 技术 · 5 MIN READ

服务器被挖矿之后

最近阿里云和腾讯云上的两台服务器相继被植入了挖矿程序,我不得不重装系统,一部分数据没能留下来。趁着这次重装,把加固的过程记一下。

个人服务器我跑了很多年,一直只当成兴趣项目的部署环境,上面没什么要紧数据。所以早些年发现有挖矿程序在跑,只要不影响我自己的网站,我也就没太当回事。

最近开始做小程序,情况不一样了——小程序碰的是真实的用户数据,这个丢不起。

重装完的第一件事,是禁用密码登录、改用 SSH 密钥。服务器开在公网上,SSH 每周会收到几千次爆破尝试;只认密钥之后,这些尝试就都是白费力气了。

客户端:~/.ssh/config

plaintext
Host myserver
    HostName <服务器 IP 或域名>
    User root
    IdentityFile ~/.ssh/<私钥文件>
    IdentitiesOnly yes

配好之后 ssh myserver 就能连,不用每次敲 IP 和 -i

有两个地方容易踩:

必须用别名连,不能用 IP。 ssh root@<IP> 匹配不到 Host myserver 这条规则,整段配置不生效,ssh 会退回去试默认的 ~/.ssh/id_rsaid_ed25519——服务器不认,于是报 Permission denied (publickey)。我在这里卡过一次。

IdentitiesOnly yes 是必要的。 不加的话 ssh 会把 ~/.ssh 下所有密钥挨个试一遍。密钥多了会超过服务器的 MaxAuthTries(默认 6),还没轮到正确那把就被踢掉了。

私钥

plaintext
权限   600      ← 必须,宽于此 ssh 会直接拒绝使用
类型   RSA 2048 或 ED25519(新生成推荐后者)

权限不对时的报错很明确:

plaintext
Permissions 0644 for '...' are too open.

修:chmod 600 ~/.ssh/<私钥文件>

服务器端:authorized_keys

公钥放在登录用户的家目录下:

plaintext
~/.ssh/authorized_keys    权限 600
~/.ssh/                   权限 700

权限过宽 sshd 会静默忽略这个文件——不报错,只是认证失败,排查时很容易被误导。查已授权的公钥:

bash
ssh-keygen -l -f ~/.ssh/authorized_keys

会列出每把钥匙的指纹和注释,方便核对哪把是哪台机器的,也方便清理不再用的。

sshd 配置

关键的几项,改 /etc/ssh/sshd_config

plaintext
PasswordAuthentication no      # 关掉密码登录,最重要的一条
PubkeyAuthentication yes
PermitEmptyPasswords no
KbdInteractiveAuthentication no

改完先验证语法再重启,避免把自己关在门外:

bash
sshd -t                        # 语法检查
systemctl reload sshd

验证的时候不要关掉当前的 SSH 连接。 另开一个终端试连,确认能进去了再退出原来那个。万一配错了,原连接还在,还有机会改回来。

看生效值用 sshd -T,它输出的是实际运行值,而不是配置文件的字面内容——有些项在别处被覆盖过,只看文件会看走眼:

bash
sshd -T | grep -iE 'passwordauth|pubkeyauth|permitroot|port'

把管理面板从公网上撤下来

密码登录关掉之后,SSH 这道门算是守住了。但我还开着一个 1Panel,它的端口是直接对 0.0.0.0/0 放行的。

这件事比想象中严重。面板不是一个普通的网页——它自带文件管理器和 Web 终端,等于把 root 权限包装成了一个网页表单。SSH 那边我费劲关掉密码登录、只认密钥,而面板这边只要一个用户名密码就能进,还是从任何 IP 都能敲。真出了认证绕过的漏洞,前面做的都白做。

挖矿程序进来的路子,多半就是这一类。

先搞清楚到底开了哪些门

云厂商的安全组规则是一份,服务器上实际在监听的端口是另一份,这两份经常对不上。先各查一遍:

bash
# 服务器上实际在监听的端口
ss -tln | awk 'NR>1 {split($4,a,":"); print a[length(a)]}' | sort -un

然后拿这个结果去比对安全组的放行列表。我比出来两条空规则——安全组放行了,但服务器上根本没有服务在听。一条是很久之前装 Gitea 留下的,容器早删了,规则忘了收;另一条我已经完全想不起来是干什么的。

空规则当下不造成危害,风险在将来:哪天你随手起一个服务恰好用了那个端口,它会立刻对全网可见,而你不会察觉。顺手删掉是零成本的。

用 SSH 隧道代替公网放行

面板不需要随时可达。平时用不到,用到的时候现开一条隧道就行:

bash
ssh -N -L <面板端>:127.0.0.1:<面板端> myserver

-N 表示只建隧道、不执行命令,所以这个终端会挂住不返回,这是正常的。保持它开着,浏览器访问 http://localhost:<面板端口>,流量从本地经 SSH 加密通道到服务器,面板对公网彻底隐身。

做成别名会顺手很多:

zsh
alias panel='ssh -N -L <面板端口>:127.0.0.1:<面板端口> myserver'

两个坑

先验证隧道,再关安全组。 顺序反了,万一隧道有问题,你就同时失去了两条进面板的路。我的做法是:隧道建好、浏览器确认能正常登录,然后才去云控制台删规则。

面板可能有「安全入口」。 1Panel 支持配置一个入口路径,只有带上这段路径才能访问。我直接开 http://localhost:<端口> 时看到的是:

plaintext
Access Temporarily Unavailable
The current environment has enabled secure login access.

当时以为隧道没通,其实是通的,只是路径不对。查自己的入口:

bash
1pctl user-info

想到这里其实要说一句:这个提示页本身是个好设计。哪怕端口暴露在公网,扫描器扫到根路径也只会看到这句话,看不到登录框,也就无从下手。但它终究是靠”别人不知道路径”生效的一层混淆,不是真正的认证。

写信 WRITE BACK

如果这篇恰好对你有用,或者你只是想说点什么——iworkvip@gmail.com