Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions e2e/hotfix.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { getText } from './helper'

describe('CVE-2024-52809', () => {
beforeAll(async () => {
await page.goto(`http://localhost:8080/e2e/hotfix/CVE-2024-52809.html`)
})

test('fix', async () => {
expect(await getText(page, 'p')).toMatch('hello world!')
})
})
69 changes: 69 additions & 0 deletions e2e/hotfix/CVE-2024-52809.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>vue-i18n XSS</title>
<script src="../../node_modules/vue/dist/vue.global.js"></script>
<script src="../../packages/vue-i18n/dist/vue-i18n.global.js"></script>
<!-- Scripts that perform prototype contamination, such as being distributed from malicious hosting sites or injected through supply chain attacks, etc. -->
<script>
/**
* Prototype pollution vulnerability with `Object.prototype`.
* The 'static' property is part of the optimized AST generated by the vue-i18n message compiler.
* About details of special properties, see https://github.com/intlify/vue-i18n/blob/master/packages/message-compiler/src/nodes.ts
*
* In general, the locale messages of vue-i18n are optimized during production builds using `@intlify/unplugin-vue-i18n`,
* so there is always a property that is attached during optimization like this time.
* But if you are using a locale message AST in development or your own, there is a possibility of XSS if a third party injects prototype pollution code.
*/
Object.defineProperty(Object.prototype, 'static', {
configurable: true,
get() {
alert('prototype polluted!')
return 'prototype pollution'
}
})
</script>
</head>
<body>
<div id="app">
<p>{{ t('hello') }}</p>
</div>
<script>
const { createApp } = Vue
const { createI18n, useI18n } = VueI18n

// AST style locale message, which build by `@intlify/unplugin-vue-i18n`
const en = {
hello: {
type: 0,
body: {
items: [
{
type: 3,
value: 'hello world!'
}
]
}
}
}

const i18n = createI18n({
legacy: false,
locale: 'en',
messages: {
en
}
})

const app = createApp({
setup() {
const { t } = useI18n()
return { t }
}
})
app.use(i18n)
app.mount('#app')
</script>
</body>
</html>
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
"@types/node": "^22.5.3",
"@types/rc": "^1.2.4",
"@vitest/coverage-v8": "^2.1.5",
"@types/serve-handler": "^6.1.4",
"api-docs-gen": "^0.4.0",
"benchmark": "^2.1.4",
"brotli": "^1.3.2",
Expand Down Expand Up @@ -121,7 +122,7 @@
"rollup-plugin-node-globals": "^1.4.0",
"rollup-plugin-typescript2": "^0.36.0",
"secretlint": "^3.2.0",
"serve-static": "^1.15.0",
"serve-handler": "^6.1.6",
"textlint": "^12.6.1",
"textlint-filter-rule-comments": "^1.2.2",
"textlint-rule-abbr-within-parentheses": "^1.0.2",
Expand Down
28 changes: 20 additions & 8 deletions packages/core-base/src/compilation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,22 @@ import {
defaultOnError,
detectHtmlTag
} from '@intlify/message-compiler'
import { format, isBoolean, isObject, isString, warn } from '@intlify/shared'
import { format as formatMessage } from './format'
import {
create,
format,
hasOwn,
isBoolean,
isObject,
isString,
warn
} from '@intlify/shared'
import { format as formatMessage, resolveType } from './format'

import type {
CompileError,
CompileOptions,
CompilerResult,
Node,
ResourceNode
} from '@intlify/message-compiler'
import type { MessageCompilerContext } from './context'
Expand All @@ -24,16 +33,19 @@ function checkHtmlMessage(source: string, warnHtmlMessage?: boolean): void {
}

const defaultOnCacheKey = (message: string): string => message
let compileCache: unknown = Object.create(null)
let compileCache: unknown = create()

export function clearCompileCache(): void {
compileCache = Object.create(null)
compileCache = create()
}

export const isMessageAST = (val: unknown): val is ResourceNode =>
isObject(val) &&
(val.t === 0 || val.type === 0) &&
('b' in val || 'body' in val)
export function isMessageAST(val: unknown): val is ResourceNode {
return (
isObject(val) &&
resolveType(val as Node) === 0 &&
(hasOwn(val, 'b') || hasOwn(val, 'body'))
)
}

function baseCompile(
message: string,
Expand Down
19 changes: 11 additions & 8 deletions packages/core-base/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import {
assign,
create,
isArray,
isBoolean,
isFunction,
Expand Down Expand Up @@ -507,23 +508,23 @@ export function createCoreContext<Message = string>(options: any = {}): any {
: _locale
const messages = isPlainObject(options.messages)
? options.messages
: { [_locale]: {} }
: createResources(_locale)
const datetimeFormats = !__LITE__
? isPlainObject(options.datetimeFormats)
? options.datetimeFormats
: { [_locale]: {} }
: { [_locale]: {} }
: createResources(_locale)
: createResources(_locale)
const numberFormats = !__LITE__
? isPlainObject(options.numberFormats)
? options.numberFormats
: { [_locale]: {} }
: { [_locale]: {} }
: createResources(_locale)
: createResources(_locale)
const modifiers = assign(
{},
options.modifiers || {},
create(),
options.modifiers,
getDefaultLinkedModifiers<Message>()
)
const pluralRules = options.pluralRules || {}
const pluralRules = options.pluralRules || create()
const missing = isFunction(options.missing) ? options.missing : null
const missingWarn =
isBoolean(options.missingWarn) || isRegExp(options.missingWarn)
Expand Down Expand Up @@ -628,6 +629,8 @@ export function createCoreContext<Message = string>(options: any = {}): any {
return context
}

const createResources = (locale: Locale) => ({ [locale]: create() })

/** @internal */
export function isTranslateFallbackWarn(
fallback: boolean | RegExp,
Expand Down
5 changes: 3 additions & 2 deletions packages/core-base/src/datetime.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
assign,
create,
isBoolean,
isDate,
isEmptyObject,
Expand Down Expand Up @@ -322,8 +323,8 @@ export function parseDateTimeArgs(
...args: unknown[]
): [string, number | Date, DateTimeOptions, Intl.DateTimeFormatOptions] {
const [arg1, arg2, arg3, arg4] = args
const options = {} as DateTimeOptions
let overrides = {} as Intl.DateTimeFormatOptions
const options = create() as DateTimeOptions
let overrides = create() as Intl.DateTimeFormatOptions

let value: number | Date
if (isString(arg1)) {
Expand Down
4 changes: 2 additions & 2 deletions packages/core-base/src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
createCompileError,
COMPILE_ERROR_CODES_EXTEND_POINT
COMPILE_ERROR_CODES_EXTEND_POINT,
createCompileError
} from '@intlify/message-compiler'

import type { BaseError } from '@intlify/shared'
Expand Down
Loading