Skip to content
KitploitKITPLOIT
OutilsExploitsBlog
Log in
Soumettre
OutilsExploitsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

FluxContactConfidentialité© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2026-94545- — PoC Python pour CVE-2026-94545 : exploite une entrée SVG non échappée dans next/og de Next.js pour déclencher une corruption de libxml2 et une chaîne ROP permettant une RCE non authentifiée. | Kitploit
Outils/GitHubGitHub/mrdark-ops/cve-2026-94545-
Génération de PayloadsAnalyse des VulnérabilitésExploitationShellcodeExploitation d'Applications WebSécurité WebExploitation de Binaires
GitHubmrdark-ops/cve-2026-94545-

CVE-2026-94545-

PoC Python pour CVE-2026-94545 : exploite une entrée SVG non échappée dans next/og de Next.js pour déclencher une corruption de libxml2 et une chaîne ROP permettant une RCE non authentifiée.

Voir le dépôt
11il y a 1 moisPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

CVE-2026-94545 : Exécution de code à distance non authentifiée dans next/og de Next.js

À des fins de recherche en sécurité uniquement, dans des environnements de vulnérabilité autorisés.

ÉlémentValeur
CVECVE-2026-94545 / GHSA-vcvr-r3jv-pc5j
AmontSatori GHSA-wx4j-mvgx-mqwp
Cause racineCWE-116 (Encodage ou échappement incorrect de la sortie) → corruption de mémoire du parseur XML natif → RCE
AffectéNext.js 16.2.0 – 16.3.5 (runtime Node avec sharp installé) ; Satori embarqué ≥ 0.0.27 et < 0.33.5
CorrigéNext.js 16.3.6 / Satori 0.33.5
ImpactExécuter des commandes arbitraires dans le processus du service Next.js sans authentification
Pile testéeNext.js 16.3.5 + sharp 0.35.4 (libvips 8.18.6, librsvg 2.62.91, libxml2 2.15.3) + binaire officiel x64 de Node.js v24.20.0 (non-PIE)

Avec le runtime Edge (export const runtime = 'edge') ou lorsque sharp n'est pas installé, le rendu passe par le bac à sable resvg-wasm ; cette vulnérabilité n'atteint donc pas la chaîne de parsing native et ne devient par conséquent pas une RCE.

Chaîne de vulnérabilité

L'entrée utilisateur est concaténée dans le SVG en tant que texte brut, puis ce SVG est inséré en entier dans une URL data: et transmis à un parseur XML en langage C pour une seconde passe de parsing. Seul un encodage d'URL est effectué entre-temps ; l'échappement XML n'est jamais réalisé.

Couche 1 : App Router — l'entrée non fiable entre dans la fonction de rendu

Schéma typique : POST /api/og place l'intégralité du corps de la requête, ou GET /api/og?value= place le paramètre de requête, directement dans le <title> SVG de ImageResponse, sans filtrage ni échappement.

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 reçoit un arbre d'éléments JSX et renvoie un PNG. Toute route qui écrit des données de requête dans des nœuds enfants / attributs / styles SVG constitue un sink.

Couche 2 : transfert d'entrée next/og

node_modules/next/og.js redirige vers next/dist/server/og/image-response.js. Le runtime Node charge dynamiquement next/dist/compiled/@vercel/og/index.node.js (chemin natif) ; le runtime Edge charge le chemin wasm et n'est pas affecté par cette vulnérabilité.

Couche 3 : @vercel/og — Satori génère le SVG, puis le transmet pour rastérisation

Le flux de @vercel/og intégré à Next.js (les commentaires de l'artefact compilé confirment qu'il contient [email protected]) est le suivant :

  1. Utiliser Satori pour sérialiser l'arbre JSX en une chaîne SVG.
  2. Si sharp est détecté, transmettre le SVG à la bibliothèque native pour rastérisation ; sinon, se rabattre sur 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: ... } })
}

À ce stade, l'entrée utilisateur est déjà devenue une chaîne SVG et est transmise telle quelle à sharp (libvips natif).

Couche 4 : Satori 0.25.0 — Le texte est uniquement encodé en URL, jamais échappé en XML

Pour un <svg> explicitement écrit en JSX, Satori le sérialise entièrement dans une URL data:image/svg+xml puis l'intègre dans la sortie sous forme d'<image>.

vercel/[email protected] src/layout.ts : appelle SVGNodeToImage lors de l'entrée dans un nœud <svg>.

src/handler/preprocess.ts présente deux défauts majeurs :

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
}

&, < et > ne sont pas écrits sous la forme &amp; / &lt; / &gt;. L'encodage en pourcentage n'est qu'une exigence de syntaxe d'URL ; lorsque le parseur en aval charge l'URL data:, %3C/title%3E est restauré en un véritable </title>, et le texte de l'attaquant passe de « données » à « structure de document ».

Lorsque l'entrée est </title><xi:include …/><title>, le SVG intermédiaire transmis par Satori à sharp prend la forme suivante (décodé pour illustration) :

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

Couche 5 : sharp → libvips → librsvg → libxml2 — Analyse native et corruption de mémoire

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 charge utile utilise deux couches d'injection pour contrôler la structure d'entrée de libxml2 :

  1. Le </title><xi:include parse="xml" href="data:…"> externe fait basculer le parseur vers le chargement d'un document imbriqué entièrement contrôlé par l'attaquant.
  2. À l'intérieur du document imbriqué, des entités DTD imbriquées + XInclude sont ensuite utilisées pour construire un graphe d'expansion et de référence d'entités excessivement profond / large.

L'état interne de libxml2 est poussé au-delà de ses attentes, entraînant une écriture hors limites. L'effet de corruption est appliqué de manière fiable via des séquences de coordonnées SVG <path> soigneusement agencées (des doubles IEEE-754 transportant des mots de 8 octets en little-endian), détournant ainsi le flux de contrôle.

Couche 6 : ROP à adresses fixes sur Node non-PIE → execve

Le binaire Node.js linux-x64 officiellement publié est un exécutable non-PIE avec une base de chargement fixe ; les adresses PLT / GOT et des gadgets sont constantes, donc aucune fuite d'information n'est nécessaire.

Ce PoC est construit pour Node.js v24.20.0 officiel x64 ; les gadgets sont les suivants :

SymboleAdresseObjectif
POP_RDI0xA1EAEDpop rdi ; ret
POP_RAX0x7F38D4pop rax ; ret
POP_RSI0x95823Dpop rsi ; ret
POP_RDX0x776758pop rdx ; ret
STOSQ_RET0xD16725stosq ; ret (écrit dans la zone de travail qword par qword)
EXECVE_PLT0x745FA0stub PLT de execve
SCRATCH0x6B5F500Adresse inscriptible fixe
Télécharger l’outil