
Google Chrome CVE-2026-6307 PoC
Reprodução local e weaponização da confusão de tipos de desotimização V8 JS-to-Wasm descrita por Nebula, levada a um xcalc aberto a partir do renderizador do Chrome for Testing 146.0.7680.165 (o bug foi corrigido no 147.0.7727.101).
Todos os payloads são benignos (uma calculadora / exit(0x42) / jmp $).
Tudo é executado contra um navegador deliberadamente desatualizado em um laboratório local
não aponte para mais ninguém.
https://github.com/user-attachments/assets/d450f064-ba52-4f39-a88b-367f31217143
O operador de igualdade de JSToWasmFrameStateFunctionInfo omite a comparação de assinatura Wasm, então CSE/GVN mescla estados de frame que diferem apenas na assinatura. Uma desotimização preguiçosa então materializa um i64 Wasm como um externref marcado (e vice-versa), fornecendo um addrof/fakeobj completo de 64 bits cujos ponteiros não são comprimidos em cage (as refs Wasm usam a representação de registro marcado, retornado via kReturnRegister0).
Tudo abaixo foi verificado ao vivo contra 146.0.7680.165. Os números por carregamento são taxas de acerto medidas ao longo de execuções repetidas (a confusão é sensível ao GC; cada página reinicia em uma build suja). Nenhuma execução jamais produziu um falso positivo.
--allow-natives-syntaxA maioria dos PoCs de bugs do V8 força a otimização com %OptimizeFunctionOnNextCall, uma intrínseca apenas para teste protegida por --allow-natives-syntax que nenhuma vítima real ativa. Estes acionam o bug sem nenhuma flag, moldando o feedback de aquecimento para que a confusão de desotimização ocorra naturalmente. Cada endereço é vazado em tempo de execução — nada é hardcoded. Execute-os headless sem flags.
rce-no-aslr/ leva à execução de código: uma função JS otimizada g monta shellcode execve como literais double do JIT (constantes movabs, cada uma terminando em eb rel8 para encadear a próxima), a gravação confusa aplica um nop; jmp no código de g, e chamar g() desliza o RIP para dentro do shellcode.
Esta build usa --allow-natives-syntax (%Optimize fixa o payload JIT de g para que o GC de desotimização não possa descartá-lo) e --no-memory-protection-keys (para que a gravação confusa possa corrigir a página JIT — caso contrário, a PKU do V8 a marca como somente leitura). ASLR está desligado (randomize_va_space=0) então os endereços são determinísticos (A_MAP = 0x5555bbc0128a). O renderizador também precisa de --disable-seccomp-filter-sandbox --no-zygote, porque seccomp bloqueia o execve do shellcode.
Linux nativo x86_64, ASLR desligado, Chrome 146 no PATH, um display X em :0 (Xvfb ou X real). rce-no-aslr/run.sh define todas as flags e serve via http.
cd rce-no-aslr
./run.sh calc # pop a real xcalc over http://127.0.0.1:8080, on :0
./run.sh exit # strace-confirms the renderer's exit(66)
./run.sh jmp # gdb-confirms RIP inside the injected shellcode
./demo.sh calc # clean-slate + auto-retry, full-size headful browser (for recording)
./demo.sh msgbox # same, but the pop is a branded "0xSha :: pwned" box
pocmode.sh calc|msgbox alterna o que o execve("/usr/bin/xcalc") do RCE lança (uma calculadora real vs a caixa personalizada). As primitivas sem flags só precisam de um navegador:
chrome --headless=new --no-sandbox http://127.0.0.1:8080/flag-free/01-primitives-flagfree.html
flag-free/ a contribuição sem flags (SEM --allow-natives-syntax)
00-reachability acionador + diferencial de patch (vuln 146 vs corrigido 147)
01-primitives addrof / fakeobj
02-store gravação fora da cage
03-arw read64 / write64 dentro da cage
rce-no-aslr/ o RCE visível (determinístico, build de laboratório sem ASLR)
exploit-calc.html / exploit-exit.html
run.sh, demo.sh, pocmode.sh, xcalc-msgbox.sh
Artigo sobre o bug e causa raiz: Nebula Security (link acima). O acionador sem flags, primitivas e os trabalhos visíveis neste repositório são reproduções/extensões verificadas em um laboratório local.
| Arquivo | Comprova | Verificado |
|---|
flag-free/00-reachability-flagfree.html | aciona o estado confuso; dispara no 146 vulnerável, não no 147 corrigido | 6/15 por carga |
flag-free/01-primitives-flagfree.html | addrof / fakeobj (fakeobj(addrof(o))===o) | 8/12 |
flag-free/02-store-flagfree.html | gravação fora da cage coloca um valor escolhido em um objeto escolhido | 12/12 |
flag-free/03-arw-flagfree.html | read64 / write64 dentro da cage (array fake PACKED_DOUBLE) | 2/12 (instável, tentar novamente) |
| Arquivo | Comprova | Verificado |
|---|
rce-no-aslr/exploit-calc.html | abre uma janela real de xcalc via execve a partir do renderizador | abre em ~1s em estado limpo |
rce-no-aslr/exploit-exit.html | renderizador executa o shellcode injetado para exit(66); #jmp gira RIP dentro dele | exit(66) 8/8 (strace) |