xss与v-html

在vue开发过程中的一些关于xss与v-html的思考

问题描述

  • vue开发中,在一个下拉框中需要将下拉数据中后端返回的html代码效果直接显示在下拉框中

    文档:在网站上动态渲染任意 HTML 是非常危险的,因为容易导致 XSS 攻击只在可信内容上使用 v-html,永不用在用户提交的内容上

  • 于是我开始思考,接口返回的内容算可信内容吗?会不会出现接口被截取导致返回内容不可信的情况发生?(事实上是我多虑了…)
  • 询问了我的哥哥(后端)以后,得到了靠谱的答案!

结论

  • 接口返回的内容可以使用v-html显示。
  • 接口返回的内容需要后端做应对xss攻击的安全处理以后才能显示
  • 因为 xss 攻击主要是为了获取用户的 cookie 也就是登录身份。攻击者如果都能截取请求返回值了,为什么不直接去取请求里的 cookie 呢?所以基本不用担心攻击者截取接口篡改返回内容。

前端预防XSS

  • 当然,如果还是担心,前端也可通过XSS工具替换特殊字符,如<变为&lt; >变为&gt,<script>变为&lt;script&gt;直接显示,而不会作为脚本执行
  • 前端在显示时替换,后端在存储时替换,都做总不会有错(可参考XSS网络攻击及防范)

vue中使用XSS工具

  • 除了XSS工具官网提供的使用方法,还可以通过覆盖html指令实现。引入组件XSS,在编译前将从vue-loader传入的compilerOptions.directivesbaseOptions.directives进行了合并。
    • 覆写可以方便的从根本上面解决XSS攻击的问题,不用担心后续其他开发重复操作等问题
  • 覆写步骤:
    1.npm isntall xss
    2.配置文件:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // @/utils/xss.j
    const xss = require('xss')

    const options = {
    css: false,
    onTag(tag, html, options) {
    if (tag === 'iframe') {
    return html.replace(/javascript:?/, '')
    }
    },
    // 避免把页面样式过滤掉
    onIgnoreTagAttr(tag, name, value, isWhiteAttr) {
    // 过滤掉标签上的事件
    if (/^[^on]/.test(name)) {
    return name + '="' + xss.escapeAttrValue(value) + '"'
    }
    },
    }
    const filter = new xss.FilterXSS(options)

    export default filter
    3.挂载全局:main.js页面引入(为了在 loader中能够通过该方法过滤全局的 v-html 返回数据)
    1
    2
    import xss from 'xss';
    Vue.prototype.xss = xss
    4.模块预处理:vue.config.js中覆写html指令(如果是nuxt.js框架写的就是在nuxt.config.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // ...
    module.exports = {
    // ...
    chainWebpack: config => {
    // ...

    // 增加 xss 处理
    config.module
    .rule('vue')
    .use('vue-loader')
    .loader('vue-loader')
    .tap(options => {
    options.compilerOptions.directives = {
    html (node, directiveMeta) {
    const props = node.props || (node.props = [])
    props.push({
    name: 'innerHTML',
    value: `$xss.process(_s(${directiveMeta.value}))`,
    })
    },
    }
    return options
    })
    },
    }
,