【前端】XSS 安全问题

2024/10/29 前端 共 4532 字,约 13 分钟

导读: 最近公司项目上突然出现一个 DOM 型的 XSS 安全问题,由于之前从没有接触过,本次就在此了解一下~

XSS 简单介绍

XSS 全称是 Cross Site Script,即 跨站脚本,但是在缩写上为了不和 层叠样式表(Cascading Style Sheets,缩写 CSS) 重复,所以才改名的 XSS

攻击者通常会向 Web 页面的 input 表单、URL、留言板等位置(即 HTML 注入) 插入恶意 JavaScript(JS) 代码,导致 用户或管理员 在访问的时候触发,从而达到攻击者控制用户浏览器的目的。

在一开始,这种攻击的演示案例是跨域的,所以叫 “跨站脚本”。但是发展到今天,由于 Javascript 的强大功能以及网站前端应用的复杂化,是否跨域已经不再重要。但是由于历史原因,这个名字保留了下来。

XSS 原理

服务器对用户提交的数据过滤不严,导致浏览器把用户的输入当成了 JS 代码并直接返回给客户端执行,从而实现对客户端的攻击目的。

XSS 类型

反射型 XSS(非持久型)

反射型 XSS 是非持久型 XSS,攻击相对于受害者而言是一次性的

具体表现在受害者点击了含有的恶意 JavaScript 脚本的 url,恶意代码并没有保存在目标网站,而 Web 应用程序只是 不加处理 的把该恶意脚本 “反射” 回受害者的浏览器而使受害者的浏览器执行相应的脚本。

存储型 XSS(持久型)

存储型 XSS 是持久型 XSS,由于数据由用户提交写入了数据库,再次取出该数据即会遭受攻击

具体表现是应用程序通过 Web 请求获取不可信赖的数据,在 未检验数据 是否存在 XSS 代码的情况下,便将其 存入数据库;当下一次从数据库中获取该数据时程序也未对其进行过滤,页面再次执行 XSS 代码持续攻击用户。

存储型 XSS 漏洞大多出现在 留言板、评论区,用户提交了包含 XSS 代码的留言到数据库,当目标用户查询留言时,那些留言的内容会从服务器解析之后加载出来。

DOM 型 XSS(非持久型)

DOM(Document Object Model)是非持久型 XSS,不与服务端交互,仅通过 DOM 操作前端代码进行的攻击

具体表现是一个平台和语言都中立的接口,可以使程序和脚本能够动态访问和更新文档的内容、结构以及样式,DOM 型 XSS 简单理解就是不与后台服务器产生数据交互,是一种通过 DOM 操作前端代码输出的时候产生的问题。

总结

修复 XSS 安全问题

引用三方库:xss

首先使用 npm 安装库:

npm install xss

Node.js 中使用:

var xss = require("xss");
var html = xss('<script>alert("xss");</script>');
console.log(html);

在浏览器端使用:

// Shim 模式 -------------
<script src="https://rawgit.com/leizongmin/js-xss/master/dist/xss.js"></script>
<script>
  // 使用函数名 filterXSS,用法一样
  var html = filterXSS('<script>alert("xss");</scr' + "ipt>");
  alert(html);
</script>

// AMD 模式 -------------
<script>
  require.config({
    baseUrl: "./",
    paths: {
      xss: "https://rawgit.com/leizongmin/js-xss/master/dist/xss.js",
    },
    shim: {
      xss: { exports: "filterXSS" },
    },
  });
  require(["xss"], function (xss) {
    var html = xss('<script>alert("xss");</scr' + "ipt>");
    alert(html);
  });
</script>

vue2.x 中使用:

<template>
  <div v-html="message"></div>
</template>

<script>
import xss from 'xss'

// message 原始数据
this.message = `执行以下代码:<img scr=1 onerror=alert(1) />`

// 更新 message 数据
const newMsg = xss(this.message)
if (newMsg) {
  this.message = newMsg
}
</script>

网上有网友使用了更优的方案:CSDN - vue 项目 v-html 存在植入 xss 攻击怎么解决

通过在编译前将从 vue-loader 传入的 compilerOptions.directivesbaseOptions.directives 进行了合并,这样就能覆盖 html 指令。

未验证,谨慎使用。

// main.js ---------
import xss from 'xss';
Vue.prototype.xss = xss

// vue.config.js ----------
chainWebpack: config => {
  config.module
    .rule("vue")
    .use("vue-loader")
    .loader("vue-loader")
    .tap(options => {
        options.compilerOptions.directives = {
            html(node, directiveMeta) {
                (node.props || (node.props = [])).push({
                    name: "innerHTML",
                    value: `xss(_s(${directiveMeta.value}))`
                });
            }
        };
        return options;
    });
}

引用三方库:Dompurify

首先使用 npm 安装库:

npm install dompurify

vue2.x 中使用:

<template>
  <div v-html="message"></div>
</template>

<script>
import DOMPurify from 'dompurify'

// message 原始数据
this.message = `执行以下代码:<img scr=1 onerror=alert(1) />`

// 更新 message 数据
const newMsg = DOMPurify.sanitize(this.message)
if (newMsg) {
  this.message = newMsg
}
</script>

实测效果:

  • onerror=alert(1) 代码会被过滤移除。

引用三方库:vue-dompurify-html

首先使用 npm 安装库:

npm install vue-dompurify-html
npm install vue-dompurify-html@4.1.4

vue2.x 中使用:

// main.js 文件中 ---------- 
import { createApp } from 'vue';
import App from './App.vue';
import VueDOMPurifyHTML from 'vue-dompurify-html';

const app = createApp(App);
app.use(VueDOMPurifyHTML);
app.mount('#app');

// 目标文件中,将 v-html 替换成 v-dompurify-html ------------
<template>
  <div v-dompurify-html="message"></div>
</template>

<script>
// message 原始数据
this.message = `执行以下代码:<img scr=1 onerror=alert(1) />`
</script>

注意点: 1、如果是 Vue3.x,需要使用 vue-dompurify-html 5.x 以上的版本; 2、如果是 Vue2.x,需要使用 vue-dompurify-html 4.x,建议使用 v4.1.4

其它解决方案:字符替换

核心点:是将 <> 转义替换成其他字符样式,从而避开 v-html 识别成标签问题。

可以参考:CSDN - vue 项目 v-html 存在植入 xss 攻击怎么解决

// 此处直接挪用的作者代码
let obj = {
  name: "asdfasd <img  onerror='alert(1)'",
  age: "模拟2 <img  onerror='alert(1)'",
}
Object.keys(obj).forEach((key,i) => {
  obj[key] = obj[key].replace(/</g,'&lt;') 
});

其它资料

1、以下为需过滤的常见字符:
[1] |(竖线符号)
[2] & (& 符号)
[3] ;(分号)
[4] $(美元符号)
[5] %(百分比符号)
[6] @(at 符号)
[7] '(单引号)
[8] "(引号)
[9] \'(反斜杠转义单引号)
[10] \"(反斜杠转义引号)
[11] <>(尖括号)
[12] ()(括号)
[13] +(加号)
[14] CR(回车符,ASCII 0x0d)
[15] LF(换行,ASCII 0x0a)
[16] ,(逗号)
[17] \(反斜杠)

2、在请求返回页面关键字符进行转义;
[1] “ (双引号):&quot
[2] ’ (单引号):&apos
[3] &(&符号):&amp
[4] <(左尖括号):&lt
[5] >(右尖括号):&gt

参考链接

版权声明

原文作者苜蓿鬼仙(苜蓿、jijiucheng)

原文链接GitHub.io - 苜蓿鬼仙 - 【前端】XSS 安全问题

发表日期:2024/10/29 16:00:00

更新日期:2024/10/29 16:00:00

GitHubGitHub - jijiucheng

个人博客GitHub.io - 苜蓿鬼仙

小专栏小专栏 - 苜蓿鬼仙

掘金掘金 - 苜蓿鬼仙

微博微博 - 苜蓿鬼仙

公众号微信 - 苜蓿小站

小程序微信 - 苜蓿小站

文档信息

Search

    Table of Contents