Skip to content

Commit 5f00336

Browse files
committed
🔒 security(util): XSS vulnerability
1 parent 405b046 commit 5f00336

File tree

6 files changed

+1290
-645
lines changed

6 files changed

+1290
-645
lines changed

package.json

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@
1010
"url": "https://github.com/kazupon/vue-i18n-extensions/issues"
1111
},
1212
"dependencies": {
13-
"vm2": "^3.5.0"
1413
},
1514
"devDependencies": {
16-
"@vue/server-test-utils": "^1.0.0-beta.21",
17-
"@vue/test-utils": "^1.0.0-beta.21",
15+
"@vue/server-test-utils": "^1.0.0-beta.29",
16+
"@vue/test-utils": "^1.0.0-beta.29",
1817
"babel-eslint": "^8.2.5",
1918
"conventional-changelog-cli": "^1.2.0",
2019
"conventional-github-releaser": "^1.1.3",
@@ -23,11 +22,11 @@
2322
"git-commit-message-convention": "git://github.com/kazupon/git-commit-message-convention.git",
2423
"jest": "^23.4.1",
2524
"jest-serializer-vue": "^2.0.2",
26-
"vue": "^2.4.2",
27-
"vue-i18n": "^8.0.0",
25+
"vue": "^2.6.8",
26+
"vue-i18n": "^8.9.0",
2827
"vue-jest": "^2.6.0",
29-
"vue-server-renderer": "^2.4.2",
30-
"vue-template-compiler": "^2.4.2"
28+
"vue-server-renderer": "^2.6.8",
29+
"vue-template-compiler": "^2.6.8"
3130
},
3231
"engines": {
3332
"node": ">= 8.0"

src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ function compilerModule (i18n) {
4444

4545
const { status, value } = evaluateValue(exp)
4646
if (status === 'ng') {
47-
warn('pre-localization with v-t support only static params')
47+
warn('not support params in pre-localization')
4848
return
4949
}
5050

src/util.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
const { VM } = require('vm2')
2-
3-
const vm = new VM()
1+
const stringRE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`/g
2+
const ecmaKeywordsRE = new RegExp('\\b' + (
3+
'delete,typeof,instanceof,void,do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' +
4+
'alert,eval,super,throw,while,yield,delete,export,import,return,switch,default,' +
5+
'extends,finally,continue,debugger,function,arguments'
6+
).split(',').join('\\b|\\b') + '\\b')
47

58
function warn (msg, err) {
69
if (typeof console !== 'undefined') {
@@ -40,11 +43,16 @@ function removeAttr (el, name) {
4043

4144
function evaluateValue (expression) {
4245
const ret = { status: 'ng', value: undefined }
46+
47+
if (expression.match(ecmaKeywordsRE)) { return ret }
48+
if (!expression.match(stringRE)) { return ret }
49+
4350
try {
44-
const val = vm.run(`(new Function('return ' + ${JSON.stringify(expression)}))()`)
51+
const val = (new Function(`return ${expression}`))()
4552
ret.status = 'ok'
4653
ret.value = val
4754
} catch (e) { }
55+
4856
return ret
4957
}
5058

test/module.test.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,20 @@ it('not transform with dynamic params', () => {
4444
expect(ast.i18n).toBeFalsy()
4545
expect(render).toEqual(`with(this){return _c('p',{directives:[{name:"t",rawName:"v-t",value:(hello),expression:"hello"}]})}`)
4646
expect(errors).toEqual([])
47-
expect(spy.mock.calls[0][0]).toEqual('[vue-i18n-extensions] pre-localization with v-t support only static params')
47+
expect(spy.mock.calls[0][0]).toEqual('[vue-i18n-extensions] not support params in pre-localization')
48+
spy.mockReset()
49+
spy.mockRestore()
50+
})
51+
52+
it('not support ecmascript keywords', () => {
53+
const spy = jest.spyOn(global.console, 'warn')
54+
spy.mockImplementation(x => x)
55+
const { ast, render, errors } = compile(`<p v-t="while(true){alert('!');"></p>`, { modules: [i18nModule] })
56+
expect(ast.i18n).toBeFalsy()
57+
expect(render).toEqual(`with(this){return _c('p',{directives:[{name:"t",rawName:"v-t",value:(while(true){alert('!');),expression:"while(true){alert('!');"}]})}`)
58+
expect(errors).toEqual([`avoid using JavaScript keyword as property name: \"while\"
59+
Raw expression: v-t=\"while(true){alert('!');\"`])
60+
expect(spy.mock.calls[0][0]).toEqual('[vue-i18n-extensions] not support params in pre-localization')
4861
spy.mockReset()
4962
spy.mockRestore()
5063
})
@@ -56,7 +69,7 @@ it('not support value warning', () => {
5669
expect(ast.i18n).toBeFalsy()
5770
expect(render).toEqual(`with(this){return _c('p',{directives:[{name:"t",rawName:"v-t",value:([1]),expression:"[1]"}]})}`)
5871
expect(errors).toEqual([])
59-
expect(spy.mock.calls[0][0]).toEqual('[vue-i18n-extensions] not support value type')
72+
expect(spy.mock.calls[0][0]).toEqual('[vue-i18n-extensions] not support params in pre-localization')
6073
spy.mockReset()
6174
spy.mockRestore()
6275
})
@@ -77,6 +90,6 @@ it('detect missing translation', done => {
7790
it('fallback custom directive', () => {
7891
const { ast, render, errors } = compile(`<p v-t="'foo.bar'"></p>`)
7992
expect(ast.i18n).toBeFalsy()
80-
expect(ast.directives[0]).toEqual({ name: 't', rawName: 'v-t', value: '\'foo.bar\'', arg: null, modifiers: undefined })
93+
expect(ast.directives[0]).toEqual({ "arg": null, "isDynamicArg": false, "modifiers": undefined, "name": "t", "rawName": "v-t", "value": "'foo.bar'" })
8194
expect(errors).toEqual([])
8295
})

test/utils.test.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const { evaluateValue } = require('../src/util')
2+
3+
it('string literal should be evaluate', () => {
4+
const { status, value } = evaluateValue(`'hello'`)
5+
expect(status).toEqual('ok')
6+
expect(value).toEqual('hello')
7+
})
8+
9+
it('json should be evaluate', () => {
10+
const { status, value } = evaluateValue(`{ path: 'named', locale: 'ja', args: { name: 'kazupon' } }`)
11+
expect(status).toEqual('ok')
12+
expect(value).toEqual({ path: 'named', locale: 'ja', args: { name: 'kazupon' } })
13+
})
14+
15+
it('identifier should not be evaluate', () => {
16+
const { status, value } = evaluateValue(`hello`)
17+
expect(status).toEqual('ng')
18+
expect(value).toEqual(undefined)
19+
})
20+
21+
it('ecmascript keyword should not be evaluate', () => {
22+
const { status, value } = evaluateValue(`'while(true){alert('!');}'`)
23+
expect(status).toEqual('ng')
24+
expect(value).toEqual(undefined)
25+
})

0 commit comments

Comments
 (0)