这台 VPS 上线不到两天。我本来只是想弄明白一个开源脚本是干什么的,顺手跑一遍。日志翻开之后,数字不太像「偶尔有人扫一下」——更像有人把 22 端口当成了公共练兵场。
机器是 AlmaLinux 10.2,1 核、1.7G 内存、30G 盘。防火墙开着,只放行 22 / 80 / 443。听起来不算离谱。真正离谱的是 SSH 这一层:
PermitRootLogin yesPasswordAuthentication yes- 还在默认 22 端口
- 还没配过公钥
- 没装 fail2ban,也没装 CrowdSec
- 没有自动安全更新
翻译成人话:root 密码摊在公网上,谁猜中谁就是这台机的主人。过去大约 25 小时里,他们试了 14183 次密码,还用了 7229 个根本不存在的用户名。成功登录的 6 次全是我自己。没有被打进来。但这个「没有」只取决于那串密码还没被字典撞上。
下面按时间把这件事写清楚:原本怎么被打、GitHub 上下了什么、执行了什么、解决了什么,以及今天下午为什么还要再封一个 IP。
时间线
| 北京时间 | 发生了什么 |
|---|---|
| 8 月 14 日晚 | 机器开机。sshd 开始接待全世界 |
| 8 月 16 日 00:21 | 我问:GitHub 上那个 VPS Security Audit Script 是干什么的 |
| 00:23 | 从官方仓库把脚本拉下来,在这台机上跑 |
| 00:35–00:43 | 按顺序加固:写 SSH 公钥、装 fail2ban、换 root 密码、更新系统、收紧管理面 |
| 00:56 | 再跑一遍脚本,核对还在不在被打 |
| 01:00 左右 | 搞清楚私钥该在哪台电脑上生成 |
| 今天下午 17:02 | 复查昨晚到现在的防护效果 |
| 17:11 | 把一条躲规则的慢扫 IP 永久丢掉 |
机器自己的时区是 IST,比北京慢两个半小时。文里小时级图表用的是机器本地时间,方便和 /var/log/secure 对上。
1. 从 GitHub 拉下来的东西
仓库是 nuver-labs/vps-audit,大约 2.4k star。它不是杀毒,也不是渗透工具。作者的定位很老实:一条 不装依赖、不留常驻进程 的 Bash 脚本,给 Linux VPS 做一轮只读体检。查的都是真会把机器打下来的基础项:
- SSH 是否允许 root、是否还开着密码
- 防火墙有没有在跑
- 有没有自动安全更新
- Fail2ban / CrowdSec 在不在
- 失败登录多不多
- 端口是不是铺得太开
- 磁盘、内存、可疑 SUID
打分三档:PASS / WARN / FAIL。跑完在终端出一份彩色报告,再写一个 vps-audit-report-<时间戳>.txt。
它面向 Debian / Ubuntu。这台是 AlmaLinux,所以后文会反复出现一个情节:脚本说没事,日志里已经炸了。
下载和执行就这三行,官方 raw 地址,没有第三方套壳:
curl -fsSL -o /tmp/vps-audit.sh \
https://raw.githubusercontent.com/nuver-labs/vps-audit/main/vps-audit.sh
chmod +x /tmp/vps-audit.sh
bash /tmp/vps-audit.sh
脚本版本是 v0.2.0。第一次跑完的报告在 /tmp/vps-audit-report-20260815_215321.txt,机器本地时间 8 月 15 日 21:53。
2. 脚本说「0 次失败登录」,日志里是一万四千次
脚本自己的成绩单很难看,但还不够准:
| 检查项 | 脚本 | 对着 AlmaLinux 复核 |
|---|---|---|
| SSH 允许 root | FAIL | 属实。安装时留下的 01-permitrootlogin.conf |
| SSH 密码登录 | FAIL | 属实。当时只能靠密码 |
| SSH 22 端口 | WARN | 属实,次要 |
| 防火墙 | PASS | 属实。firewalld 在跑,只放行 22/80/443 |
| 未装入侵防护 | FAIL | 属实。当时机器上没有 fail2ban |
| 失败登录 | PASS(0 次) | 严重漏报。它去读 Debian 的 /var/log/auth.log,这台机日志在 /var/log/secure |
| 系统更新 | PASS | 漏报。它调用 apt-get,AlmaLinux 没有这个命令。实际积了 17 个包,含新内核 |
| 自动更新 | FAIL | 结论对,检测方法是 Debian 的 |
| 开放端口过多 | FAIL(8 个公网端口) | 夸大了。真正对公网的是 22 / 80 / 443;其余有的只绑 localhost,有的是 Tailscale |
| 磁盘 / SUID / 需重启 | PASS | 属实 |
| 内存偏紧、无 swap | WARN | 属实。1.7G 用了六成 |
脚本最危险的那条误报,是「失败登录 0 次」。我当时改去读 /var/log/secure,从开机到第一次体检大约 25 小时:
| 指标 | 数量 |
|---|---|
Failed password | 14183 |
Invalid user | 7229 |
| 成功的密码登录 | 6,全是我自己的家里宽带 |
| 成功的公钥登录 | 0,因为根本还没配钥匙 |
平均下来,这台 1 核小机器每分钟要接待大约 9 次猜密码。sshd 自己有一点 penalty,挡不住这种量。
打得最凶的四个地址:
110.173.190.221 一个人砸了五千多次。它不是认识我,也不是冲着博客来的。公网 22 端口对扫描器来说就是一份待处理队列,扫到就丢进去,按字典慢慢试。目的一般是:撞进之后装挖矿、做成肉鸡再去打别人,或者把壳子卖掉。
成功登录当时只有我自己的几个出口 IP。没有多余 root 账号,没有奇怪的 crontab,没有可疑 SUID。还没失陷。 风险是一直暴破下去,密码一旦被撞上就是 root。
80 端口还有另一处不舒服:nginx 把管理面板反代在 HTTP 上。面板自己的端口没进防火墙,外网直接打进不来,但走 80 就能摸到。公网 HTTP 没有 TLS。443 上跑的是我自己的代理,不是脚本说的「神秘高危端口」。
3. 按顺序做,先保证不会把自己锁外面
脚本跑完,处理顺序其实比「立刻关掉密码登录」更重要。当时这台机 没有一把能用的公钥。先关密码,下一秒我就进不来了。
所以当晚只做不锁人的六步,SELinux 和关密码留到钥匙确认能登上去以后。
第一步:写下 SSH 公钥,密码先留着
机器上生成了一把 ed25519,写入了 authorized_keys。PasswordAuthentication 没动。指纹、公钥内容这类东西不会写在这里。
这件事后来专门又聊了一轮,因为「在哪台机器上生成私钥」很容易搞反:
| 位置 | 该放什么 | 不该放什么 |
|---|---|---|
| 我的电脑 | 私钥 id_ed25519 | 不要把私钥拷到服务器 |
| 这台 VPS | 公钥,追加进 authorized_keys | 不要在服务器上为「我连回来」这件事 ssh-keygen |
服务器凭什么认我的公钥?它不会在网上看到一把钥匙就自动装上。它只认已经写进 ~/.ssh/authorized_keys 的那几行。谁能改这个文件,谁才能加新钥匙。外人自己生成一对密钥,没有这台机的写入权,加不进来;只有公钥没有私钥,也连不上。
更稳的做法是在 电脑上 ssh-keygen,再用 ssh-copy-id 把 .pub 推过来。服务器上那把只是过渡,等电脑上的钥匙配对成功就可以不用它。
第二步:装 fail2ban,对准真正的日志
dnf install -y fail2ban fail2ban-firewalld fail2ban-systemd dnf-automatic
jail.local 按这台机改过,不是照抄 Debian 示例:
- 读
/var/log/secure,不用不存在的/var/log/auth.log - 15 分钟内失败 4 次 → 封 24 小时
- 重复再犯,封禁时间加长,最长一周
- 白名单:本机、组网网段、家里常用出口
- 动作走
firewallcmd-rich-rules,和现有 firewalld 叠在一起 - 顺手装上的 exim 立刻关掉并 mask,避免多开一个邮件端口
fail2ban 在机器本地时间 22:08 起来。启动当秒就封掉了当时还在砸的那几个,包括 110.173.190.221、167.233.166.95。
第三步:换掉正在被打的 root 密码
按这个暴破规模,旧密码必须当成已经出现在别人的字典里。当晚用 chpasswd 换成一串 24 位随机口令,磁盘上的明文副本随即删掉。旧密码立刻失效。
密码登录此时 还开着。新密码只是把「正在被砸的那把锁」换掉,不是最终方案。最终方案是钥匙能登上去之后,关掉密码。
第四步:补上 17 个更新,打开自动补丁
脚本说系统已是最新,那是因为它在喊 apt-get。实际 dnf check-update 有 17 个待更新包,包括内核 6.12.0-211.47.1 和 libnghttp2。当晚 dnf update 跑完了,但 没重启,所以当时还在跑旧内核 6.12.0-211.46.1。
同时启用 dnf-automatic-install.timer,大约每天凌晨自动装更新,不会自动重启。1.7G 内存扛更新容易 OOM,加了 1G swap(/swapfile)。现在 swap 还在,已经真正用上了几百兆。
第五步:SELinux 这次没开
当前是 Disabled。AlmaLinux 默认应该是 Enforcing。从关到开必须改配置、整盘 relabel、再重启。代理、nginx 反代、管理面板都可能被策略拦住。SSH 钥匙还没从我自己的电脑验证过,这个窗口不适合拿来赌开机。
第六步:管理面板不再对公网裸奔
nginx 里这个 server 改成只允许组网网段和本机,其他来源访问对应 Host 直接 403,当时测过。手机走内网主机名仍然 200。别的站点没动。
面板进程当时还听在所有地址上,但防火墙没放行那个端口,外网打不进。配置已改成下次重启只绑本机回环。当晚没重启面板,避免把正在用的页面踢掉。
刻意没做的,也列清楚:
| 没做的事 | 原因 |
|---|---|
| 关闭 SSH 密码 / 禁止 root 远程 | 等我确认自己电脑上的钥匙能登上 |
| 改 SSH 端口 | 次要,而且要同步改 fail2ban |
| 打开 SELinux | 要 relabel + 重启,容易弄挂现有服务 |
| 立刻重启吃新内核 | 会中断代理、面板和当时的会话 |
4. 装完 20 分钟后再跑一遍:还在打,但量掉了一个数量级
本地时间 22:27,同一条脚本又跑了一次。它仍然报「没装 fail2ban」「失败登录 0 次」——还是那个 Debian 视角。对照 journal 和 fail2ban-client status sshd 才是真的。
按小时看 Failed password,转折就在 22 点:
| 本地时间 | 失败登录 | 状态 |
|---|---|---|
| 17 时 | 1083 | 裸奔 |
| 18 时 | 1101 | 裸奔 |
| 19 时 | 1043 | 裸奔 |
| 20 时 | 909 | 裸奔 |
| 21 时 | 1196 | 裸奔 |
| 22 时 | 138 | 22:08 fail2ban 起来,当小时后半段被掐住 |
| 刚启动后 20 分钟 | 26 | 新 IP 试满 4 次就被封 |
| 最近 5 分钟(当时) | 0 | 门外暂时清净 |
当时已经封了 10 个 IP。22 端口没有 ESTABLISHED / SYN-RECV。结论写成一句话:互联网扫描还在继续,但已经被挡在门外;没有看到已经登录成功的陌生地址。
脚本第二次还在报 FAIL 的几项,对照如下:
| 检查 | 脚本第二次 | 实际 |
|---|---|---|
| 自动更新 | FAIL | 误报。dnf-automatic-install.timer 已经 enabled |
| Fail2ban | FAIL | 误报。它用 dpkg 查,AlmaLinux 上查不到 |
| 失败登录 | PASS(0 次) | 仍然漏报。真实累计已经过万 |
| SSH root + 密码 | FAIL | 属实,当晚故意没关 |
5. 今天下午再查:狂砸被按住了,有人改成了慢扫
防护加上之后大约 16.5 小时。把这段和装之前的 8.5 小时放在一起,差别不用解释:
更细的小时曲线:
- 15 日 18–21 点(含 Invalid user):每小时 2500–3400
- 22 点 fail2ban 启动后立刻掉到 407,里面很大一部分是启动时一次性清算旧日志
- 15 日 23 点之后:每小时 6–37
- 今天下午 14 点这一小时:12 次
启动当秒封掉的那几个没有再打进来。之后不断有新 IP 过来试,试满 4 次就被封。今天下午复查时,名单已经堆到 28 个;再过一会儿封慢扫的时候,是 30 个。
没有失陷的依据也还站得住:
Accepted记录里,密码登录仍是我自己的家里宽带- 公钥登录只有本机回环,以及 CI 用来部署博客的那把钥匙
- 28 个封禁 IP 全部是 SSH 失败后封的,没有「解封后再打进来」
- 订阅接口昨晚到现在只有我自己拉过
两处现有规则没挡住。
第一处是慢速爆破。 130.49.213.228 从今天 00:40 起到下午 14:34 还在打,大约 每 16–17 分钟试 1 次,每次换一个用户名。规则是「15 分钟内 4 次才封」,它卡在窗口外面,所以一直没被自动封。这不是手滑,是故意躲 fail2ban。
它只碰 22 端口,网站、订阅、Xray 都没碰。试过大约 32 个账号,几乎全是泄露密码表里的业务默认用户,不是我的名字:
| 类型 | 它试过的账号 |
|---|---|
| 数据库 / 中间件 | oracle postgres grid applmgr emqttd dirsrv |
| 运维 / 部署 | ubuntu deploy git admin root dev developer patch |
| 厂商默认号 | dspace duzon filmlight dell hp |
| 常见人名号 | mahesh amit |
| 随便猜的 | test user user2 support mobile node |
root 它只试过 1 次。更想撞的是别人忘了改密码的 Oracle、Git、Ubuntu 默认用户。全部失败。
这个地址归 AS207713 GLOBAL INTERNET SOLUTIONS LLC(GIR),机房标注在新西伯利亚,反解是批量 PTR 169368.ip-ptr.tech。能 ping 通,延迟大约 241ms。这家主机商在安全圈里口碑很差,同网段有过钓鱼和批量域名的记录。更像租来的扫描炮灰,不是某个认识我的人。
今天下午 17:11 手动封掉,做了两层:
- 丢进 fail2ban 的 sshd 黑名单(当时共 30 个 IP)
- firewalld 永久 rich rule:这个源地址全部 drop,重启也不丢
fail2ban 那条 24 小时后可能会解,防火墙那条会一直留着。它再按 16 分钟一次来试,包到不了 sshd。
第二处是 Web 扫描。 fail2ban 现在只有 sshd jail,80 端口没人拦。今天至少有两个地址在扫 /.env、/.git/config、/phpinfo.php、phpunit 老洞。日志里是 200,核对过了:那是默认站前端的 try_files ... /index.html,一律回同一份页面,不是真的环境变量或源码。订阅路径没有被外人拉走。Web 这边以后可以单独加一个 nginx 监狱,今晚没做。
6. 一张表看清「做了什么」和「得到了什么」
| 动作 | 做了什么 | 成果 |
|---|---|---|
| 跑 vps-audit v0.2.0 | 官方脚本 + 人工对照 /var/log/secure | 发现 1.4 万次撞库,以及脚本自己的 Debian 盲区 |
| 写入 SSH 公钥 | ed25519 + authorized_keys | 有了关密码登录的前提,还没关 |
| 安装 fail2ban | 15 分钟 4 次 / 封 24 小时,读 secure | 小时失败量从 ~1100 掉到十几 |
| 更换 root 密码 | chpasswd,旧密码作废 | 正在被砸的那把锁换掉了 |
| 系统更新 + 自动补丁 | dnf update + dnf-automatic | 17 个包补上;以后凌晨自动装,不自动重启 |
| 加 1G swap | /swapfile | 1.7G 机器更新和日常不容易直接 OOM |
| 收紧管理面板 | nginx 只放行组网网段 | 公网 Host 访问变 403,管理面不再裸奔 80 |
| 关掉 exim | mask 掉 fail2ban 顺带拉进来的邮件服务 | 少一个监听端口 |
永久封 130.49.213.228 | fail2ban + firewalld drop | 躲 15 分钟窗口的慢扫不再能碰到 SSH |
再压成前后对比:
| 项 | 昨晚加固前 | 现在 |
|---|---|---|
| 25 小时 SSH 失败密码 | 14183 | 新流量按小时计是原来的 1%–3% |
| 入侵防护 | 无 | fail2ban,当前封 30 个 IP |
| root 密码 | 开在公网、正在被字典打 | 已轮换 |
| SSH 密钥 | 没有 | 已写入,密码登录仍开着 |
| 自动安全更新 | 无 | 每天自动安装 |
| 管理面 | 公网 HTTP 能摸到 | 仅组网 / 本机 |
| 慢扫 | 不存在「规则」可躲 | 已对这条 IP 永久丢包 |
| 是否失陷 | 未见陌生成功登录 | 仍然未见 |
7. 还没做完的
这篇文章不是「已经安全了」。是「从裸奔变成能挡普通扫描」。还挂着的:
密码登录和 root 远程都还开着。 钥匙在服务器上有了,我自己电脑上的那把还需要完成配对。配对成功的下一件事就是:
PasswordAuthentication no PermitRootLogin prohibit-password PubkeyAuthentication yesfail2ban 窗口仍然是 15 分钟 4 次。 今晚这条慢扫是手工封的。以后可以把
findtime拉长,让「16 分钟一次」也会累计。80 端口的路径扫描没人管。 现在靠前端兜底,不等于永远没事。
SELinux 仍是 Disabled。 要单独挑一个维护窗口。
SSH 还在 22。 改端口只是少吃一点垃圾流量,不能代替关密码。
给刚开好公网 VPS 的人,我现在会按这个顺序说:
- 先在 自己电脑 上
ssh-keygen,公钥写进服务器,登上去确认 - 再关密码,再考虑关 root 远程
- 装 fail2ban,确认它读的是这台发行版真正的日志
- 换掉刚刚在公网上裸奔过的密码
- 打开自动安全更新
- 管理面板不要放在公网 80
vps-audit 值得跑。但在 AlmaLinux / Rocky / CentOS 上,不要相信它的「失败登录 0 次」和「系统已是最新」。那两行要自己再查一遍 /var/log/secure 和 dnf check-update。
第一晚的故事到这里。机器还在,博客也还在。门外那几万次敲门,大部分已经听不见了。
