[{"content":"这台 VPS 上线不到两天。我本来只是想弄明白一个开源脚本是干什么的，顺手跑一遍。日志翻开之后，数字不太像「偶尔有人扫一下」——更像有人把 22 端口当成了公共练兵场。\n机器是 AlmaLinux 10.2，1 核、1.7G 内存、30G 盘。防火墙开着，只放行 22 / 80 / 443。听起来不算离谱。真正离谱的是 SSH 这一层：\nPermitRootLogin yes PasswordAuthentication yes 还在默认 22 端口 还没配过公钥 没装 fail2ban，也没装 CrowdSec 没有自动安全更新 翻译成人话：root 密码摊在公网上，谁猜中谁就是这台机的主人。过去大约 25 小时里，他们试了 14183 次密码，还用了 7229 个根本不存在的用户名。成功登录的 6 次全是我自己。没有被打进来。但这个「没有」只取决于那串密码还没被字典撞上。\n下面按时间把这件事写清楚：原本怎么被打、GitHub 上下了什么、执行了什么、解决了什么，以及今天下午为什么还要再封一个 IP。\n时间线 北京时间 发生了什么 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 对上。\n1. 从 GitHub 拉下来的东西 仓库是 nuver-labs/vps-audit，大约 2.4k star。它不是杀毒，也不是渗透工具。作者的定位很老实：一条 不装依赖、不留常驻进程 的 Bash 脚本，给 Linux VPS 做一轮只读体检。查的都是真会把机器打下来的基础项：\nSSH 是否允许 root、是否还开着密码 防火墙有没有在跑 有没有自动安全更新 Fail2ban / CrowdSec 在不在 失败登录多不多 端口是不是铺得太开 磁盘、内存、可疑 SUID 打分三档：PASS / WARN / FAIL。跑完在终端出一份彩色报告，再写一个 vps-audit-report-\u0026lt;时间戳\u0026gt;.txt。\n它面向 Debian / Ubuntu。这台是 AlmaLinux，所以后文会反复出现一个情节：脚本说没事，日志里已经炸了。\n下载和执行就这三行，官方 raw 地址，没有第三方套壳：\ncurl -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。\n2. 脚本说「0 次失败登录」，日志里是一万四千次 脚本自己的成绩单很难看，但还不够准：\n检查项 脚本 对着 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 小时：\n指标 数量 Failed password 14183 Invalid user 7229 成功的密码登录 6，全是我自己的家里宽带 成功的公钥登录 0，因为根本还没配钥匙 平均下来，这台 1 核小机器每分钟要接待大约 9 次猜密码。sshd 自己有一点 penalty，挡不住这种量。\n打得最凶的四个地址：\n110.173.190.221 一个人砸了五千多次。它不是认识我，也不是冲着博客来的。公网 22 端口对扫描器来说就是一份待处理队列，扫到就丢进去，按字典慢慢试。目的一般是：撞进之后装挖矿、做成肉鸡再去打别人，或者把壳子卖掉。\n成功登录当时只有我自己的几个出口 IP。没有多余 root 账号，没有奇怪的 crontab，没有可疑 SUID。还没失陷。 风险是一直暴破下去，密码一旦被撞上就是 root。\n80 端口还有另一处不舒服：nginx 把管理面板反代在 HTTP 上。面板自己的端口没进防火墙，外网直接打进不来，但走 80 就能摸到。公网 HTTP 没有 TLS。443 上跑的是我自己的代理，不是脚本说的「神秘高危端口」。\n3. 按顺序做，先保证不会把自己锁外面 脚本跑完，处理顺序其实比「立刻关掉密码登录」更重要。当时这台机 没有一把能用的公钥。先关密码，下一秒我就进不来了。\n所以当晚只做不锁人的六步，SELinux 和关密码留到钥匙确认能登上去以后。\n第一步：写下 SSH 公钥，密码先留着 机器上生成了一把 ed25519，写入了 authorized_keys。PasswordAuthentication 没动。指纹、公钥内容这类东西不会写在这里。\n这件事后来专门又聊了一轮，因为「在哪台机器上生成私钥」很容易搞反：\n位置 该放什么 不该放什么 我的电脑 私钥 id_ed25519 不要把私钥拷到服务器 这台 VPS 公钥，追加进 authorized_keys 不要在服务器上为「我连回来」这件事 ssh-keygen 服务器凭什么认我的公钥？它不会在网上看到一把钥匙就自动装上。它只认已经写进 ~/.ssh/authorized_keys 的那几行。谁能改这个文件，谁才能加新钥匙。外人自己生成一对密钥，没有这台机的写入权，加不进来；只有公钥没有私钥，也连不上。\n更稳的做法是在 电脑上 ssh-keygen，再用 ssh-copy-id 把 .pub 推过来。服务器上那把只是过渡，等电脑上的钥匙配对成功就可以不用它。\n第二步：装 fail2ban，对准真正的日志 dnf install -y fail2ban fail2ban-firewalld fail2ban-systemd dnf-automatic jail.local 按这台机改过，不是照抄 Debian 示例：\n读 /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。\n第三步：换掉正在被打的 root 密码 按这个暴破规模，旧密码必须当成已经出现在别人的字典里。当晚用 chpasswd 换成一串 24 位随机口令，磁盘上的明文副本随即删掉。旧密码立刻失效。\n密码登录此时 还开着。新密码只是把「正在被砸的那把锁」换掉，不是最终方案。最终方案是钥匙能登上去之后，关掉密码。\n第四步：补上 17 个更新，打开自动补丁 脚本说系统已是最新，那是因为它在喊 apt-get。实际 dnf check-update 有 17 个待更新包，包括内核 6.12.0-211.47.1 和 libnghttp2。当晚 dnf update 跑完了，但 没重启，所以当时还在跑旧内核 6.12.0-211.46.1。\n同时启用 dnf-automatic-install.timer，大约每天凌晨自动装更新，不会自动重启。1.7G 内存扛更新容易 OOM，加了 1G swap（/swapfile）。现在 swap 还在，已经真正用上了几百兆。\n第五步：SELinux 这次没开 当前是 Disabled。AlmaLinux 默认应该是 Enforcing。从关到开必须改配置、整盘 relabel、再重启。代理、nginx 反代、管理面板都可能被策略拦住。SSH 钥匙还没从我自己的电脑验证过，这个窗口不适合拿来赌开机。\n第六步：管理面板不再对公网裸奔 nginx 里这个 server 改成只允许组网网段和本机，其他来源访问对应 Host 直接 403，当时测过。手机走内网主机名仍然 200。别的站点没动。\n面板进程当时还听在所有地址上，但防火墙没放行那个端口，外网打不进。配置已改成下次重启只绑本机回环。当晚没重启面板，避免把正在用的页面踢掉。\n刻意没做的，也列清楚：\n没做的事 原因 关闭 SSH 密码 / 禁止 root 远程 等我确认自己电脑上的钥匙能登上 改 SSH 端口 次要，而且要同步改 fail2ban 打开 SELinux 要 relabel + 重启，容易弄挂现有服务 立刻重启吃新内核 会中断代理、面板和当时的会话 4. 装完 20 分钟后再跑一遍：还在打，但量掉了一个数量级 本地时间 22:27，同一条脚本又跑了一次。它仍然报「没装 fail2ban」「失败登录 0 次」——还是那个 Debian 视角。对照 journal 和 fail2ban-client status sshd 才是真的。\n按小时看 Failed password，转折就在 22 点：\n本地时间 失败登录 状态 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。结论写成一句话：互联网扫描还在继续，但已经被挡在门外；没有看到已经登录成功的陌生地址。\n脚本第二次还在报 FAIL 的几项，对照如下：\n检查 脚本第二次 实际 自动更新 FAIL 误报。dnf-automatic-install.timer 已经 enabled Fail2ban FAIL 误报。它用 dpkg 查，AlmaLinux 上查不到 失败登录 PASS（0 次） 仍然漏报。真实累计已经过万 SSH root + 密码 FAIL 属实，当晚故意没关 5. 今天下午再查：狂砸被按住了，有人改成了慢扫 防护加上之后大约 16.5 小时。把这段和装之前的 8.5 小时放在一起，差别不用解释：\n更细的小时曲线：\n15 日 18–21 点（含 Invalid user）：每小时 2500–3400 22 点 fail2ban 启动后立刻掉到 407，里面很大一部分是启动时一次性清算旧日志 15 日 23 点之后：每小时 6–37 今天下午 14 点这一小时：12 次 启动当秒封掉的那几个没有再打进来。之后不断有新 IP 过来试，试满 4 次就被封。今天下午复查时，名单已经堆到 28 个；再过一会儿封慢扫的时候，是 30 个。\n没有失陷的依据也还站得住：\nAccepted 记录里，密码登录仍是我自己的家里宽带 公钥登录只有本机回环，以及 CI 用来部署博客的那把钥匙 28 个封禁 IP 全部是 SSH 失败后封的，没有「解封后再打进来」 订阅接口昨晚到现在只有我自己拉过 两处现有规则没挡住。\n第一处是慢速爆破。 130.49.213.228 从今天 00:40 起到下午 14:34 还在打，大约 每 16–17 分钟试 1 次，每次换一个用户名。规则是「15 分钟内 4 次才封」，它卡在窗口外面，所以一直没被自动封。这不是手滑，是故意躲 fail2ban。\n它只碰 22 端口，网站、订阅、Xray 都没碰。试过大约 32 个账号，几乎全是泄露密码表里的业务默认用户，不是我的名字：\n类型 它试过的账号 数据库 / 中间件 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 默认用户。全部失败。\n这个地址归 AS207713 GLOBAL INTERNET SOLUTIONS LLC（GIR），机房标注在新西伯利亚，反解是批量 PTR 169368.ip-ptr.tech。能 ping 通，延迟大约 241ms。这家主机商在安全圈里口碑很差，同网段有过钓鱼和批量域名的记录。更像租来的扫描炮灰，不是某个认识我的人。\n今天下午 17:11 手动封掉，做了两层：\n丢进 fail2ban 的 sshd 黑名单（当时共 30 个 IP） firewalld 永久 rich rule：这个源地址全部 drop，重启也不丢 fail2ban 那条 24 小时后可能会解，防火墙那条会一直留着。它再按 16 分钟一次来试，包到不了 sshd。\n第二处是 Web 扫描。 fail2ban 现在只有 sshd jail，80 端口没人拦。今天至少有两个地址在扫 /.env、/.git/config、/phpinfo.php、phpunit 老洞。日志里是 200，核对过了：那是默认站前端的 try_files ... /index.html，一律回同一份页面，不是真的环境变量或源码。订阅路径没有被外人拉走。Web 这边以后可以单独加一个 nginx 监狱，今晚没做。\n6. 一张表看清「做了什么」和「得到了什么」 动作 做了什么 成果 跑 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 再压成前后对比：\n项 昨晚加固前 现在 25 小时 SSH 失败密码 14183 新流量按小时计是原来的 1%–3% 入侵防护 无 fail2ban，当前封 30 个 IP root 密码 开在公网、正在被字典打 已轮换 SSH 密钥 没有 已写入，密码登录仍开着 自动安全更新 无 每天自动安装 管理面 公网 HTTP 能摸到 仅组网 / 本机 慢扫 不存在「规则」可躲 已对这条 IP 永久丢包 是否失陷 未见陌生成功登录 仍然未见 7. 还没做完的 这篇文章不是「已经安全了」。是「从裸奔变成能挡普通扫描」。还挂着的：\n密码登录和 root 远程都还开着。 钥匙在服务器上有了，我自己电脑上的那把还需要完成配对。配对成功的下一件事就是：\nPasswordAuthentication no PermitRootLogin prohibit-password PubkeyAuthentication yes fail2ban 窗口仍然是 15 分钟 4 次。 今晚这条慢扫是手工封的。以后可以把 findtime 拉长，让「16 分钟一次」也会累计。\n80 端口的路径扫描没人管。 现在靠前端兜底，不等于永远没事。\nSELinux 仍是 Disabled。 要单独挑一个维护窗口。\nSSH 还在 22。 改端口只是少吃一点垃圾流量，不能代替关密码。\n给刚开好公网 VPS 的人，我现在会按这个顺序说：\n先在 自己电脑 上 ssh-keygen，公钥写进服务器，登上去确认 再关密码，再考虑关 root 远程 装 fail2ban，确认它读的是这台发行版真正的日志 换掉刚刚在公网上裸奔过的密码 打开自动安全更新 管理面板不要放在公网 80 vps-audit 值得跑。但在 AlmaLinux / Rocky / CentOS 上，不要相信它的「失败登录 0 次」和「系统已是最新」。那两行要自己再查一遍 /var/log/secure 和 dnf check-update。\n第一晚的故事到这里。机器还在，博客也还在。门外那几万次敲门，大部分已经听不见了。\n","permalink":"https://blog.charryc.fun/posts/2026-08-16-vps-first-night/","summary":"从 GitHub 拉下 vps-audit，发现 25 小时里 SSH 失败登录 1.4 万次。装 fail2ban、换 root 密码、封慢扫 IP 之后，攻击从每小时上千次掉到十几次。","title":"第一晚，这台机器被砸了一万四千次"},{"content":"写前端，也写 AI agent。对界面和交互比较较真。一年经验，日常用 TypeScript、JavaScript、Node.js、Python。最近在啃 agent，后端业务也在补。\nGitHub：Charry-C\n这个站挂在 blog.charryc.fun，Hugo 编成静态页，本机 nginx 直出。\n","permalink":"https://blog.charryc.fun/about/","summary":"\u003cp\u003e写前端，也写 AI agent。对界面和交互比较较真。一年经验，日常用 TypeScript、JavaScript、Node.js、Python。最近在啃 agent，后端业务也在补。\u003c/p\u003e","title":"关于"},{"content":"博客已经在服务器上跑起来了。域名 blog.charryc.fun 解析过来之后，访问的就是这一页。\n日常发一篇（推荐：电脑上写，push 自动上线） 仓库：git@github.com:Charry-C/charryc-blog.git。本机一次：\ngit clone git@github.com:Charry-C/charryc-blog.git cd charryc-blog 以后每发一篇：\n# 新建：复制一篇现成的 md，或在服务器上 blog new 再拉下来 # 文件放在 content/posts/，例如 2026-08-16-weekend-notes.md git add content/posts/2026-08-16-weekend-notes.md git commit -m \u0026#34;post: 周末随记\u0026#34; git push push 到 master/main 后，GitHub Actions 会 SSH 到这台 VPS 执行 blog deploy（拉最新提交 + Hugo 发布）。想再跑一遍部署：GitHub → Actions → deploy → Run workflow。\n也可以 SSH 到服务器手动发布：\nblog new my-slug \u0026#34;文章标题\u0026#34; # 编辑提示里给出的那个 .md blog pub # 若要把文章备份到 GitHub：git add \u0026amp;\u0026amp; git commit \u0026amp;\u0026amp; git push blog new 会在 /root/blog/content/posts/ 下生成文件，例如 2026-08-16-my-slug.md。打开改正文即可。\n文章开头那几行是元信息，常用这几个：\n--- title: \u0026#34;文章标题\u0026#34; date: 2026-08-16T12:00:00+08:00 draft: false tags: [\u0026#34;随笔\u0026#34;] summary: \u0026#34;列表页上显示的简介\u0026#34; --- draft: true：草稿，构建时不会发布 date 不要写成未来时间，否则 Hugo 会跳过这篇文章 改旧文：改对应 .md，git commit \u0026amp;\u0026amp; git push（或服务器上 blog pub） 删文：删掉那个 .md，同样 push 或 blog pub 图片 放到 /root/blog/static/images/，正文里这样引用：\n![说明](/images/example.png) 然后同样 git push 或 blog pub。\n改站点名字、菜单 编辑 hugo.toml，保存后 git push（或服务器上 blog pub）。\n域名还没解析时怎么预览 本机或服务器上：\ncurl -sI -H \u0026#34;Host: blog.charryc.fun\u0026#34; http://127.0.0.1/ 电脑的 hosts 里加一行也可以：把这台机指到 blog.charryc.fun。\n解析请指到这台机，只开 80 端口的网站。443 已经给代理用了。\n如果走 Cloudflare：\n橙云（代理）选 Flexible，浏览器有小绿锁，回源走 80 不要选 Full / Full (strict)，这台机 443 不是网站证书 灰云（仅 DNS）的话，用 http://blog.charryc.fun 也能打开 ","permalink":"https://blog.charryc.fun/posts/hello/","summary":"这个站怎么发文、改文、上线。DNS 解析好之后打开就能看。","title":"先写一篇，当作说明书"}]