如何将 Buefy 的 Dialog 组件与用户提供的内容一起使用并在 XSS 方面是安全的

How to use Buefy's Dialog component with user provided content and be safe in terms of XSS

Buefy 的 Dialog 组件需要一个 message 属性 - 字符串。根据文档,该字符串可以包含 HTML。我想在字符串中使用模板值,但当然应该是 XSS 安全的。

当前不安全示例

这是不安全的,因为 this.name 是不安全的。我可以使用 NPM 包来 html 编码名称,但我更喜欢使用 Vue。

<script lang="ts">
import Vue from 'vue';

export default Vue.extend({
  props: {
    name: { type: String, required: false },
  },
  methods: {
    showModal() {
      this.$buefy.dialog.confirm({
        title: 'myTitle',
        message: `<p>Hello ${this.name}</p>`, // unsafe - possible XSS!
        cancelText: 'Cancel',
        confirmText: 'OK',
        type: 'is-success',
        onConfirm: async () => {
          // something
        },
      });
    },
  },
});
</script>

这是所用 Buefy 组件的问题,如文档所述here:

所需设置

我创建了一个新组件,在这个例子中我称之为 ModalMessage.Vue

<template>
    <p>Hello {{name}}</p>
</template>

<script lang="ts">
import Vue from 'vue';

export default Vue.extend({
  props: {
    name: { type: String, required: true },
  },
});
</script>

然后我想在 Typescript 中将 ModalMessage.Vue 渲染为 string

<script lang="ts">
import Vue from 'vue';
import ModalMessage from 'ModalMessage.vue';

export default Vue.extend({
  props: {
    name: { type: String, required: false },
  },
  methods: {
    showModal() {
      this.$buefy.dialog.confirm({
        title: 'myTitle',
        message:, // todo render ModalMessage and pass name prop
        cancelText: 'Cancel',
        confirmText: 'OK',
        type: 'is-success',
        onConfirm: async () => {
          // something
        },
      });
    },
  },
});
</script>

问题

我如何渲染 ModalMessage.Vue,并将 name 属性传递给 string

我很确定这是可能的 - 我以前见过。不幸的是,我无法在网络或 Whosebug 上找到它。我只能找到从 from 字符串呈现模板的问题,但我不需要那个 - 它需要是 to string.

试试这个。

<script lang="ts">
import Vue from 'vue';
import ModalMessage from 'ModalMessage.vue';

export default Vue.extend({
  props: {
    name: { type: String, required: false },
  }, 
  methods: {
    showModal() {
      const message = new Vue({
        components: { ModalMessage },
        render: h => h('ModalMessage', { name: this.name })
      })
      message.$mount()

      const dialog = this.$buefy.dialog.confirm({
        title: 'myTitle',
        message: [message._vnode],
        cancelText: 'Cancel',
        confirmText: 'OK',
        type: 'is-success',
        onConfirm: async () => {
          // something
        },
      });

      dialog.$on('hook:beforeDestroy', () => {
          message.$destroy()
      });
    },
  },
});
</script>

源代码:

演示:

恕我直言,您真正的问题是 “如何将 Buefy 的对话框组件与用户提供的内容一起使用,并在 XSS 方面是安全的”

所以您想要的是创建一些 HTML,在该 HTML 内容中包含一些用户提供的内容 (this.name) 并将其显示在对话框中。你是对的,将未经过滤的用户输入放入 Dialogmessage 参数是不安全的(如 Buefy docs 中明确指出)

但是您的“所需设置”似乎不必要地复杂。恕我直言,最简单的方法是使用(记录不详)事实,即 Buefy Dialog 配置对象的 message 参数可以是 VNode 的 Array 而不是字符串。它的文档很少,但从源代码 here and here 中可以清楚地看出,如果您传递一个 VNode 数组,Buefy 会将这些内容放入 Dialogs 默认插槽中,而不是使用 v-html 渲染它(这是危险的部分)

在 Vue 中获得 Array of VNode 的最简单方法是使用插槽...

所以组件:

<script lang="ts">
import Vue from 'vue';

export default Vue.extend({
  methods: {
    showModal() {
      this.$buefy.dialog.confirm({
        title: 'myTitle',
        message: this.$slots.default, // <- this is important part
        cancelText: 'Cancel',
        confirmText: 'OK',
        type: 'is-success',
        onConfirm: async () => {
          // something
        },
      });
    },
  },
});
</script>

及其用法:

<MyDialog>
  <p>Hello {{name}}</p>
</MyDialog>

<MyDialog>
  <ModalMessage :name="name" />
</MyDialog>

在这两种情况下,如果 name 包含任何 HTML,它将是 encoded by Vue

Here 是上述技术的简单演示(使用纯 JS - 不是 TS)