当源站被攻击者短暂攻陷并植入了恶意页面,即便你在几分钟内就修复了漏洞,噩梦也远未结束——CDN边缘节点上已经缓存了被篡改的内容。此时用户访问到的仍是挂马页面、钓鱼表单或黑帽SEO跳转,搜索引擎甚至可能再次抓取并标记你的站点为危险网站。清理CDN节点上的恶意缓存,是一个需要“快、准、后续验证”三步走的技术活。本文将从缓存污染的定位讲起,详细拆解精确刷新、批量清理、安全验证的完整操作链路。

一、先搞清楚缓存是如何被污染的
CDN的工作原理决定了它的“双刃剑”属性:源站响应被边缘节点缓存后,在TTL过期之前,节点不会回源。这意味着如果攻击者利用源站漏洞注入了恶意响应,而该响应恰好满足CDN的缓存条件,恶意内容就会被分发到全球多个节点。
常见的污染路径有几种:一是源站被上传Webshell后直接篡改页面;二是利用缓存投毒漏洞(Cache Poisoning),攻击者构造携带恶意Header或参数的请求,让CDN缓存了带恶意字段的响应;三是源站程序短暂返回了错误内容(如被篡改的404页面)被CDN缓存。不同的污染方式对应的清理策略不同——缓存投毒往往污染的是特定缓存键对应的资源,而源站篡改则可能污染整个目录。
在动手清理之前,务必先确认源站已经彻底修复。否则刷新缓存后CDN回源,又会拉取到恶意内容,白忙一场。可以通过curl直接请求源站IP绕过CDN验证:
# 绕过CDN直接访问源站,确认源站内容干净 curl -H "Host: www.ippipp.com" http://源站IP/index.html # 检查响应头中的可疑字段 curl -sI -H "Host: www.ippipp.com" http://源站IP/ | grep -iE "set-cookie|location|refresh"
二、精确刷新:只清该清的,别把整站打挂
发现恶意缓存后,很多管理员的直觉是“全站刷新”。但全站刷新意味着短时间内大量请求回源,源站可能直接被流量打垮,造成二次事故。正确的做法是先精确定位被污染的URL,再做针对性刷新。
定位手段主要有三类:第一,查看CDN的访问日志,筛选异常状态码和异常大小的响应;第二,用安全扫描工具(后文详述)对站点全量爬取,比对页面指纹找出被篡改页面;第三,检查搜索引擎快照与安全厂商的拦截提示,这些外部信号能帮你发现被注入的具体页面路径。
拿到污染URL清单后,就可以调用CDN的刷新API。以阿里云CDN为例:
import requests
import json
# 阿里云CDN刷新接口(需配合签名,此处简化展示)
url = "https://cdn.aliyuncs.com/"
params = {
"Action": "RefreshObjectCaches",
"ObjectPath": "https://www.ipipp.com/infected/page.html\nhttps://www.ipipp.com/malware/script.js",
"ObjectType": "File"
}
resp = requests.post(url, data=params)
print(json.loads(resp.text))绝大多数主流CDN(阿里云、腾讯云、Cloudflare、七牛等)都提供类似的Purge API。Cloudflare的用法更直接,支持按URL、按Tag、按主机名三种粒度:
# Cloudflare按URL精确清除
curl -X POST "https://api.cloudflare.com/client/v4/zones/区域ID/purge_cache" \
-H "Authorization: Bearer 你的API令牌" \
-H "Content-Type: application/json" \
--data '{"files":["https://www.ipipp.com/bad.html","https://www.ipipp.com/evil.js"]}'
# 按Cache-Tag批量清除(需要在源站响应头中输出Cache-Tag)
curl -X POST "https://api.cloudflare.com/client/v4/zones/区域ID/purge_cache" \
-H "Authorization: Bearer 你的API令牌" \
-H "Content-Type: application/json" \
--data '{"tags":["user-js","article-page"]}'这里要特别强调Cache-Tag的价值:如果源站在响应时输出了分类标签(如Cache-Tag: article-page),那么清理时可以按标签一次性清除某一类资源,既避免了全站刷新的回源风暴,又不会遗漏。这属于“治本”的设计,建议在架构层面提前布局。
刷新提交后注意验证生效状态。CDN刷新通常是异步的,各节点生效时间从几秒到几分钟不等,部分海外节点可能更久。可以通过带特定随机参数的请求探测节点状态,或使用CDN厂商提供的节点检测工具确认清理是否彻底。
三、安全扫描:验证清理效果,揪出漏网之鱼
刷新完成后并不代表万事大吉。恶意内容可能分布在多个URL变体上——比如同一页面带不同查询参数、大小写不同的路径、移动端与PC端的不同模板。人工核对几乎不可能穷尽,这时需要自动化安全扫描出场。
首选方案是部署爬虫式篡改检测工具,例如开源的WhatWeb结合自研脚本、或直接使用OWASP Zulu式的页面指纹比对。原理很简单:预先对正常页面采样计算哈希(剔除动态区域后),扫描时重新抓取计算,哈希不一致即告警。一个简易的检测脚本思路如下:
import hashlib
import requests
import re
def page_fingerprint(html):
# 剔除动态内容(时间戳、随机token等)后计算指纹
cleaned = re.sub(r'\d{10,}', '', html)
cleaned = re.sub(r'csrf[_-]?token.*?["\']', '', cleaned, flags=re.I)
return hashlib.md5(cleaned.encode()).hexdigest()
def scan(url, expected_fp):
# 通过CDN访问,检测边缘节点缓存内容
html = requests.get(url, timeout=10).text
# 检查常见恶意特征
evil_patterns = ['eval(atob', 'base64_decode', 'document.write(unescape',
'window.location.replace("http']
hits = [p for p in evil_patterns if p in html]
if hits:
return "污染", hits
if page_fingerprint(html) != expected_fp:
return "内容变更", []
return "正常", []
result, detail = scan("https://www.ipipp.com/index.html", "预存的正常指纹")
print(result, detail)除了自建脚本,也可以结合商业WAF的网页防篡改模块、云厂商的网站安全检测产品。扫描时注意两个细节:一是扫描请求必须走CDN(也就是扫描域名而非源站IP),否则测的是源站而非节点缓存;二是要在不同地域发起检测,因为CDN节点是分布式的,某地干净不代表全球干净。
对于已经被搜索引擎标记“危险网站”的情况,清理完成后要主动到Google Search Console、腾讯安全中心等平台提交申诉,附上清理证明,加快解除拦截标记的时间。
四、预防机制:让恶意缓存不再轻易出现
事后清理永远是被动的,稳固的预防体系才能把风险降到最低。第一层防线是收紧CDN缓存策略:对动态页面(登录页、搜索页、个人中心)禁用缓存或设置极短TTL,避免敏感响应被缓存;对静态资源启用内容哈希文件名(如app.a3f8c2.js),文件名即版本号,更新即换URL,天然规避陈旧缓存问题。
第二层防线是规范缓存键。很多缓存投毒漏洞的根源在于CDN把不安全的请求头或参数纳入了缓存键(或反之,忽略了本应区分的变量)。务必在CDN配置中明确Vary头和缓存键规则,测试攻击者是否可以通过构造畸形请求让CDN缓存差异化恶意响应。HttpOnly、CSP等响应头策略也要校验,防止恶意头被缓存后下发。
第三层防线是监控与告警。开启CDN的日志实时分析,对以下信号设置告警:单URL响应体积突变、响应中新增可疑Header、命中率异常波动、安全厂商拦截通报。把前文的指纹比对脚本接入定时任务,每小时对核心页面全量校验一次,发现异常自动触发刷新API并通知值班人员,实现“发现即清理”的自动化闭环。
最后保留一份完整的应急处置手册:污染URL清单的获取方法、刷新API的调用脚本、扫描验证的流程、申诉解封的入口。CDN缓存被污染是高概率的攻防场景,预案越细,恢复越快,业务损失越小。