安全更新:一次体检,和一把差点重演的刀

可爱的站长 发布于 3 小时前 9 次阅读 1326 字 web


前几篇都在说"怎么变快",这篇说"怎么别被人打"。顺手做了一次完整的安全体检,把能堵的都堵上了,中间还被宝塔坑了一把——那部分放在最后,因为它差点让我重演 8 月那次事故。

一、最严重的一个:8080 端口把防护全绕过去了

架构是这样的:nginx(443) → 127.0.0.1:8080 → 容器 Apache。问题在于 compose 里写的是 "8080:80",这个写法等于 把容器的 80 端口直接映射到了公网。

实测:

http://服务器IP:8080/xmlrpc.php
→ 405 "XML-RPC server accepts POST requests only."

也就是说,我在 nginx 里写的 location = /xmlrpc.php { deny all; } 完全没用——攻击者绕开 nginx 直接打容器就行了。同理,安全响应头、访问日志、限流,全部形同虚设。

修法一行:

- "8080:80"          # 公网可达
+ "127.0.0.1:8080:80" # 只有宿主机能连

改完 docker compose up -d,外部直接 ECONNREFUSED。这种"以为有防护其实没有"的情况,比完全没防护还危险。

二、SSH 一直在被人尝试登录

翻了一下登录失败记录:

最近失败登录:447 次
chenhuil@115.29.185.119
pengfei@115.29.185.119
root@14.103.39.152

而且配置是 PermitRootLogin yes + PasswordAuthentication yes,服务器上也没装 fail2ban、防火墙是关的。等于全靠密码复杂度硬扛。

处理:

  • 装 fail2ban,10 分钟内失败 4 次就封 1 天
  • 生成密钥、验证能用之后,关掉密码登录(PasswordAuthentication no + PermitRootLogin prohibit-password)

验证方式很朴素但有效:先在本机用密钥登录成功,再关密码,再登一次。失败就回滚。

三、其他几个小口子

问题 影响 处理
REST /wp/v2/users 可读 暴露管理员用户名 reimu0721user 移除路由,返回 401
/?author=1 跳转 同样泄露用户名 重定向回首页
XML-RPC 仍可从 PHP 层响应 暴力破解入口 xmlrpc_enabled=false + 去掉 X-Pingback
注册只靠蜜罐 批量注册 算术人机验证(HMAC 签名)+ 同 IP 每小时限 3 次
登录无频率限制 可被爆破 同 IP 15 分钟失败 5 次锁定
没有 CSP XSS 少一层兜底 加 object-src 'none'; base-uri 'self'; frame-ancestors 'self'
wp-config.php 权限 644 同组用户可读数据库密码 改 640
登录报错区分"用户不存在/密码错" 辅助猜用户名 统一成"账号或密码不正确"
宝塔面板 11451 对公网开放 面板被爆破 = 整机失守 设置随机安全入口

四、被宝塔坑的那一把

处理面板时执行了 bt restart。然后整站 403。

查下来是这样的:宝塔面板重启时会跑它的"安全任务",把 /www/server/panel/data 递归改成 chmod 600 + chown root。而我这套 Docker 部署的数据目录正好在这个路径下面:

/www/server/panel/data/compose/myblog/
├── wp_data     ← WordPress 全站文件
└── db_data     ← MySQL 数据目录

结果就是:目录失去 x 位、文件属主变成 root,Apache 读不了 → 全站 500;MySQL 的数据目录也一起被打成 drw------- root:root——它当时还能跑,是因为进程已经打开了文件句柄,但下一次重启必死。

这就是 8 月那次"挂六天"的同一个故障:目录权限被改成 600,MySQL 误判成空库,然后 abort。

在面板源码里找到了元凶(class/jobs.py:486),一行被注释掉的:

chmod -R 600 /www/server/panel/data;
chown -R root:root /www/server/panel/data;

恢复方法:wp_data 归 33:33、目录 755 文件 644;db_data 归 999:999、目录 755 文件 660。两个容器重启,mysqld is alive。

加了兜底

光修好不够,得防它再来一次。加了个**每 5 分钟**跑一次的守护脚本:

  • 属主不对 → chown -R 回来
  • 目录缺 x 位 → chmod 755
  • 站点返回非 200 → 自动重启容器并记日志

五、现在的体检结果

敏感文件   .git/.env/wp-config.php.bak/.sql → 全部 404
wp-config  直接访问返回空 body(PHP 已执行,无源码泄露)
目录列表   uploads / plugins 均不可列
用户枚举   401
XML-RPC    403 + 无响应头
安全头     CSP / HSTS / XFO / nosniff / Referrer / Permissions 齐全
X-Powered-By  已隐藏
TLS        1.3 + HTTP/2,证书剩 89 天
MySQL      3306 未对公网开放
SSH        仅密钥登录 + fail2ban
8080       已关闭

六、给同样跑宝塔 + Docker 的人

  1. compose 里的端口映射一定写 127.0.0.1:端口:端口,除非你确实要对外暴露。反代架构下写 "8080:80" 等于把所有 nginx 防护白给。
  2. 数据目录别放在 /www/server/panel/data 下面。宝塔的安全任务会递归 chmod 这个目录,Docker 数据放里面就是待爆的地雷。
  3. 面板重启(bt restart)这种操作,动手前最好确认一遍站点健康检查。
  4. 改 SSH 之前一定先用密钥验证能登,再关密码——不然就是把自己关在门外。

折腾完这一轮最大的感受是:安全这件事,出事之前全是"看起来没问题"。8080 那个端口,配了三个月,谁也没觉得有问题——直到有人真的去敲了一下。