@@ -14,6 +14,8 @@ import {
isFalsyAttrValue
} from 'web/util/attrs'
+import { isSSRUnsafeAttr } from 'web/server/util'
+
export default function renderAttrs (node: VNodeWithData): string {
let attrs = node.data.attrs
let res = ''
@@ -34,6 +36,9 @@ export default function renderAttrs (node: VNodeWithData): string {
}
for (const key in attrs) {
+ if (isSSRUnsafeAttr(key)) {
+ continue
+ }
if (key === 'style') {
// leave it to the style module
continue
@@ -18,6 +18,11 @@ const isAttr = makeMap(
'target,title,type,usemap,value,width,wrap'
)
+const unsafeAttrCharRE = /[>/="'\u0009\u000a\u000c\u0020]/
+export const isSSRUnsafeAttr = (name: string): boolean => {
+ return unsafeAttrCharRE.test(name)
+}
/* istanbul ignore next */
const isRenderableAttr = (name: string): boolean => {
return (
@@ -1,6 +1,6 @@
/* @flow */
-import { escape } from 'web/server/util'
+import { escape, isSSRUnsafeAttr } from 'web/server/util'
import { isObject, extend } from 'shared/util'
import { renderAttr } from 'web/server/modules/attrs'
import { renderClass } from 'web/util/class'
@@ -109,6 +109,9 @@ function renderStringList (
function renderAttrs (obj: Object): string {
for (const key in obj) {
res += renderAttr(key, obj[key])
return res
@@ -929,6 +929,40 @@ describe('SSR: renderToString', () => {
})
+ it('should prevent xss in attribute names', done => {
+ renderVmWithOptions({
+ data: {
+ xss: {
+ 'foo="bar"></div><script>alert(1)</script>': ''
+ },
+ template: `
+ <div v-bind="xss"></div>
+ `
+ }, res => {
+ expect(res).not.toContain(`<script>alert(1)</script>`)
+ done()
+ })
+ it('should prevent xss in attribute names (optimized)', done => {
+ <div>
+ <a v-bind="xss">foo</a>
+ </div>
it('should prevent script xss with v-bind object syntax + array value', done => {
renderVmWithOptions({
data: {