Article

一次惊心动魄的服务器安全危机:从怀疑被黑到发现环境变量错误的排查之旅

记录一次服务器突然卡顿、SSH连接失效的惊险经历。从怀疑被黑客攻击,到排查发现22端口全开放的安全隐患,再到最终发现是环境变量配置错误导致的容器无限重启循环。一次完整的安全意识和故障排查学习过程。

一次云服务器“被黑”惊魂:从 SSH 失联到 Zod 环境变量报错:

服务器突然"失联"

那是一个平凡的下午,我正在愉快地vibe coding,突然发现部署在云服务器上的应用响应变得异常缓慢。起初以为是网络波动,没太在意。但几分钟后,当我尝试通过 SSH 连接到服务器时,终端无情地显示:

ssh: connect to host xxx.xxx.xxx.xxx port 22: Connection timed out

心跳瞬间加速——服务器失联了!

第一反应:我被黑了吗?

我的第一反应是:"完了,服务器被黑了!"

脑海中闪过各种可能性:

  • 是不是有黑客通过漏洞入侵了?
  • 是不是被挖矿了?
  • 是不是被 DDoS 攻击了?

我立即登录云服务商的控制台,通过 VNC 连接到服务器。还好,控制台还能用,但响应速度慢得让人抓狂。

与 GPT 的问答:发现第一个安全隐患

在焦虑中,我开始向 GPT 求助:"服务器突然卡顿,SSH 连接不上,可能是什么原因?"

GPT 给出了几个建议:

  1. 检查系统负载:tophtop
  2. 检查网络连接:netstat -tulpn
  3. 检查安全组/防火墙设置

我按照建议检查了安全组配置,结果发现了一个让我冷汗直流的错误:我对所有 IP 开放了 22 端口!

# 错误的安全组规则
Port: 22
Protocol: TCP
Source: 0.0.0.0/0  # 对所有人开放!

这是一个极其危险的安全隐患!任何知道服务器 IP 的人都可以尝试暴力破解 SSH 密码。

紧急修复:修改密码 + 收紧安全组

我立即采取行动:

  1. 修改 root 密码:使用云控制台的密码重置功能
  2. 收紧安全组:只允许我的办公 IP 和家庭 IP 访问 22 端口
  3. 启用密钥认证:禁用密码登录,只允许 SSH 密钥登录
# 正确的安全组规则
Port: 22
Protocol: TCP
Source: 123.123.123.123/32  # 只允许特定 IP

修复完成后,我满怀期待地重新尝试 SSH 连接...

失望:问题依旧

然而,现实给了我一记重击——问题并没有解决!服务器依然卡顿,SSH 连接依然时好时坏。

但我观察到一个重要现象:每次重启服务器后,SSH 可以正常连接一段时间,然后逐渐变卡,最终完全失联。

深入排查:发现真正的元凶

既然不是外部攻击,那问题一定在服务器内部。我使用 top -o %CPU 命令查看系统资源使用情况:

top -o %CPU

结果让我大吃一惊:

PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
1234 mysql     20   0  3.2g   1.8g   12345 S  45.6  23.4   10:23.45 mysqld
5678 lobster   20   0  2.1g   1.2g   67890 S  38.9  15.6   08:12.34 lobster

龙虾(openclaw)和 MySQL 占用了大量的内存!

果断行动:删除可疑进程

看到 "lobster" 这个陌生的进程名,我第一反应是:"这肯定是恶意软件!" 果断执行:

# 找到 lobster 进程
ps aux | grep lobster

# 杀死进程
kill -9 5678

# 删除相关文件(如果存在)
find / -name "*lobster*" -type f -delete

删除后,服务器确实稍微流畅了一些,但问题依然存在,只是从"完全卡死"变成了"极度缓慢"。

关键发现:Docker Compose 的循环重启

在继续排查时,我突然意识到一个规律:每次我启动 docker-compose up 后,服务器就会开始变卡。

于是我开始逐个排查 Docker 容器:

# 查看容器状态
docker ps -a

# 查看容器日志
docker logs [container_id]

当我检查后端容器时,发现了惊人的真相:

docker logs backend-container

# 输出显示:
Starting backend service...
Error: Invalid environment variables
  at validateEnv (file:///app/src/env.ts:45:11)
  at file:///app/src/index.ts:12:1
Container exited with code 1
Restarting container...
Starting backend service...
Error: Invalid environment variables
  at validateEnv (file:///app/src/env.ts:45:11)
  at file:///app/src/index.ts:12:1
Container exited with code 1
Restarting container...

容器在无限重启循环中!

真相大白:一个环境变量的疏忽

进一步查看日志,发现了具体的错误信息:

# 更详细的错误信息
ZodError: [
  {
    "code": "invalid_type",
    "expected": "string",
    "received": "undefined",
    "path": ["DATABASE_URL"],
    "message": "Required"
  }
]

原来,我在 .env 文件中新增了一个 DATABASE_URL 字段,但忘记同步到服务端的 .env 文件了!

问题链条:完整的因果循环

让我来梳理一下整个问题的发生链条:

  1. 环境变量缺失:服务端缺少 DATABASE_URL 环境变量
  2. Zod 验证失败:启动时 Zod 检查环境变量,发现缺失字段
  3. 容器启动失败:服务启动失败,容器退出
  4. Docker 重启策略:Docker 配置了 restart: always,容器不断重启
  5. 数据库同步循环:每次重启都会执行 Drizzle 的 push 操作,同步数据库结构
  6. 资源耗尽:MySQL 不断执行表结构修改,占用大量资源
  7. 系统卡顿:系统资源被耗尽,SSH 连接超时

解决方案:一步修复所有问题

修复方法其实很简单:

# 1. 在服务端 .env 文件中添加缺失的环境变量
echo "DATABASE_URL=postgresql://user:password@localhost:5432/dbname" >> .env

# 2. 停止所有容器
docker-compose down

# 3. 重新启动
docker-compose up -d

# 4. 验证服务状态
docker ps
docker logs backend-container

经验教训与安全建议

这次经历让我学到了很多宝贵的经验:

安全方面:

  1. 永远不要对所有人开放 22 端口:使用 IP 白名单或 VPN 访问
  2. 使用 SSH 密钥认证:禁用密码登录
  3. 定期检查安全组规则:确保没有不必要的开放端口
  4. 监控异常进程:定期检查 topps aux 输出

运维方面:

  1. 环境变量管理:使用版本控制或配置管理工具管理环境变量
  2. 容器重启策略:谨慎使用 restart: always,考虑使用 restart: unless-stopped
  3. 日志监控:设置日志监控和告警
  4. 资源限制:为容器设置 CPU 和内存限制

故障排查流程:

  1. 从外到内:先检查网络、安全组等外部因素
  2. 观察规律:注意问题出现的规律和条件
  3. 分层排查:从系统层到应用层逐步排查
  4. 善用工具top, docker logs, journalctl 等工具是排查利器

结语

这次"惊魂记"虽然让我紧张了好几个小时,但最终收获颇丰。不仅修复了一个安全隐患,还学会了更系统的故障排查方法。

在云原生时代,一个小小的环境变量错误就可能引发连锁反应,导致整个系统瘫痪。作为开发者,我们需要时刻保持警惕,既要关注代码质量,也要重视运维安全。

记住:安全无小事,配置需谨慎!

返回首页