- A fronteira de privilégios do WebUI do Chromium, o recurso de teste de políticas corporativas e uma falha na API de extensões do DevTools se combinaram, permitindo que uma extensão maliciosa do Chrome chegasse à execução de comandos de shell com apenas pouca interação do usuário
- O caminho de ataque chamava uma API de teste de políticas não documentada em
chrome://policypara alterar políticas do usuário e depois abusava do caminho e dos argumentos do navegador alternativo do Browser Switcher como comandos de shell chrome.devtools.inspectedWindow.reload()permitia executarinjectedScript, e por causa de um atraso no bloqueio de acesso no momento da navegação para WebUI ou de uma requisiçãoPage.reloadremanescente após um crash, era possível executar código em um WebUI privilegiado- O Google classificou as falhas como P1/S1 e adicionou validação de
loaderIdemPage.reload, verificação de URL eminspectedWindow.reload()e checagem de ativação do teste de políticas nos handlers do WebUI - As falhas relacionadas receberam os identificadores CVE-2024-5836 e CVE-2024-6778, ambas com CVSS 8.8 High, e a recompensa final foi de $20.000
WebUI do Chromium e a fronteira do sandbox
- O Chromium executa código não confiável dentro de um sandbox, e o JavaScript de extensões do Chrome também deve operar apenas dentro das permissões concedidas e das APIs acessíveis
- Só com permissões de extensão já é possível roubar credenciais de login ou histórico do navegador, mas em princípio o impacto deveria permanecer dentro do navegador
- Partes da GUI do Chromium são implementadas como WebUI, como
chrome://settingsechrome://history- O WebUI é escrito em HTML, CSS e JavaScript, mas como precisa exibir e modificar informações internas do navegador, tem privilégios mais altos do que páginas web comuns
- O JavaScript do frontend do WebUI pode se comunicar com código nativo C++ do navegador por meio de APIs privadas
- Se a execução de código se tornar possível dentro do WebUI, isso pode levar ao bypass do sandbox do Chromium, então é importante impedir que atacantes executem JavaScript não confiável em páginas
chrome:// - Por exemplo, se um item de download
.exefor clicado emchrome://downloads, o executável pode ser aberto; por isso, o Chromium verifica se a ação de abrir o arquivo realmente veio de uma entrada do usuário
Bypass no recurso de teste de políticas corporativas
- A busca pela vulnerabilidade começou no enterprise policy system do Chromium
- Esse sistema permite que administradores apliquem configurações obrigatórias em dispositivos de empresas ou escolas
- As políticas normalmente ficam vinculadas a uma conta Google e são baixadas de servidores de administração do Google
- As políticas se dividem em device policies e user policies
- As device policies gerenciam configurações do dispositivo inteiro no Chrome OS
- As user policies se aplicam a um usuário específico ou a uma instância do navegador e estão disponíveis em todas as plataformas
- No Linux, é possível aplicar user policies a uma instância do Google Chrome colocando arquivos JSON em
/etc/opt/chrome/policies, mas é preciso privilégio de root para escrever nesse diretório
- As políticas atualmente aplicadas no dispositivo podem ser vistas no WebUI
chrome://policy- Essa página oferece a lista de políticas aplicadas, logs do serviço de políticas e exportação em JSON
- Em condições normais, não há como editar políticas nessa página
- As notas de lançamento do Chrome Enterprise v117 mencionavam que a página
chrome://policy/testpermitia testar políticas nos canais Beta, Dev e Canary- Esse recurso não era mencionado na documentação do Chromium fora dessas notas de lançamento
- Para ativá-lo normalmente, era necessária a política não documentada
PolicyTestPageEnabled - Sem essa política,
chrome://policy/testredirecionava parachrome://policy
Falha de validação em setLocalTestPolicies
- O código JavaScript de
chrome://policy/testdefinia políticas de teste comsendWithPromise('setLocalTestPolicies', ...)sendWithPromise()é um wrapper da API privada do WebUIchrome.send()- Essa chamada envia uma requisição para uma função handler em C++, que pode executar operações internas do navegador
- Ao chamar
setLocalTestPoliciesdiretamente no console dechrome://policy, o navegador inicialmente travou, e os logs mostraram uma mensagem dizendo que era necessário um array de políticas - Depois de ajustar o formato do array e passar uma user policy como
AllowDinosaurEasterEgg, políticas arbitrárias puderam ser definidas mesmo sem ativar explicitamente o recurso - O handler
HandleSetLocalTestPoliciesno lado C++ apenas verificava a existência delocal_test_provider, sem checar se o recurso de teste de políticas estava realmente permitido LocalTestPolicyProvider::CreateIfAllowed()chamavaIsPolicyTestingEnabled(nullptr, channel)- Como o primeiro argumento,
pref_service, eranull, a verificação dePolicyTestPageEnabledera ignorada - A checagem restante só testava se o canal de release era
CANARYouDEFAULT
- Como o primeiro argumento,
- Em builds unbranded do Chromium, o código
GOOGLE_CHROME_BRANDINGnão é compilado, então o canal permanece comoUNKNOWN- Como no enum
UNKNOWN = 0eDEFAULT = UNKNOWN, a checagem de canal passa em builds do Chromium e derivados - Em builds stable do Google Chrome com branding, o canal de release é configurado corretamente, então esse bug não funciona no Google Chrome stable
- Como no enum
Execução de comandos de shell via Browser Switcher
- Quando se tornou possível definir user policies arbitrárias, o módulo Legacy Browser Support das políticas corporativas do Chrome virou um caminho para fuga do sandbox
- O Legacy Browser Support também é chamado de Browser Switcher e foi projetado para abrir um navegador alternativo quando o usuário visita certas URLs no Chromium
- É um recurso criado para dar suporte a usuários do Internet Explorer
- Seu comportamento é controlado por políticas
- Ao combinar as políticas
AlternativeBrowserPatheAlternativeBrowserParameters, o Chromium podia executar comandos de shell arbitrários como “navegador alternativo”- Essas políticas do Browser Switcher existem apenas em Linux, macOS e Windows
- O fluxo de exemplo era o seguinte
- Definir
BrowserSwitcherEnabledcomotrue - Colocar
example.comemBrowserSwitcherUrlList - Definir
AlternativeBrowserPathcomo/bin/bashno Linux - Definir
AlternativeBrowserParameterscomo["-c", "xcalc # ${url}"]
- Definir
- Quando o navegador navegava para
example.com, o Browser Switcher era acionado e um comando no formato/bin/bash -c 'xcalc # https://example.com'era executado- O valor substituído de
${url}era colocado depois de#para virar comentário de shell
- O valor substituído de
- Depois de definir as políticas em
chrome://policy, bastava chamarwindow.open("https://example.com")para chegar à execução arbitrária de comandos de shell só com JavaScript
Caminho de bypass pela API de extensões do DevTools
- Só a etapa anterior ainda exigia que a vítima colasse código malicioso no console do navegador em
chrome://policy, então a exploração era pouco prática - O caminho para execução automática foi encontrado por meio de uma extensão maliciosa do Chrome
- Extensões podem injetar JavaScript em páginas, mas não deveriam conseguir executar JavaScript em páginas WebUI privilegiadas
- Havia quatro APIs principais para extensões executarem JavaScript em páginas
chrome.scriptingchrome.tabsdo Manifest v2chrome.debuggerchrome.devtools.inspectedWindow
- O foco da investigação foi
chrome.devtools.inspectedWindow, considerado relativamente menos endurecido- Extensões que usam a API
chrome.devtoolsprecisam ter o campodevtools_pageno manifest - Quando o usuário abre o DevTools, essa página é carregada em um iframe, e dali a extensão pode usar as APIs
chrome.devtools
- Extensões que usam a API
- Já havia um relatório anterior de David Erceg mostrando execução de código em WebUI usando
chrome.devtools.inspectedWindow.eval()- Em condições normais, o uso da API do DevTools deveria ser desativado quando a página inspecionada navega para um WebUI
- O núcleo do bypass era enviar a requisição de eval antes de o Chrome desativar a API, fazendo com que ela chegasse à página WebUI
inspectedWindow.reload() e a particularidade do about:blank
chrome.devtools.inspectedWindow.reload()também pode executar JavaScript na página inspecionada se receber o argumentoinjectedScript- Ao chamar
inspectedWindow.reload()em uma páginaabout:blankaberta por um WebUI, foi possível executar JavaScript em uma página privilegiadaabout:blanknão é especial pela URL em si, mas herda os privilégios e a origin da página que a abriu- Um
about:blankaberto porchrome://settingstinha originchrome://settingse era uma página privilegiada
- O código que desativava a API do DevTools verificava apenas a URL da página inspecionada, não a origin
- Mesmo que a URL parecesse comum, a origin podia ser privilegiada
- Só o caminho via
about:blanknão era fácil de usar diretamente na cadeia de exploit, porquechrome://policynão abre popupsabout:blank - Ainda assim,
inspectedWindow.reload()executava JavaScript emchrome://settingsmesmo em situações em queinspectedWindow.eval()falhava- Isso mostrava que
eval()tinha uma verificação própria equivalente a uma checagem de origin, enquantoreload()não tinha proteção no mesmo nível
- Isso mostrava que
Da race condition para um método estável baseado em crash
- A primeira cadeia de exploit repetia chamadas a
inspectedWindow.reload()para atingir a curta janela de tempo logo após a página inspecionada navegar para um WebUI e antes de a página do DevTools desativar a API- Isso dependia de a página inspecionada e a página do DevTools estarem em processos diferentes
- Se uma requisição
reload()entrasse entre a navegação parachrome://policye a desativação da API do DevTools, o código seria executado no WebUI
- O método funcionava, mas tinha baixa confiabilidade
- Após ajustes, a taxa de sucesso ficou em cerca de 70%
- Embora fosse uma vulnerabilidade grave, a instabilidade podia reduzir sua severidade
- Depois disso, foi testado se, como na técnica anterior de David Erceg, o comportamento de requisições de depurador pendentes após um crash de aba também poderia ser aplicado a
inspectedWindow.reload() - Ao acionar a instrução
debuggerduas vezes seguidas, a aba travava, e uma requisiçãoPage.reloadpodia permanecer na fila e ser executada depois da navegação para o WebUI- Isso eliminava a necessidade de race condition e fazia o método funcionar de forma 100% confiável
- Em um patch de bug anterior, o Google passou a limpar requisições pendentes do depurador após um crash, mas deixou as requisições
Page.reloadcomo exceção- Como
inspectedWindow.reload()envia internamente uma requisiçãoPage.reload, essa exceção a afetava diretamente - O patch da época não impedia que
Page.reloadpudesse executar scripts
- Como
- O crash da aba também podia ser provocado por consumo excessivo de memória, mas no PoC final foi usado o crash por
debugger, que era mais rápido
Cadeia final de exploit e interação do usuário
- O PoC final seguia esta sequência
- Usar a vulnerabilidade em
chrome.devtools.inspectedWindow.reload()para executar um payload JavaScript emchrome://policy - O payload chamava
sendWithPromise("setLocalTestPolicies", policy)para definir políticas do usuário - Eram configuradas
BrowserSwitcherEnabled,BrowserSwitcherUrlList,AlternativeBrowserPatheAlternativeBrowserParameters - Em seguida,
window.open()ou uma navegação de página acionava o Browser Switcher para executar o comando de shell
- Usar a vulnerabilidade em
- O PoC usava comandos para abrir a calculadora conforme o sistema operacional
- Windows:
C:\Windows\System32\cmd.exeecalc.exe - Linux:
/bin/bashexcalc - macOS:
/bin/basheopen -na Calculator
- Windows:
- A interação do usuário se limitava basicamente a fazê-lo abrir o DevTools
- A mensagem “extension install error” na tela de exemplo servia para induzir o usuário a abrir o DevTools
- Quando o DevTools era aberto, a cadeia que levava à fuga do sandbox começava
Correções do Google e atribuição de CVEs
- Após o reporte, o Google confirmou rapidamente as falhas e as classificou como P1/S1
- P1/S1 significa alta prioridade e alta severidade
- Nas semanas seguintes, três correções principais foram aplicadas
- Adição do argumento
loaderIdao comandoPage.reloade verificação deloaderIDno lado do renderer- Isso restringe o comando a uma única origin e impede que ele funcione caso chegue acidentalmente a uma página privilegiada
- Verificação de URL na função
inspectedWindow.reload()- Assim, a proteção não depende apenas da revogação de acesso à API da extensão
- Checagem de ativação do teste de políticas no handler do WebUI
- Isso bloqueia completamente as políticas de teste
- Adição do argumento
- A vulnerabilidade relacionada à race condition recebeu o identificador CVE-2024-5836
- Sua pontuação de severidade CVSS é 8.8 High
- A vulnerabilidade relacionada ao crash da página inspecionada recebeu o identificador CVE-2024-6778
- Essa falha também recebeu pontuação CVSS 8.8
- Depois que as correções foram integradas ao branch de release, o painel do Chrome VRP definiu a recompensa, e o valor final foi de $20.000
Cronograma de divulgação e materiais
- A timeline foi a seguinte
- 16 de abril: descoberta do bug de test policies
- 29 de abril: descoberta do bug de race condition em
inspectedWindow.reload() - 1º de maio: reporte dos bugs ao Google
- 4 de maio: classificação como P1/S1 pelo Google
- 5 de maio: descoberta do bug relacionado ao crash da página inspecionada e atualização do reporte
- 6 de maio: o Google pediu relatórios separados para cada parte da cadeia
- 8 de julho: os bugs foram marcados como corrigidos
- 13 de julho: envio ao painel do Chrome VRP para decisão de recompensa
- 17 de julho: o painel do VRP definiu a recompensa de $20.000
- 15 de outubro: divulgação pública do relatório completo
- O relatório original relacionado pode ser visto em crbug.com/338248595
- Os PoCs de cada parte da vulnerabilidade foram publicados em um repositório no GitHub
- O bug em
inspectedWindow.reloadfuncionava desde o Chrome v45 - Quando recursos não documentados, inacabados e inseguros são distribuídos para todos os usuários, erros simples podem se combinar e resultar em vulnerabilidades de alta severidade
1 comentários
Opiniões no Hacker News
Disseram que a URL da página é substituída por
${url}e que, se colocada depois de#, vira comentário para não estragar o comando; mas existe alguma lógica de validação nessa política exigindo que a URL seja passada para algum lugar emAlternativeBrowserParameters?Um estudante do ensino médio interessado em programação, desenvolvimento web e cibersegurança — realmente impressionante
A ética profissional de ter seguido até o processo de divulgação responsável também é admirável; parece alguém que vai longe
Foi um ótimo texto e um ótimo trabalho; deu para sentir a empolgação crescendo junto conforme as descobertas iam se acumulando
A recompensa também foi mais do que merecida
O encadeamento das vulnerabilidades foi elegante, e o texto também é excelente. Gostei de como ele destrinchou o funcionamento do código vulnerável
Truques simples como “pressione F12 para tentar novamente” me impressionam toda vez que vejo; é realmente muito brincalhão
Isso me lembrou de quando eu usei a mesma API para depurar o shell
croshdo Chrome OS, contornar as proteções do SO e até obter acesso root em um dispositivo de desenvolvimento. Foi a CVE-2014-3172Dito isso, o autor deste texto teve que contornar obstáculos muito mais difíceis, e foi realmente um excelente trabalho
Está tarde demais para eu investigar a fundo o que quebrou na validação do WebUI, mas gostei de ver que ele foi até o fim para descobrir
Desconfiar e não confiar na cadeia de ferramentas das coisas que distribuímos é uma postura bastante padrão, mas ao mesmo tempo confiamos demais nas ferramentas de desenvolvimento magicamente convenientes de grandes empresas como Google ou Microsoft. No fim das contas, é porque quero escrever e testar meu código em vez de me preocupar com o que pode estar escondido dentro do Chromium ou do VSCode
Um dos melhores textos que já li
Foi um trabalho de investigação realmente inteligente
O esforço de vasculhar o código do navegador até chegar a isso é impressionante, e o texto também é muito interessante e detalhado
Um estudante do ensino médio? Uau, realmente incrível
O projeto Chromium removeu o
chrome://net-internalsalegando que era complexo demais, e depois adicionou ochrome://policycom suporte meio inacabado para edição de JSON