
PoC privato Fortbridge per la catena RCE CVE-2026-32740 Next.js/sharp leak-to-memcpy-GOT
Proof of concept funzionante che trasforma CVE-2026-32740 nello
stack di immagini Next.js/sharp hardcoded in una scrittura ad indirizzo scelto e in una
callback validata a /usr/bin/id.
Questo è un repository di ricerca privato di Fortbridge. Usalo solo contro il lab incluso o un altro sistema che sei esplicitamente autorizzato a testare.
L'exploit utilizza solo le route HTTP di upload e ottimizzazione immagini del target:
g_module_open_full di GLib, una routine
GModule che avvolge dlopen per caricare una libreria condivisa nativa da un
percorso file. L'exploit calcola il suo indirizzo a runtime come la base libvips
recuperata più l'offset hardcoded del profilo 0x3e995e. Il caricamento della libreria esegue
il suo costruttore./usr/bin/id. Invia quell'ELF attraverso la route pubblica
di upload con il nome immagine x.jpg e media type image/jpeg.memcpy@GOT - 16.uploads/x.jpg immediatamente prima
dello slot GOT e sostituisce memcpy@GOT con il loader GModule derivato. La riga
successiva chiama quel loader con il percorso della libreria già in RDI./usr/bin/id via TCP. Un token per tentativo impedisce che una callback
obsoleta venga conteggiata come successo; la riga uid=... restituita è la prova.Le esecuzioni di validazione originali relative a libvips sono registrate in
evidence/libvips-gmodule-rce-10x.json.
Tutti e dieci i processi freschi hanno restituito un output valido di /usr/bin/id con dieci
basi libvips randomizzate distinte e dieci indirizzi del loader derivati indipendentemente.
I dati esatti dell'esecuzione su Ubuntu sono in
evidence/libvips-gmodule-pie-rce-10x.json.
I dati esatti dell'esecuzione su Debian 13 APT sono in
evidence/debian13-apt-libvips-gmodule-rce-10x.json.
Abbiamo testato l'exploit completo contro 10 processi Node Ubuntu avviati di recente
e 10 processi Node Debian avviati di recente. Tutte e 20 le esecuzioni hanno raggiunto l'esecuzione
di comandi e restituito l'output di /usr/bin/id del target. L'exploit ha anche
calcolato un indirizzo del loader GModule diverso per ogni base libvips randomizzata.
Prima di creare il payload finale, l'exploit potrebbe richiedere diversi tentativi di leak ASLR. Ogni tentativo carica l'AVIF di leak, lo invia attraverso la route di ottimizzazione e controlla il PNG restituito per quattro puntatori che identificano un profilo e una base libvips. Ubuntu ha richiesto tra 3 e 23 tentativi. Debian ha richiesto tra 3 e 15. Evidenze incomplete o ambigue causano un altro tentativo invece di un profilo ipotizzato.
Il progetto ha anche 96 test di regressione automatizzati. Questi sono controlli a livello di codice, non 96 ulteriori esecuzioni dell'exploit. Coprono la validazione del profilo, la classificazione dei puntatori restituiti, la calibrazione dell'heap, il calcolo degli indirizzi, la costruzione del payload e il fallimento sicuro quando le evidenze non corrispondono a un target supportato.
Entrambi i target x86-64 usano Next.js 15.5.23, sharp 0.34.4, libvips 8.17.2 incluso, libheif 1.20.2 incluso, ASLR e NX. I loro runtime nativi differiscono:
| Profilo | Node | glibc | libstdc++ | Relazione del selettore |
|---|---|---|---|---|
| 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 |
I build ID completi e i valori SHA-256 sono in
profiles/native_stack_profiles_pie.json.
Le misurazioni del pacchetto Debian, degli artefatti, dello smoke test delle route e del layout
a cinque lifetime sono catturate in
evidence/debian13-profile-derivation.json.
Il target Debian usa il pacchetto standard della distribuzione
nodejs=20.19.2+dfsg-1+deb13u3; Node non è compilato dai sorgenti.
La catena non necessita della base Node randomizzata. Il suo target di controllo è la
routine interna g_module_open_full dentro libvips, la cui base randomizzata è
recuperata dai pixel restituiti. Ogni profilo versionato hardcoda le
costanti specifiche della build richieste dall'exploit:
memcpy@GOT e l'offset di g_module_open_full più i suoi byte
di istruzione di validazione;La base libvips randomizzata, l'indirizzo del loader a runtime risultante e il selettore finale a due byte non sono hardcoded. Sono derivati per ogni processo target dai pixel restituiti e dal profilo selezionato.
L'exploit fallisce in modo sicuro quando un profilo è malformato o i pixel restituiti non selezionano esattamente una coppia profilo/base supportata. Il profilo stock richiede anche il suo record heap restituito completo. Un profilo è un'affermazione di compatibilità esatta, quindi l'operatore dovrebbe verificare gli artefatti del target offline prima di usarlo.
L'ABI del loader è importante. La chiamata sovrascritta fornisce il percorso della libreria in RDI, un puntatore a riga immagine in RSI e la lunghezza di copia di 58 byte in RDX. Questa esatta routine GModule profilata usa solo bit di flag supportati da ESI e non dereferenzia RDX sul percorso di caricamento riuscito. Un harness offline ha validato quel entry point e la firma delle istruzioni prima che fosse usato nelle esecuzioni misurate.
ffmpeg con l'encoder libaom-av1cc sulla macchina dell'attaccanteInstalla l'unica dipendenza Python:```bash python3 -m venv .venv . .venv/bin/activate python3 -m pip install -r requirements.txt
Conferma il supporto alla codifica AV1:```bash
ffmpeg -hide_banner -encoders | grep libaom-av1
Il laboratorio accetta intenzionalmente caricamenti arbitrari e passa file selezionati a sharp. Non esporlo a una rete non attendibile.```bash cd lab npm ci npm run build npm run start
Il target è quindi disponibile all'indirizzo `http://127.0.0.1:3000`.