静态网站被篡改,最常见的情况是服务器权限被拿到,或者DNS被劫持。很多人以为用了HTTPS、设置了强密码就万事大吉,但实际上,攻击者往往不是从正面突破,而是通过你意想不到的侧信道进入。比如你本地的开发环境中了木马,FTP/SFTP密码被窃取;或者你使用的CI/CD流水线被污染,构建过程中植入了恶意脚本;甚至是你引用的第三方CDN资源被替换。要防住这些,不能只靠单一手段,必须从代码完整性校验、传输链路锁定、部署环境隔离、运行时监控四个维度构建纵深防御体系。

第一步:彻底切断明文传输,锁定部署通道

很多人部署静态网站还在用FTP,这等于把大门钥匙放在门垫下。FTP是明文协议,密码在网络上裸奔,任何中间节点都能嗅探到。必须立刻停用FTP,改用基于密钥认证的SFTP或SCP。更进一步,应该完全放弃在本地手动上传的习惯,强制使用Git进行版本控制,并通过Webhook触发自动部署。这样做的逻辑是:本地机器不可信,它是最大的攻击面。你的电脑可能被植入窃密木马,但服务器上的Git仓库和自动化脚本是相对纯净的。

具体操作上,在服务器端配置好Git裸仓库,设置接收钩子(post-receive hook)。当你向远程仓库推送代码时,钩子脚本会自动将最新版本的文件检出到Web目录。这里有个关键点:钩子脚本里绝对不能直接使用git checkout -f,这会把.git目录也暴露在Web目录下,造成源码泄露。正确的做法是使用git archive命令导出干净的文件快照,或者指定一个临时目录进行检出,再用rsync同步过去,并配合--delete参数清除Web目录下被删除的旧文件,防止攻击者利用残留的后门文件。

#!/bin/bash
# 服务器端 post-receive 钩子示例
WEB_DIR="/var/www/your-site"
GIT_DIR="/home/git/repos/your-site.git"
TMP_DIR="/tmp/your-site-deploy"

mkdir -p $TMP_DIR
git --work-tree=$TMP_DIR --git-dir=$GIT_DIR checkout -f
rsync -av --delete --exclude='.git' $TMP_DIR/ $WEB_DIR/
rm -rf $TMP_DIR
# 关键步骤:立即重置Web目录权限
find $WEB_DIR -type d -exec chmod 755 {} \;
find $WEB_DIR -type f -exec chmod 644 {} \;
chown -R www-data:www-data $WEB_DIR

注意脚本最后两行的权限重置。即使攻击者通过某种方式在文件里注入了恶意代码,只要Web服务器进程(如nginx、apache)对文件只有读权限,没有写权限,攻击者就无法通过Web漏洞篡改文件。这是最小权限原则的具体落地。

第二步:锁定域名解析,防止偷梁换柱

内容篡改不一定发生在服务器上,也可能发生在用户到服务器之间的链路上。DNS劫持是静态网站面临的第二大威胁。攻击者如果控制了你的域名解析,就可以将用户引导到一个伪造的站点,而你服务器上的文件完好无损,你甚至完全不知情。普通的DNS服务没有防篡改机制,你必须启用DNSSEC(域名系统安全扩展)。DNSSEC通过数字签名验证DNS响应的真实性,从根域到顶级域再到你的权威域,形成一条完整的信任链。

在域名注册商或DNS托管服务商处开启DNSSEC后,会生成一对密钥:KSK(密钥签名密钥)和ZSK(区域签名密钥)。你需要在域名注册商处添加DS记录,完成信任链的锚定。开启后,即使攻击者污染了某个递归DNS缓存,由于没有对应的私钥签名,伪造的解析结果也会被客户端或递归解析器直接丢弃。这能从根本上杜绝DNS层面的篡改。同时,配合CAA(证书颁发机构授权)记录,明确指定只允许特定的CA机构为你的域名签发证书,防止攻击者通过其他CA违规签发证书进行中间人攻击。

第三步:构建文件完整性监控,实现秒级发现

前面两步是预防,但万一防线被突破,你必须能第一时间知道文件被改了。传统的做法是用AIDE或Tripwire这类主机入侵检测系统,它们对文件生成哈希快照,定期对比。但对于静态网站,这种事后检测太慢了,攻击者可能在几分钟内完成篡改并传播恶意内容,然后迅速恢复原样,等你发现时日志都被清理了。

更硬核的做法是利用操作系统的inotify特性实现实时监控。在Linux服务器上部署一个轻量级的监控脚本,使用inotifywait命令监听Web目录下所有文件的修改、创建、删除事件。一旦检测到任何变化,立即触发告警,同时自动从Git备份中恢复被篡改的文件,并阻断部署管道。这样就把应急响应时间从小时级压缩到了毫秒级。

#!/bin/bash
# 实时文件完整性监控脚本
WATCH_DIR="/var/www/your-site"
GIT_REPO="/home/git/repos/your-site.git"

inotifywait -m -r -e modify,create,delete,move --format '%w%f %e' $WATCH_DIR | while read FILE EVENT
do
    echo "[ALERT] File changed: $FILE - Event: $EVENT"
    # 发送告警通知,例如调用Webhook机器人
    curl -X POST -d "msg=Website file tampered: $FILE" https://your-alert-api.com/alert
    
    # 紧急恢复:直接从Git仓库重置所有文件
    git --work-tree=$WATCH_DIR --git-dir=$GIT_REPO checkout -f
    # 再次重置权限
    find $WATCH_DIR -type d -exec chmod 755 {} \;
    find $WATCH_DIR -type f -exec chmod 644 {} \;
done

这个脚本要放在screen或tmux会话里后台运行,或者配置成systemd服务,确保它始终在线。有人担心inotifywait性能,对于静态网站几千个文件来说,这点开销完全可以忽略不计。更重要的是,这个机制能捕捉到任何通过Web漏洞、错误配置甚至恶意内部人员产生的文件变更,是最后一道自动止损线。

第四步:锁定浏览器端资源,实施SRI校验

静态网站大量依赖外部CDN加速和第三方库。如果你引用了CDN上的jQuery或Bootstrap,而CDN节点被入侵,或者你使用的第三方库作者账号被盗,发布了带后门的版本,你的网站就会变成攻击者的钓鱼页面或挖矿代理。解决这个问题必须使用SRI(子资源完整性)属性。在script和link标签里加上integrity属性,浏览器会在加载资源前计算其哈希值,与声明的哈希比对,不匹配则拒绝执行。

生成SRI哈希很简单,以sha384算法为例,使用openssl命令对下载到本地的库文件进行计算,然后将哈希值填入标签。这样即使CDN被完全控制,攻击者替换了恶意脚本,浏览器也不会执行,因为哈希对不上。这是零成本的防护,但能有效防御供应链攻击。

# 生成SRI哈希
openssl dgst -sha384 -binary jquery.min.js | openssl base64 -A
# 输出类似:sha384-abc123def456...

在HTML中引用时,标签应写成:

<script src="https://cdn.example.com/jquery.min.js" 
        integrity="sha384-abc123def456..." 
        crossorigin="anonymous"></script>

注意crossorigin="anonymous"属性必须加上,否则浏览器不会对跨域资源执行SRI校验。对于自己站点托管的静态资源,同样建议加上SRI,因为攻击者可能通过中间人攻击或浏览器扩展篡改本地资源。配合内容安全策略(CSP)头部,可以进一步限制脚本来源,形成纵深防御。

第五步:强化Web服务器配置,消除信息泄露

Web服务器本身的配置是最后一道门。Nginx或Apache的默认配置往往会泄露服务器版本信息,这给攻击者提供了精准打击的线索。需要在配置文件中关闭版本号显示,隐藏后端技术栈。同时,针对静态文件设置强力的缓存控制,但这不仅仅是性能优化,更是安全措施。通过设置正确的Cache-Control头,可以防止浏览器和中间代理缓存敏感内容。

更重要的是,要配置严格的CSP头。对于静态网站,理想情况下应该做到只允许加载本域资源,禁止内联脚本和eval执行。一个严格的CSP策略可以这样写:default-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self';。这意味着所有资源只能从本站加载,彻底堵死跨站脚本攻击的执行通道。如果必须使用内联样式或脚本,使用nonce或hash机制进行白名单放行,而不是直接使用unsafe-inline。此外,添加X-Content-Type-Options: nosniff头防止MIME类型嗅探攻击,添加X-Frame-Options: DENY头防止点击劫持,添加Referrer-Policy: strict-origin-when-cross-origin头控制来源泄露。这些头部配置简单,但能极大提升攻击门槛。

第六步:部署静态资源加密与访问控制

对于包含敏感文档或付费内容的静态网站,仅仅依赖前端JavaScript验证是自欺欺人。真正的访问控制必须在边缘层完成。如果你使用对象存储配合CDN分发,应该启用CDN的鉴权功能,比如URL鉴权或Token鉴权。URL鉴权通过共享密钥生成带有过期时间的签名URL,CDN节点验证签名通过后才返回内容。这能防止资源被直接盗链或未授权访问。

配置URL鉴权时,鉴权参数通常包含时间戳和MD5或SHA256签名。生成规则一般是将URI、时间戳、密钥拼接后计算哈希。CDN边缘节点收到请求后,独立计算签名并比对,同时检查时间戳是否过期。这种机制下,即使攻击者拿到了资源URL,超过有效期后也无法访问。对于更高级的需求,可以在CDN边缘运行Serverless函数,实现基于Cookie或Token的鉴权逻辑,将静态网站动态化处理,但保持源站文件仍然是静态的。

第七步:建立安全基线,定期演练恢复流程

所有技术手段部署完成后,最后一步是建立可验证的安全基线。将整个网站的初始文件列表和哈希值导出为清单文件,存储在离线介质中。定期(比如每天)重新计算全站文件哈希,与基线对比,任何差异都需要人工确认。同时,定期执行恢复演练,假设服务器完全被入侵,从Git仓库重新部署整个站点需要多长时间?这个时间应该控制在分钟级。演练过程中要验证DNSSEC配置是否正常、SRI哈希是否匹配、CSP头是否生效。只有经过实战检验的防御体系才是真正可靠的。

静态网站的安全防护,核心思想是假设任何单一环节都可能被突破,因此必须在每个层面都设置检测和阻断机制。从本地的代码提交,到服务器的自动部署,再到DNS解析和浏览器加载,形成一条完整的信任链。任何一个环节出现异常,都能被下一个环节发现并阻断,同时触发告警让人工介入。这种纵深防御架构,才是静态网站防篡改防入侵的正确姿势。