
PoC privado da Fortbridge para a cadeia RCE CVE-2026-32740 Next.js/sharp leak-to-memcpy-GOT
Prova de conceito funcional que transforma o CVE-2026-32740 na
pilha de imagens Next.js/sharp com versões fixas em uma escrita em endereço escolhido e um
callback validado para /usr/bin/id.
Este é um repositório privado de pesquisa da Fortbridge. Use-o apenas contra o laboratório incluído ou outro sistema para o qual você tenha autorização explícita de teste.
O exploit usa apenas as rotas HTTP de upload e otimização de imagens do alvo:
g_module_open_full do GLib, uma rotina
GModule que envolve dlopen para carregar uma biblioteca compartilhada nativa a partir de um
caminho de arquivo. O exploit calcula seu endereço em tempo de execução como a base libvips
recuperada mais o offset de perfil fixo 0x3e995e. Carregar a biblioteca executa
seu construtor./usr/bin/id. Ele envia esse ELF pela rota pública de
upload sob o nome de imagem x.jpg e tipo de mídia image/jpeg.memcpy@GOT - 16.uploads/x.jpg imediatamente antes do
slot GOT e substitui memcpy@GOT pelo carregador GModule derivado. A próxima
linha chama esse carregador com o caminho da biblioteca já em RDI./usr/bin/id via TCP. Um token por tentativa impede que um callback
obsoleto seja contado como sucesso; a linha uid=... retornada é a prova.As execuções originais de validação relativa ao libvips estão registradas em
evidence/libvips-gmodule-rce-10x.json.
Todos os dez processos novos retornaram saída válida de /usr/bin/id com dez
bases libvips randomizadas distintas e dez endereços de carregador derivados
independentemente.
Os dados exatos da execução no Ubuntu estão em
evidence/libvips-gmodule-pie-rce-10x.json.
Os dados exatos da execução no Debian 13 APT estão em
evidence/debian13-apt-libvips-gmodule-rce-10x.json.
Testamos o exploit completo contra 10 processos Node do Ubuntu recém-iniciados
e 10 processos Node do Debian recém-iniciados. Todas as 20 execuções alcançaram execução
de comando e retornaram a saída de /usr/bin/id do alvo. O exploit também
calculou um endereço de carregador GModule diferente para cada base libvips
randomizada.
Antes de criar o payload final, o exploit pode precisar de várias tentativas de vazamento de ASLR. Cada tentativa envia o AVIF de vazamento, o encaminha pela rota de otimização e verifica o PNG retornado em busca de quatro ponteiros que identificam um perfil e uma base libvips. O Ubuntu precisou de 3 a 23 tentativas. O Debian precisou de 3 a 15. Evidências incompletas ou ambíguas causam outra tentativa em vez de um perfil adivinhado.
O projeto também tem 96 testes de regressão automatizados. Estes são verificações em nível de código, não 96 execuções adicionais do exploit. Eles cobrem validação de perfil, classificação de ponteiros retornados, calibração de heap, cálculo de endereço, construção de payload e falha segura quando a evidência não corresponde a um alvo suportado.
Ambos os alvos x86-64 usam Next.js 15.5.23, sharp 0.34.4, libvips 8.17.2 empacotado, libheif 1.20.2 empacotado, ASLR e NX. Seus runtimes nativos diferem:
| Perfil | Node | glibc | libstdc++ | Relação do seletor |
|---|---|---|---|---|
| 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 |
Os build IDs completos e valores SHA-256 estão em
profiles/native_stack_profiles_pie.json.
As medições de pacote Debian, artefato, route-smoke e layout de cinco tempos de vida
estão capturadas em
evidence/debian13-profile-derivation.json.
O alvo Debian usa o pacote de distribuição padrão
nodejs=20.19.2+dfsg-1+deb13u3; o Node não é compilado a partir do código-fonte.
A cadeia não precisa da base Node randomizada. Seu alvo de controle é a
rotina interna g_module_open_full dentro do libvips, cuja base randomizada é
recuperada dos pixels retornados. Cada perfil versionado fixa as
constantes específicas da build exigidas pelo exploit:
memcpy@GOT e o offset de g_module_open_full mais seus bytes de
instrução de validação;A base libvips randomizada, o endereço de carregador resultante em tempo de execução e o seletor final de dois bytes não são fixos. Eles são derivados para cada processo alvo a partir dos pixels retornados e do perfil selecionado.
O exploit falha de forma segura quando um perfil está malformado ou os pixels retornados não selecionam exatamente um par perfil/base suportado. O perfil padrão também requer seu registro de heap retornado completo. Um perfil é uma afirmação exata de compatibilidade, então o operador deve verificar os artefatos do alvo offline antes de usá-lo.
A ABI do carregador é importante. A chamada sobrescrita fornece o caminho da biblioteca em RDI, um ponteiro de linha de imagem em RSI e o comprimento de cópia de 58 bytes em RDX. Esta rotina GModule exata do perfil usa apenas bits de flag suportados de ESI e não desreferencia RDX no caminho de carregamento bem-sucedido. Um harness offline validou esse ponto de entrada e assinatura de instrução antes de ser usado nas execuções medidas.
ffmpeg com o codificador libaom-av1cc na máquina do atacanteInstale a única dependência Python:```bash python3 -m venv .venv . .venv/bin/activate python3 -m pip install -r requirements.txt
Confirme o suporte à codificação AV1:```bash
ffmpeg -hide_banner -encoders | grep libaom-av1
O laboratório aceita intencionalmente uploads arbitrários e passa arquivos selecionados para sharp. Não o exponha a uma rede não confiável.```bash cd lab npm ci npm run build npm run start
O alvo fica então disponível em `http://127.0.0.1:3000`.