
PoC privé Fortbridge pour la chaîne RCE CVE-2026-32740 Next.js/sharp leak-to-memcpy-GOT
Preuve de concept fonctionnelle qui transforme CVE-2026-32740 dans la
pile d'images Next.js/sharp codée en dur en une écriture à adresse choisie et un
callback /usr/bin/id validé.
Ceci est un dépôt de recherche privé Fortbridge. Utilisez-le uniquement contre le lab inclus ou un autre système que vous êtes explicitement autorisé à tester.
L'exploit utilise uniquement les routes HTTP d'upload et d'optimisation d'images de la cible :
g_module_open_full de GLib, une
routine GModule qui encapsule dlopen pour charger une bibliothèque partagée
native depuis un chemin de fichier. L'exploit calcule son adresse d'exécution
comme la base libvips récupérée plus l'offset de profil codé en dur
0x3e995e. Le chargement de la bibliothèque exécute son constructeur./usr/bin/id. Il envoie cet ELF via la
route d'upload publique sous le nom d'image x.jpg et le type MIME
image/jpeg.memcpy@GOT - 16.uploads/x.jpg immédiatement
avant l'emplacement GOT et remplace memcpy@GOT par le chargeur GModule
dérivé. La ligne suivante appelle ce chargeur avec le chemin de la
bibliothèque déjà dans RDI./usr/bin/id via TCP. Un jeton par tentative empêche qu'un callback
obsolète soit compté comme un succès ; la ligne uid=... retournée constitue
la preuve.Les exécutions de validation originales relatives à libvips sont enregistrées
dans
evidence/libvips-gmodule-rce-10x.json.
Les dix processus frais ont tous retourné une sortie /usr/bin/id valide avec
dix bases libvips randomisées distinctes et dix adresses de chargeur dérivées
indépendamment.
Les données exactes de l'exécution Ubuntu sont dans
evidence/libvips-gmodule-pie-rce-10x.json.
Les données exactes de l'exécution Debian 13 APT sont dans
evidence/debian13-apt-libvips-gmodule-rce-10x.json.
Nous avons testé l'exploit complet contre 10 processus Node Ubuntu fraîchement
démarrés et 10 processus Node Debian fraîchement démarrés. Les 20 exécutions ont
atteint l'exécution de commande et retourné la sortie /usr/bin/id de la cible.
L'exploit a également calculé une adresse de chargeur GModule différente pour
chaque base libvips randomisée.
Avant de créer la charge utile finale, l'exploit peut nécessiter plusieurs tentatives de fuite ASLR. Chaque tentative envoie l'AVIF de fuite, le fait passer par la route d'optimisation, et vérifie dans le PNG retourné la présence de quatre pointeurs qui identifient un profil et une base libvips. Ubuntu a nécessité entre 3 et 23 tentatives. Debian en a nécessité entre 3 et 15. Des preuves incomplètes ou ambiguës déclenchent une nouvelle tentative au lieu d'un profil deviné.
Le projet comporte également 96 tests de régression automatisés. Ce sont des vérifications au niveau du code, pas 96 exécutions supplémentaires de l'exploit. Ils couvrent la validation de profil, la classification des pointeurs retournés, la calibration du tas, le calcul d'adresse, la construction de la charge utile, et l'échec sécurisé lorsque les preuves ne correspondent pas à une cible supportée.
Les deux cibles x86-64 utilisent Next.js 15.5.23, sharp 0.34.4, libvips 8.17.2 embarqué, libheif 1.20.2 embarqué, ASLR et NX. Leurs runtimes natifs diffèrent :
| Profil | Node | glibc | libstdc++ | Relation du sélecteur |
|---|---|---|---|---|
| Ubuntu | 25.8.1, PIE ET_DYN | 2.43-2ubuntu2.4 | 6.0.35 | 0x6000 - 0x690 = 0x5970 |
| Debian 13 | Debian APT 20.19.2, PIE ET_DYN | 2.41-12+deb13u4 | 6.0.33 | 0x6000 - 0x3a0 = 0x5c60 |
Les build IDs complets et les valeurs SHA-256 sont dans
profiles/native_stack_profiles_pie.json.
Les mesures du paquet Debian, de l'artefact, du route-smoke et de la disposition
sur cinq durées de vie sont capturées dans
evidence/debian13-profile-derivation.json.
La cible Debian utilise le paquet de distribution standard
nodejs=20.19.2+dfsg-1+deb13u3 ; Node n'est pas compilé depuis les sources.
La chaîne n'a pas besoin de la base Node randomisée. Sa cible de contrôle est la
routine interne g_module_open_full à l'intérieur de libvips, dont la base
randomisée est récupérée à partir des pixels retournés. Chaque profil versionné
code en dur les constantes spécifiques au build requises par l'exploit :
memcpy@GOT et l'offset g_module_open_full plus ses octets
d'instruction de validation ;La base libvips randomisée, l'adresse de chargeur à l'exécution résultante et le sélecteur final sur deux octets ne sont pas codés en dur. Ils sont dérivés pour chaque processus cible à partir des pixels retournés et du profil sélectionné.
L'exploit échoue de manière sécurisée lorsqu'un profil est malformé ou que les pixels retournés ne sélectionnent pas exactement une paire profil/base supportée. Le profil stock nécessite également son enregistrement de tas retourné complet. Un profil est une revendication de compatibilité exacte, donc l'opérateur doit vérifier les artefacts de la cible hors ligne avant de l'utiliser.
L'ABI du chargeur est importante. L'appel écrasé fournit le chemin de la bibliothèque dans RDI, un pointeur de ligne d'image dans RSI, et la longueur de copie de 58 octets dans RDX. Cette routine GModule profilée exacte utilise uniquement les bits de drapeau supportés depuis ESI et ne déréférence pas RDX sur le chemin de chargement réussi. Un harnais hors ligne a validé ce point d'entrée et cette signature d'instruction avant son utilisation dans les exécutions mesurées.
ffmpeg avec l'encodeur libaom-av1cc sur la machine de l'attaquantInstallez la seule dépendance Python :```bash python3 -m venv .venv . .venv/bin/activate python3 -m pip install -r requirements.txt
Confirmer la prise en charge de l'encodage AV1 :```bash
ffmpeg -hide_banner -encoders | grep libaom-av1