Security
Headlines
HeadlinesLatestCVEs

Headline

CVE-2023-49293: XSS vulnerability in `server.transformIndexHtml` via URL payload

Vite is a website frontend framework. When Vite’s HTML transformation is invoked manually via server.transformIndexHtml, the original request URL is passed in unmodified, and the html being transformed contains inline module scripts (<script type="module">...</script>), it is possible to inject arbitrary HTML into the transformed output by supplying a malicious URL query string to server.transformIndexHtml. Only apps using appType: 'custom' and using the default Vite HTML middleware are affected. The HTML entry must also contain an inline script. The attack requires a user to click on a malicious URL while running the dev server. Restricted files aren’t exposed to the attacker. This issue has been addressed in [email protected], [email protected], and [email protected]. There are no known workarounds for this vulnerability.

CVE
#xss#vulnerability#web#js

Summary

When Vite’s HTML transformation is invoked manually via server.transformIndexHtml, the original request URL is passed in unmodified, and the html being transformed contains inline module scripts (<script type="module">…</script>), it is possible to inject arbitrary HTML into the transformed output by supplying a malicious URL query string to server.transformIndexHtml.

Impact

Only apps using appType: ‘custom’ and using the default Vite HTML middleware are affected. The HTML entry must also contain an inline script. The attack requires a user to click on a malicious URL while running the dev server. Restricted files aren’t exposed to the attacker.

Patches

Fixed in [email protected], [email protected], [email protected]

Details

Suppose index.html contains an inline module script:

<script type="module"> // Inline script </script>

This script is transformed into a proxy script like

<script type="module" src="/index.html?html-proxy&index=0.js"></script>

due to Vite’s HTML plugin:

if (isModule) {

inlineModuleIndex++

if (url && !isExcludedUrl(url) && !isPublicFile) {

// <script type="module" src="…"/>

// add it as an import

js += `\nimport ${JSON.stringify(url)}`

shouldRemove = true

} else if (node.childNodes.length) {

const scriptNode =

node.childNodes.pop() as DefaultTreeAdapterMap[‘textNode’]

const contents = scriptNode.value

// <script type="module">…</script>

const filePath = id.replace(normalizePath(config.root), ‘’)

addToHTMLProxyCache(config, filePath, inlineModuleIndex, {

code: contents,

})

js += `\nimport "${id}?html-proxy&index=${inlineModuleIndex}.js"`

shouldRemove = true

}

everyScriptIsAsync &&= isAsync

someScriptsAreAsync ||= isAsync

someScriptsAreDefer ||= !isAsync

} else if (url && !isPublicFile) {

if (!isExcludedUrl(url)) {

config.logger.warn(

`<script src="${url}"> in “${publicPath}” can’t be bundled without type="module" attribute`,

)

}

} else if (node.childNodes.length) {

const scriptNode =

node.childNodes.pop() as DefaultTreeAdapterMap[‘textNode’]

scriptUrls.push(

…extractImportExpressionFromClassicScript(scriptNode),

)

}

}

When appType: ‘spa’ | 'mpa’, Vite serves HTML itself, and htmlFallbackMiddleware rewrites req.url to the canonical path of index.html,

if (fs.existsSync(filePath)) {

const newUrl = url + ‘index.html’

debug?.(`Rewriting ${req.method} ${req.url} to ${newUrl}`)

req.url = newUrl

so the url passed to server.transformIndexHtml is /index.html.

However, if appType: 'custom’, HTML is served manually, and if server.transformIndexHtml is called with the unmodified request URL (as the SSR docs suggest), then the path of the transformed html-proxy script varies with the request URL. For example, a request with path / produces

<script type="module" src="/@id/__x00__/index.html?html-proxy&index=0.js"></script>

It is possible to abuse this behavior by crafting a request URL to contain a malicious payload like

"></script><script>alert('boom')</script>

so a request to http://localhost:5173/?%22%3E%3C/script%3E%3Cscript%3Ealert(%27boom%27)%3C/script%3E produces HTML output like

<script type="module" src="/@id/__x00__/?"></script><script>alert(“boom”)</script>?html-proxy&index=0.js"></script>

which demonstrates XSS.

PoC

  • Example 1. Serving HTML from vite dev middleware with appType: ‘custom’
    • Go to https://stackblitz.com/edit/vitejs-vite-9xhma4?file=main.js&terminal=dev-html
    • “Open in New Tab”
    • Edit URL to set query string to ?%22%3E%3C/script%3E%3Cscript%3Ealert(%27boom%27)%3C/script%3E and navigate
    • Witness XSS:
  • Example 2. Serving HTML from SSR-style Express server (Vite dev server runs in middleware mode):
    • Go to https://stackblitz.com/edit/vitejs-vite-9xhma4?file=main.js&terminal=server
    • (Same steps as above)
  • Example 3. Plain vite dev (this shows that vanilla vite dev is not vulnerable, provided htmlFallbackMiddleware is used)
    • Go to https://stackblitz.com/edit/vitejs-vite-9xhma4?file=main.js&terminal=dev
    • (Same steps as above)
    • You should not see the alert box in this case

Detailed Impact

This will probably predominantly affect development-mode SSR, where vite.transformHtml is called using the original req.url, per the docs:

const url = req.originalUrl

try {

// 1. Read index.html

let template = fs.readFileSync(

path.resolve(__dirname, ‘index.html’),

'utf-8’,

)

// 2. Apply Vite HTML transforms. This injects the Vite HMR client,

// and also applies HTML transforms from Vite plugins, e.g. global

// preambles from @vitejs/plugin-react

template = await vite.transformIndexHtml(url, template)

However, since this vulnerability affects server.transformIndexHtml, the scope of impact may be higher to also include other ad-hoc calls to server.transformIndexHtml from outside of Vite’s own codebase.

My best guess at bisecting which versions are vulnerable involves the following test script

import fs from 'node:fs/promises’; import * as vite from 'vite’;

const html = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> </head> <body> <script type="module"> // Inline script </script> </body> </html> `; const server = await vite.createServer({ appType: ‘custom’ }); const transformed = await server.transformIndexHtml('/?%22%3E%3C/script%3E%3Cscript%3Ealert(%27boom%27)%3C/script%3E’, html); console.log(transformed); await server.close();

and using it I was able to narrow down to #13581. If this is correct, then vulnerable Vite versions are 4.4.0-beta.2 and higher (which includes 4.4.0).

Related news

GHSA-92r3-m2mg-pj97: Vite XSS vulnerability in `server.transformIndexHtml` via URL payload

### Summary When Vite's HTML transformation is invoked manually via `server.transformIndexHtml`, the original request URL is passed in unmodified, and the `html` being transformed contains inline module scripts (`<script type="module">...</script>`), it is possible to inject arbitrary HTML into the transformed output by supplying a malicious URL query string to `server.transformIndexHtml`. ### Impact Only apps using `appType: 'custom'` and using the default Vite HTML middleware are affected. The HTML entry must also contain an inline script. The attack requires a user to click on a malicious URL while running the dev server. Restricted files aren't exposed to the attacker. ### Patches Fixed in [email protected], [email protected], [email protected] ### Details Suppose `index.html` contains an inline module script: ```html <script type="module"> // Inline script </script> ``` This script is transformed into a proxy script like ```html <script type="module" src="/index.html?html-proxy&index=0.js"></...

CVE: Latest News

CVE-2023-50976: Transactions API Authorization by oleiman · Pull Request #14969 · redpanda-data/redpanda
CVE-2023-6905
CVE-2023-6903
CVE-2023-6904
CVE-2023-3907