Web前端安全系列之:XSS攻防及Vue防御(万字长文)

article/2025/11/8 16:24:12

关注公众号

@笑果杂谈

前言 Web 安全的兴起

Web 攻击技术的发展也可以分为几个阶段。在 Web 1.0 时代,人们更多的是关注服务器端
动态脚本的安全问题,比如将一个可执行脚本(俗称 webshell)上传到服务器上,从而获得权
限。后续有出现了SQL注入,SQL注入的出现是Web安全史上的一个里程碑,SQL注入漏洞至今仍然是Web安全领域中的一个重要组成部分。再后续另一个里程碑的安全问题问世–XSS(跨站脚本攻击)。伴随着 Web 2.0 的兴起,XSSCSRF 等攻击已经变得更为强大。Web 攻击的思路也从服务器端转向了客户端,转向了浏览器和用户。

Web 技术发展到今天,构建出了丰富多彩的互联网。互联网业务的蓬勃发展,也催生出了
许多新兴的脚本语言,比如 PythonRubyNodeJS 等,敏捷开发成为互联网的主旋律。而手机技术、移动互联网的兴起,也给 HTML 5 带来了新的机遇和挑战。与此同时,Web 安全技术,也将紧跟着互联网发展的脚步,不断地演化出新的变化。

跨站脚本攻击(XSS)是客户端脚本安全中的头号大敌。OWASP TOP 10 威胁多次把 XSS
列在榜首,该篇文章将重点介绍XSS的攻防问题

初探XSS

跨站脚本攻击,英文全称是 Cross Site Script,本来缩写是 CSS,但是为了和层叠样式表(Cascading Style SheetCSS)有所区别,所以在安全领域叫做“XSS”。

XSS攻击,通常指黑客通过HTML注入 篡改网页,插入恶意脚本,从而在用户浏览网页时,控制用户浏览器的一种攻击行为。在这种行为最初出现之时,所有的演示案例全是跨域行为,所以叫做 “跨站脚本” 。时至今日,随着Web端功能的复杂化,应用化,是否跨站已经不重要了,但 XSS这个名字却一直保留下来。

随着Web发展迅速发展,Web开发已经被应用的非常广泛了,由之前的单一PC端扩展到现在的移动端(APPH5),甚至包括桌面工具、设备大屏等等,所以在产生的应用场景越来越多,越来越复杂的情况下,同时大多数互联网(尤其是传统行业)的产品开发版本迭代上线时间非常短,一周一版本,两周一大版本的情况下,忽略了安全这一重要属性,一旦遭到攻击,后果将不堪设想。

XSS攻击类型分类

XSS攻击可以分为3类:反射型(非持久型)、存储型(持久型)、基于DOM XSS

反射型

反射型XSS只是简单地把用户输入的数据“反射”给浏览器。也就是说,黑客往往需要诱使用户“点击”一个恶意链接,才能攻击成功。反射型XSS也叫做 “非持久型 XSS”(Non-persistent XSS

通常反射型XSS的恶意代码存在URL里,通过URL传递参数的功能,如网站搜索、跳转等。由于需要用户主动打开恶意的URL才能生效,攻击者往往会结合多种手段诱导用户点击。

一个最初级的反射型攻击是:我们对网页数据进行获取:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>XSS攻防演练</title>
</head>
<body><div id="t"></div><input id="s" type="button" value="这是一个按钮" onclick="test()">
</body>
<script>function test() {const arr = ['自定义的数据1', '自定义的数据2', '自定义的数据3', '<img src="11" οnerrοr="console.log(window.localStorage)" />']const t = document.querySelector('#t')arr.forEach(item => {const p = document.createElement('p')p.innerHTML = itemt.append(p)})}
</script>
</html>

当黑客点击这是一个按钮时,即可轻松获取本地localStorage数据,从而获取关键信息。

存储型

存储型 XSS 会把用户输入的数据“存储”在服务器端。这种 XSS 具有很强的稳定性。

比较常见的一个场景就是,黑客写下一篇包含有恶意 JavaScript 代码的博客文章,文章发表后,所有访问该博客文章的用户,都会在他们的浏览器中执行这段恶意的 JavaScript 代码。黑客把恶意的脚本保存到服务器端,所以这种 XSS 攻击就叫做 “存储型 XSS

<!-- 例如我们分别在网站中的输入框中输入以下信息,并保存到远程数据库 -->
<img src="11" onerror="console.log(window.localStorage)" />
<img src="11" onerror="alert(111)" />

页面输入

在这里插入图片描述

在这里插入图片描述

使用者浏览页面,分别先后触发了alert弹框和localStorage获取本地数据:

在这里插入图片描述
在这里插入图片描述

以上就是一个典型的存储型攻击。

基于DOM XSS

实际上,这种类型的XSS并非按照“数据是否保存在服务器端”来划分,DOM Based XSS从效果上来说也是反射型XSS。单独划分出来,是因为DOM Based XSS 的形成原因比较特别,发现它的安全专家专门提出了这种类型的XSSDOM 型 XSS跟前两种XSS的区别:DOM 型 XSS攻击中,取出和执行恶意代码由浏览器端完成,属于前端JavaScript自身的安全漏洞,而其他两种XSS都属于服务端的安全漏洞。

接下来我们来看一个简单的示例:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>XSS攻防演练</title>
</head>
<body><h3>基于DOM的XSS</h3><input type="text" id="input"><button id="btn">提交内容</button><div id="div"></div>
</body>
<script>const input = document.getElementById('input');const btn = document.getElementById('btn');const div = document.getElementById('div');let inputValue;input.addEventListener('change', (e) => {inputValue = e.target.value;}, false);btn.addEventListener('click', () => {div.innerHTML = `<a href=${inputValue}>链接地址</a>`}, false);
</script>
</html>

我们再页面输入框中输入以下文本'' onclick=alert(/xss/),这里的''引号是为了关闭掉href属性,给它赋予了一个空值。然后点击提交内容按钮,则页面中的<div id="div"></div>标签包含了一下html内容

<a href onlick="alert(/xss/)">链接地址</a>

在这里插入图片描述

在这里插入图片描述

XSS攻击防御

关于XSS的防御是非常复杂的,值得幸运的是现代浏览器、前端框架/库已经帮我们做了相当大的一部分工作。

HttpOnly

HttpOnly 最早是由微软提出,并在IE 6中最先实现的,至今已经逐渐成为一个标准。浏览器将禁止页面的JavaScript访问带有HttpOnly属性的Cookie。所以我们需要在http的响应头set-cookie时设置httpOnly,让浏览器知道不能通过document.cookie的方式获取到cookie内容。

严格地说,HttpOnly 并非为了对抗 XSS——HttpOnly 解决的是XSS后的 Cookie 劫持攻击。所以说使用HttpOnly有助于缓解XSS攻击,但仍然需要其他能够解决XSS漏洞的方案;

输入检查

对于用户的输入内容我们需要持怀疑态度。在对输入不做任何过滤检查的情况下用户可能输入任何字符串。比如我们期望输入的内容是:hello word, 也许我们收到的内容是onclick=alert(/xss/)

XSS的防御上,输入检查一般是检查用户输入的数据中是否包含一些特殊字符,如<、>、’、”等。如果发现存在特殊字符,则将这些字符过滤或者编码。这种输入检查的方式,可以称为“XSS Filter”。互联网上有很多开源的“XSS Filter”的实现。比如一个简单的htmlencode转义:

const htmlEncode = function (handleString){return handleString.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/ /g,"&nbsp;").replace(/\'/g,"&#39;").replace(/\"/g,"&quot;");
}

但是输入检查也有弊端,比如

  • 攻击者绕过前端页面直接使用接口就可以提交恶意代码到远程库中。
  • 输入数据,还可能会被展示在多个地方,每个地方的语境可能各不相同,如果使用单一的替换操作,则可能会出现问题。输入检查也需要有针对性,如果我们想表达的意思是一个数小于另一个数( 3 < 4),前端转义后的字符就变成3 &lt; 4,当这个值被存到远端时后,再通过AJAX获取使用就会造成不必要的麻烦,比如我就进行数值计算等等。

输出检查

一般来说,除富文本的输出外,在变量输出到HTML页面时,可以使用编码或转义的方式来防御XSS攻击。

XSS的本质还是一种“HTML 注入”,用户的数据被当成了HTML代码一部分来执行,从而混淆了原本的语义,产生了新的语义。

如同输入检查一样,我们可以对输出进行编码转义。

1.在HTML中输出

比如我们的html代码中有这样一段代码:

<div>$htmlVar</div>
<a href="">$htmlVar</a>

如果输出的变量没有进行安全处理,直接使用并渲染在页面中,都能导致直接产生XSS。最终的结果可能生成一下代码:

<div><script>alert('我是一个XSS攻击者')</script></div>
<a href="#"><img href="" onclick="alert('我是另外一个XSS攻击者')"></a>

这个预防的方法就是对html进行转义检查

2. 在HTML属性中输出

如果我们的html属性时动态值,那么利用属性也可以被攻击;

<div id="testXSS" data-name=""></div>

现在往data-name属性中插入一段未转义的代码"><script>alert('我是一个XSS攻击者')</script><",结果如下:

<div id="testXSS" data-name=""><script>alert('我是一个XSS攻击者')</script><""></div>

3. 在<script>标签中输出

<script>标签中输出时,首先应该确保输出的变量在引号中。

<script>// 假设userData是攻击者注入的数据let xssVar = userData;
</script>

攻击者需要先闭合引号才能实施XSS攻击:

<script>// 假设userData是攻击者注入的数据let xssVar = "";alert('我是一个script XSS攻击者');
</script>

4. 在CSS中输出

CSSstylestyle attribute 中形成 XSS 的方式非常多样化,所以,一般来说,尽可能禁止用户可控制的变量在“<style>标签”、“HTML标签的style属性”以及“CSS 文件”中输出。如果一定有这样的需求,则推荐使用一个关于CSS转义库。

防御DOM Based XSS

DOM Based XSS是一种比较特别的XSS漏洞,前文提到的几种防御方法都不太适用,需要特别对待。这个本质上,实际上就是网站前端JavaScript代码本身不够严谨,把不可信的数据当作代码执行了。

如果用 Vue/React 技术栈,并且不使用 v-html/dangerouslySetInnerHTML功能,就在前端render阶段避免innerHTMLouterHTMLXSS隐患。稍后会有专门的Vue关于XSS的防御段落。

会触发DOM Based XSS的地方有很多,以下几个地方是JavaScript输出到HTML页面的必经之路。

  • document.write();
  • document.writeln();
  • xxx.innerHTML();
  • xxx.outerHTML();
  • xxx.innerHTML.replace();
  • document.attachEvent();
  • window.attachEvent();
  • window.location();
  • window.name;

所以开发者需要重点关注这几个地方的参数是否可以被用户控制。如果项目中有用到这些的话,一定要避免在字符串中拼接不可信数据。

Vue中的XSS防御

如果你在项目中使用了Vue作为前端开发框架,恭喜你,Vue将为你解决绝大多数的XSS攻击问题,但是Vue不是一个预防XSS攻击的框架,在开发使用的时候还是有被攻击的漏洞存在;

Vue中的防御措施

不论使用模板还是渲染函数,Vue都会将插值的内容都会自动转义。也就是说对于这份模板:

<template><p>{{userData}}</p>
</template><script>// 从远程获取的数据userData = "<script>alert('xss')</script>"
</script>

最终编译后页面显示的html源码内容如下:

<p><script>alert('xss')</script>
</p>

原因是Vue帮我们对数据进行了转义,因此避免了脚本注入。该转义通过诸如 textContent 的浏览器原生的 API 完成,所以除非浏览器本身存在安全漏洞,否则不会存在安全漏洞。转义后的内容如下:

&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;

注入HTML

如果你要动态注入远程的HTML内容,首先你应该确保这些内容是安全有效的,否则你应该采取一些防御措施,去过滤或转义掉一些危险的标签符号;例如你可以这样显示的渲染HTML

<!-- 当使用模版时 -->
<div v-html="userProvidedHtml"></div><!-- 当使用渲染函数时 -->
<script>h('div', {domProps: {innerHTML: this.userProvidedHtml}})
</script>
<!-- 当使用JSX 的渲染函数时 -->
<div domPropsInnerHTML={this.userProvidedHtml}></div>

例如我们可以使用一个简单的方法(或者引用一个更加健壮的库/插件XSS来过滤一遍这个远程的userProvidedHtml数据内容,以确保安全;

// 一个简单的函数,通过转义<为&lt以及>为&gt来实现防御HTML节点内容
const escape = function(str){return str.replace(/</g, '&lt;').replace(/>/g, '&gt;')
}

样式注入

在使用Vue 要在模板内避免渲染 style 标签:

<style>{{ userProvidedStyles }}</style>

这是因为,一但通过userProvidedStyles,恶意用户仍可以提供 CSS 来进行“点击诈骗”,例如将链接的样式设置为一个透明的方框覆盖在“登录”按钮之上。然后再把 https://user-XSS-website.com/ 做成你的应用的登录页的样子,它们就可能获取一个用户真实的登录信息,所以Vue推荐使用对象语法且只允许用户提供特定的可以安全控制的property的值:

<!-- sanitizedUrl应为受控的地址 -->
<av-bind:href="sanitizedUrl"v-bind:style="{color: userProvidedColor,background: userProvidedBackground}"
>click me
</a>

安全问题“没有银弹”

在解决安全问题的过程中,不可能一劳永逸,也就是说“没有银弹”。

一般来说,人们都会讨厌麻烦的事情,在潜意识里希望能够让麻烦越远越好。而安全,正是一件麻烦的事情,而且是无法逃避的麻烦。任何人想要一劳永逸地解决安全问题,都属于一相情愿,是“自己骗自己”,是不现实的。

最佳实践

通用的规则是只要允许执行未过滤的用户提供的内容 (不论作为 HTMLJavaScript 甚至 CSS),你就可能令自己处于被攻击的境地。这些建议实际上不论使用 VueReact还是别的框架甚至不使用框架,都是成立的。

参考文献

  • 白帽子讲Web安全
  • Vue官方文档

http://chatgpt.dhexx.cn/article/p1c4dGXQ.shtml

相关文章

前端安全:CSRF、XSS该怎么防御?

近几年随着业务的不断发展&#xff0c;前端随之面临很多安全挑战。我们在日常开发中也需要不断预防和修复安全漏洞。接下来&#xff0c;梳理一些场景的前端安全问题和对应的解决方案。 XSS攻击介绍 XSS是后端的责任&#xff0c;后端应该在用户提交数据的接口对隐私敏感的数据…

如何解决Web前端安全问题?

我国网络技术水平的提升&#xff0c;带动着WEB前端业务量的显著增长&#xff0c;人们对于网络服务的需求也日益复杂&#xff0c;与此同时&#xff0c;越来越多的黑客出现&#xff0c;其攻击水平也有了明显提升&#xff0c;WEB前端也成为了众多黑客进行网络攻击的主要目标。 因…

前端安全问题的解决方法

目录 前言&#xff1a; 1.常见的安全性问题 2.XSS攻击的解释和解决方法 2.1 XSS攻击是什么&#xff1a; 2.2 经常出现的原因&#xff1a;用户输入&#xff0c;如input框 2.3 防御方式&#xff1a; 3.CSRF&#xff08;跨站请求伪造&#xff09;的解释和解决方法 3.1 是什么…

前端常见的安全问题

一、XSS &#xff08;Cross-Site Scripting&#xff09;跨站脚本攻击 通常指通过“HTML注入”篡改了网页&#xff0c;插入了恶意的脚本&#xff0c;从而在用户浏览网页时&#xff0c;获取用户信息、控制用户浏览器等的一种攻击 分类&#xff1a;持久性&#xff08;存储型xss&…

前端常见安全性问题

文章目录 一、常见的安全性问题二、XXS攻击&#xff08;Cross Site Scripting&#xff09;&#xff08;跨站脚本攻击&#xff09;三、CSRF安全漏洞&#xff08;跨站请求伪造&#xff09;四、文件上传漏洞五、限制URL访问&#xff0c;越权访问六、不安全的加密存储七、SQL注入攻…

前端WEB安全

一、浏览器安全 首先了解前端web安全知识&#xff0c;比不可绕开的基础就是同源策略了&#xff0c;同源策略&#xff08;Same Origin Policy&#xff09;是一种约定&#xff0c;它是浏览器最核心也最基本的安全功能&#xff0c;如果缺少了同源策略&#xff0c;则浏览器的正常功…

前端安全问题以及解决方案汇总

随着大前端的快速发展&#xff0c;各种技术不断更新&#xff0c;前端的安全问题也值得我们重视。今天我们来聊一聊前端常见的7个安全方面问题&#xff1a; 1.iframe 2.opener 3.CSRF&#xff08;跨站请求伪造&#xff09; 4.XSS&#xff08;跨站脚本攻击&#xff09; 5.ClickJa…

浅谈前端安全

1 什么是前端安全&#xff1f; 所有发生在浏览器、单页面应用、Web页面当中的安全问题都算是算是“前端安全问题”。或者就是说所有需要前端开发人员去修复的问题都属于前端安全问题。 2 前端目前存在哪些安全问题 2.1 xss&#xff08;Cross Site Scripting&#xff09;跨站…

数据结构中头结点的作用

数据结构中&#xff0c;在单链表的开始结点之前附设一个类型相同的结点&#xff0c;称之为头结点。头结点的数据域可以不存储任何信息&#xff0c;头结点的指针域存储指向开始结点的指针&#xff08;即第一个元素结点的存储位置&#xff09;。 作用 1、防止单链表是空的而设的…

链表的首元结点、头结点、头指针的区别(图示)

1、首元结点&#xff1a;就是指链表中存储第一个数据元素a1的结点。 2、头结点&#xff1a;它是在首元结点之前附设的一个节点&#xff0c;其指针域指向首元结点。头结点的数据域可以不存储任何信息&#xff0c;也可以存储与数据元素类型的其他附加信息&#xff0c;例如&#…

单链表两种结构:头指针,头结点与首元结点辨析

要区分头指针&#xff0c;头结点&#xff0c;首元结点这几个概念分别指什么&#xff0c;需要结构图来帮助阐释。 我们先来看一种不太严谨的表现形式。 1->2->3->4->NULL 头结点这个名词很具有迷惑性。比如这张图中&#xff0c;刚接触的新手很容易以为1就是来自头…

【头指针,头结点、首元节点】

链表中第一个结点的存储位置叫做头指针&#xff0c;那么整个链表的存取就必须是从头指针开始进行了。之后的每一个结点&#xff0c;其实就是上一个的后继指针指向的位置。 这里有个地方要注意&#xff0c;就是对头指针概念的理解&#xff0c;这个很重要。“链表中第一个结点的存…

单链表:头指针 头结点 首元结点区别与联系

单链表组成&#xff1a;头指针头结点第一个结点第二个结点第三个结点.............第N个结点。 注意下图中&#xff1a;首元结点就是第一个结点。 头指针&#xff1a;作用&#xff1a;1.头指针的名字就是本链表的名字 2.头指针也是一个指针&#xff0c;存放头节点地址 头结…

链表:头结点

简介&#xff1a; 头结点的数据域可以不存储任何信息&#xff0c;头结点的指针域存储指向第一个结点的指针&#xff08;即第一个元素结点的存储位置&#xff09;。头结点的作用是使所有链表&#xff08;包括空表&#xff09;的头指针非空&#xff0c;并使对单链表的插入、删除操…

头指针和头结点

在了解头指针和头结点之前&#xff0c;先介绍数据域、指针域、结点和链表的定义&#xff1a; 数据域用来存储元素的数值数据 指针域存储直接后继节点的存储位置 结点是数据元素的存储映像。由数据域和指针域两部分组成 链表 n个结点由指针链组成一个链表。它是线性表的链式…

关于链表中头指针和头结点的理解

线性表使用顺序&#xff08;数组&#xff09;存储时有个弊端&#xff0c;那就是在插入和删除时需要大量的移动数据&#xff0c;这显示是非常消耗时间的&#xff0c;所以可以采用链式存储&#xff0c;即有一个指针域&#xff08;单链表&#xff09;&#xff0c;来记录下个结点的…

首元结点,头结点,头指针区别

https://www.cnblogs.com/letianpaiai/p/13227755.html 首元结点就是指链表中存储的第一个数据元素的结点&#xff0c;就是结点Li 头指针是指向链表中的第一个结点的指针&#xff0c;如果有头结点&#xff0c;那么头指针所指结点为头结点&#xff0c;否则为首元结点 头结点是…

头指针、头结点、首元结点概念区别

转自&#xff1a;https://blog.csdn.net/liangxingda/article/details/52755800 链表中第一个结点的存储位置叫做头指针&#xff0c;那么整个链表的存取就必须是从头指针开始进行了。之后的每一个结点&#xff0c;其实就是上一个的后继指针指向的位置。 这里有个地方要注意&a…

链表的头节点理解

不管带不带头节点&#xff0c;头指针始终指向第一个结点&#xff0c;头指针始终指向第一个结点&#xff0c;而头节点是带头结点的链表的第一个结点&#xff0c;结点内通常不存储信息。 图示如下&#xff1a; 2.在建立链表时&#xff0c;如果是不带头节点&#xff0c;第一个结…

头结点的作用

数据结构中&#xff0c;在单链表的开始结点之前附设一个类型相同的结点&#xff0c;称之为头结点。头结点的数据域可以不存储任何信息&#xff0c;头结点的指针域存储指向开始结点的指针&#xff08;即第一个元素结点的存储位置&#xff09;。 作用 1、防止单链表是空的而设的&…