CDN的出现让静态资源分发变得高效便捷,但它也引入了一个容易被忽视的安全边界问题:你的JS、CSS文件不再只存在于自己的服务器上,还分布在大量第三方节点中。任何一个环节被攻破,攻击者都可能往文件里塞入恶意代码,而CDN的缓存机制会把这个污染结果扩散给所有访问用户。防挂马的核心思路其实很朴素,就是确保浏览器拿到的文件和你发布的文件一模一样,下面从完整性校验和恶意代码扫描两个维度展开讲讲具体做法。

一、用Subresource Integrity从源头锁住文件指纹
SRI(Subresource Integrity,子资源完整性)是浏览器原生支持的一种校验机制,原理是对文件内容计算哈希值,然后在引用资源的标签中携带这个哈希。浏览器下载完CDN上的文件后,会重新计算一次哈希并与标签中声明的值比对,不一致就拒绝执行。目前主流浏览器都已支持,对于防御CDN节点文件被篡改非常直接有效。
使用方式很简单,在script或link标签中添加integrity属性,值由算法名和base64编码的哈希组成。计算哈希可以用openssl命令行完成:
# 计算文件的sha384哈希 openssl dgst -sha384 -binary app.js | openssl base64 -A # 输出类似:ocHamilton5rSg8GmS7MlExIk7bTn5I0YzFSZFu5uY
拿到哈希后,标签这样写:
<script src="https://cdn.example-static.com/js/app.js"
integrity="sha384-ocHamilton5rSg8GmS7MlExIk7bTn5I0YzFSZFu5uY"
crossorigin="anonymous"></script>
这里有个细节必须注意:integrity属性要生效,crossorigin属性不能少,否则同源策略会导致校验流程出错。另外SRI也有局限,它只保护通过标签加载的资源,动态创建script节点注入的地址同样要记得带上integrity属性;如果是自己搭建的构建流程,可以在打包阶段自动为所有资源注入哈希,避免手工维护。
二、建立静态文件的完整性基线与定期巡检
SRI解决的是浏览器端的校验,从运维角度还需要一套主动检测机制,能在用户大规模中招之前发现问题。做法是建立文件基线:每次发布时,对所有静态资源计算哈希并记录清单,之后定期从CDN节点拉取文件重新计算,比对是否出现偏差。
生成基线清单可以用一个简单脚本:
#!/bin/bash # 生成静态资源哈希清单 MANIFEST="manifest_$(date +%Y%m%d).txt" find ./dist -type f \( -name "*.js" -o -name "*.css" -o -name "*.html" \) | while read f; do echo "$(sha256sum "$f")" >> "$MANIFEST" done echo "基线文件已生成:$MANIFEST"
巡检脚本则从CDN的多个边缘节点拉取文件做比对,一旦发现哈希不一致立即触发告警。这里建议采样多个地域的节点,因为CDN缓存是分层的,有时污染只发生在个别区域,单点检测容易漏掉。巡检频率可以设为每5到10分钟一次,配合监控平台的告警通道,把响应时间压缩到分钟级。
除了主动拉取,还可以借助CDN厂商提供的回源校验或配置CDN回源时校验源站文件的ETag,开启后节点回源时会自动比对内容版本,能在一定程度上防止回源环节的污染被缓存住。不同厂商的功能名称不一样,但基本都提供类似能力,建议在控制台里找一下并开启。
三、静态资源恶意代码扫描的落地实践
哈希比对能发现文件变了,但变的内容是不是恶意代码,需要扫描引擎来判断。静态资源层面的恶意代码通常有一些明显特征,比如外部脚本动态注入可疑域名、eval处理超长加密字符串、伪造的表单提交、挖矿脚本中的WebSocket长连接特征等。可以基于这些特征写正则规则做初筛。
// 简化的敏感特征检测示例
const rules = [
{ name: "eval加密字符串", pattern: /eval\s*\(\s*(atob|unescape|String\.fromCharCode)/ },
{ name: "可疑域名注入", pattern: /https?:\/\/[^"'\s]*(xx|malware|tk-domain)\./ },
{ name: "键盘记录特征", pattern: /addEventListener\s*\(\s*['"]keypress['"]/ }
];
function scanContent(code) {
return rules.filter(r => r.pattern.test(code)).map(r => r.name);
}
自建规则库覆盖面有限,生产环境建议结合专业方案:一是接入第三方网页安全检测服务,定期对站点做全页面扫描;二是在CI流水线里加入卡点检查,构建产物在发布前必须通过扫描,检测到高危特征直接中断发布。这样即使开发者本地环境被感染,带毒文件也发布不出去。
CI卡点可以用Node脚本结合现有工具链实现,在流水线的打包阶段之后、上传CDN之前插入扫描任务,扫描通过才允许执行上传命令。把这个检查做成强制的红线,是很多团队踩过坑之后总结出的经验。
四、发现被挂马后的应急处理流程
假设巡检告警响了,确认CDN上的文件确实被篡改,这时候动作要快。第一步不是排查原因,而是止血:立即在CDN控制台刷新被污染的URL缓存,同时从源站确认文件本身是否干净。如果源站文件也是脏的,说明源站或发布链路已被入侵,需要直接下线污染版本回滚到上一个可信版本。
第二步是取证和排查。保留被篡改文件的样本,分析恶意代码的行为:是否收集用户输入、是否窃取Cookie、是否向哪些C2地址回传数据。这些信息决定了后续是否需要通知用户修改密码、是否需要上报安全事件。排查重点包括源站服务器是否被入侵、发布账号是否存在弱口令或泄露的AccessKey、CI环境是否被植入后门。
第三步是加固收尾。修复入侵路径后,重新构建发布干净版本,验证各节点缓存已全部刷新,并把这次事件中暴露的缺失补上,比如之前没开SRI的补上SRI、巡检频率过低的调高频率、没有CI卡点的加上扫描环节。安全防护从来不是一次性的配置,而是随攻防演进持续迭代的过程,把完整性校验、恶意代码扫描和应急预案串成闭环,CDN资源被挂马的风险才能被真正压到可控范围。