ghsa-92r3-m2mg-pj97
Vulnerability from github
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 vite@5.0.5, vite@4.5.1, vite@4.4.12
Details
Suppose index.html
contains an inline module script:
```html
```
This script is transformed into a proxy script like
```html
```
due to Vite's HTML plugin:
https://github.com/vitejs/vite/blob/7fd7c6cebfcad34ae7021ebee28f97b1f28ef3f3/packages/vite/src/node/plugins/html.ts#L429-L465
When appType: 'spa' | 'mpa'
, Vite serves HTML itself, and htmlFallbackMiddleware
rewrites req.url
to the canonical path of index.html
,
https://github.com/vitejs/vite/blob/73ef074b80fa7252e0c46a37a2c94ba8cba46504/packages/vite/src/node/server/middlewares/htmlFallback.ts#L44-L47
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
```html
```
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
```html
?html-proxy&index=0.js"> ```
which demonstrates XSS.
PoC
- Example 1. Serving HTML from
vite dev
middleware withappType: '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 vanillavite dev
is not vulnerable, providedhtmlFallbackMiddleware
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:
https://github.com/vitejs/vite/blob/7fd7c6cebfcad34ae7021ebee28f97b1f28ef3f3/docs/guide/ssr.md?plain=1#L114-L126
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
```js import fs from 'node:fs/promises'; import * as vite from 'vite';
const 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).
{ "affected": [ { "package": { "ecosystem": "npm", "name": "vite" }, "ranges": [ { "events": [ { "introduced": "4.4.0" }, { "fixed": "4.4.12" } ], "type": "ECOSYSTEM" } ] }, { "package": { "ecosystem": "npm", "name": "vite" }, "ranges": [ { "events": [ { "introduced": "4.5.0" }, { "fixed": "4.5.1" } ], "type": "ECOSYSTEM" } ], "versions": [ "4.5.0" ] }, { "package": { "ecosystem": "npm", "name": "vite" }, "ranges": [ { "events": [ { "introduced": "5.0.0" }, { "fixed": "5.0.5" } ], "type": "ECOSYSTEM" } ] } ], "aliases": [ "CVE-2023-49293" ], "database_specific": { "cwe_ids": [ "CWE-79" ], "github_reviewed": true, "github_reviewed_at": "2023-12-05T23:31:34Z", "nvd_published_at": "2023-12-04T23:15:27Z", "severity": "MODERATE" }, "details": "### Summary\nWhen Vite\u0027s 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 (`\u003cscript type=\"module\"\u003e...\u003c/script\u003e`), it is possible to inject arbitrary HTML into the transformed output by supplying a malicious URL query string to `server.transformIndexHtml`.\n\n### Impact\nOnly apps using `appType: \u0027custom\u0027` 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\u0027t exposed to the attacker.\n\n### Patches\nFixed in vite@5.0.5, vite@4.5.1, vite@4.4.12\n\n### Details\nSuppose `index.html` contains an inline module script:\n\n```html\n\u003cscript type=\"module\"\u003e\n // Inline script\n\u003c/script\u003e\n```\n\nThis script is transformed into a proxy script like\n\n```html\n\u003cscript type=\"module\" src=\"/index.html?html-proxy\u0026index=0.js\"\u003e\u003c/script\u003e\n```\n\ndue to Vite\u0027s HTML plugin:\n\nhttps://github.com/vitejs/vite/blob/7fd7c6cebfcad34ae7021ebee28f97b1f28ef3f3/packages/vite/src/node/plugins/html.ts#L429-L465\n\nWhen `appType: \u0027spa\u0027 | \u0027mpa\u0027`, Vite serves HTML itself, and `htmlFallbackMiddleware` rewrites `req.url` to the canonical path of `index.html`,\n\nhttps://github.com/vitejs/vite/blob/73ef074b80fa7252e0c46a37a2c94ba8cba46504/packages/vite/src/node/server/middlewares/htmlFallback.ts#L44-L47\n\nso the `url` passed to `server.transformIndexHtml` is `/index.html`.\n\nHowever, if `appType: \u0027custom\u0027`, 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\n\n```html\n\u003cscript type=\"module\" src=\"/@id/__x00__/index.html?html-proxy\u0026index=0.js\"\u003e\u003c/script\u003e\n```\n\nIt is possible to abuse this behavior by crafting a request URL to contain a malicious payload like\n\n```\n\"\u003e\u003c/script\u003e\u003cscript\u003ealert(\u0027boom\u0027)\u003c/script\u003e\n```\n\nso a request to http://localhost:5173/?%22%3E%3C/script%3E%3Cscript%3Ealert(%27boom%27)%3C/script%3E produces HTML output like\n\n```html\n\u003cscript type=\"module\" src=\"/@id/__x00__/?\"\u003e\u003c/script\u003e\u003cscript\u003ealert(\"boom\")\u003c/script\u003e?html-proxy\u0026index=0.js\"\u003e\u003c/script\u003e\n```\n\nwhich demonstrates XSS.\n\n### PoC\n\n- Example 1. Serving HTML from `vite dev` middleware with `appType: \u0027custom\u0027`\n - Go to https://stackblitz.com/edit/vitejs-vite-9xhma4?file=main.js\u0026terminal=dev-html\n - \"Open in New Tab\"\n - Edit URL to set query string to `?%22%3E%3C/script%3E%3Cscript%3Ealert(%27boom%27)%3C/script%3E` and navigate\n - Witness XSS:\n - ![image](https://user-images.githubusercontent.com/2456381/287434281-13757894-7a63-4a73-b1e9-d2b024c19d14.png)\n- Example 2. Serving HTML from SSR-style Express server (Vite dev server runs in middleware mode):\n - Go to https://stackblitz.com/edit/vitejs-vite-9xhma4?file=main.js\u0026terminal=server\n - (Same steps as above)\n- Example 3. Plain `vite dev` (this shows that vanilla `vite dev` is _not_ vulnerable, provided `htmlFallbackMiddleware` is used)\n - Go to https://stackblitz.com/edit/vitejs-vite-9xhma4?file=main.js\u0026terminal=dev\n - (Same steps as above)\n - You should _not_ see the alert box in this case\n\n### Detailed Impact\n\nThis will probably predominantly affect [development-mode SSR](https://vitejs.dev/guide/ssr#setting-up-the-dev-server), where `vite.transformHtml` is called using the original `req.url`, per the docs:\n\nhttps://github.com/vitejs/vite/blob/7fd7c6cebfcad34ae7021ebee28f97b1f28ef3f3/docs/guide/ssr.md?plain=1#L114-L126\n\nHowever, 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\u0027s own codebase.\n\nMy best guess at bisecting which versions are vulnerable involves the following test script\n\n```js\nimport fs from \u0027node:fs/promises\u0027;\nimport * as vite from \u0027vite\u0027;\n\nconst html = `\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n \u003chead\u003e\n \u003cmeta charset=\"UTF-8\" /\u003e\n \u003c/head\u003e\n \u003cbody\u003e\n \u003cscript type=\"module\"\u003e\n // Inline script\n \u003c/script\u003e\n \u003c/body\u003e\n\u003c/html\u003e\n`;\nconst server = await vite.createServer({ appType: \u0027custom\u0027 });\nconst transformed = await server.transformIndexHtml(\u0027/?%22%3E%3C/script%3E%3Cscript%3Ealert(%27boom%27)%3C/script%3E\u0027, html);\nconsole.log(transformed);\nawait server.close();\n```\n\nand 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).", "id": "GHSA-92r3-m2mg-pj97", "modified": "2023-12-05T23:31:34Z", "published": "2023-12-05T23:31:34Z", "references": [ { "type": "WEB", "url": "https://github.com/vitejs/vite/security/advisories/GHSA-92r3-m2mg-pj97" }, { "type": "ADVISORY", "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-49293" }, { "type": "PACKAGE", "url": "https://github.com/vitejs/vite" } ], "schema_version": "1.4.0", "severity": [ { "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N", "type": "CVSS_V3" } ], "summary": "Vite XSS vulnerability in `server.transformIndexHtml` via URL payload" }
Sightings
Author | Source | Type | Date |
---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or seen somewhere by the user.
- Confirmed: The vulnerability is confirmed from an analyst perspective.
- Exploited: This vulnerability was exploited and seen by the user reporting the sighting.
- Patched: This vulnerability was successfully patched by the user reporting the sighting.
- Not exploited: This vulnerability was not exploited or seen by the user reporting the sighting.
- Not confirmed: The user expresses doubt about the veracity of the vulnerability.
- Not patched: This vulnerability was not successfully patched by the user reporting the sighting.