1 pontos por GN⁺ 2024-10-18 | 1 comentários | Compartilhar no WhatsApp
  • 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://policy para 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 executar injectedScript, e por causa de um atraso no bloqueio de acesso no momento da navegação para WebUI ou de uma requisição Page.reload remanescente 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 loaderId em Page.reload, verificação de URL em inspectedWindow.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://settings e chrome://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 .exe for clicado em chrome://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/test permitia 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/test redirecionava para chrome://policy

Falha de validação em setLocalTestPolicies

  • O código JavaScript de chrome://policy/test definia políticas de teste com sendWithPromise('setLocalTestPolicies', ...)
    • sendWithPromise() é um wrapper da API privada do WebUI chrome.send()
    • Essa chamada envia uma requisição para uma função handler em C++, que pode executar operações internas do navegador
  • Ao chamar setLocalTestPolicies diretamente no console de chrome://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 HandleSetLocalTestPolicies no lado C++ apenas verificava a existência de local_test_provider, sem checar se o recurso de teste de políticas estava realmente permitido
  • LocalTestPolicyProvider::CreateIfAllowed() chamava IsPolicyTestingEnabled(nullptr, channel)
    • Como o primeiro argumento, pref_service, era null, a verificação de PolicyTestPageEnabled era ignorada
    • A checagem restante só testava se o canal de release era CANARY ou DEFAULT
  • Em builds unbranded do Chromium, o código GOOGLE_CHROME_BRANDING não é compilado, então o canal permanece como UNKNOWN
    • Como no enum UNKNOWN = 0 e DEFAULT = 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

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 AlternativeBrowserPath e AlternativeBrowserParameters, 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 BrowserSwitcherEnabled como true
    • Colocar example.com em BrowserSwitcherUrlList
    • Definir AlternativeBrowserPath como /bin/bash no Linux
    • Definir AlternativeBrowserParameters como ["-c", "xcalc # ${url}"]
  • 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
  • Depois de definir as políticas em chrome://policy, bastava chamar window.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.scripting
    • chrome.tabs do Manifest v2
    • chrome.debugger
    • chrome.devtools.inspectedWindow
  • O foco da investigação foi chrome.devtools.inspectedWindow, considerado relativamente menos endurecido
    • Extensões que usam a API chrome.devtools precisam ter o campo devtools_page no 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
  • 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 argumento injectedScript
  • Ao chamar inspectedWindow.reload() em uma página about:blank aberta por um WebUI, foi possível executar JavaScript em uma página privilegiada
    • about:blank não é especial pela URL em si, mas herda os privilégios e a origin da página que a abriu
    • Um about:blank aberto por chrome://settings tinha origin chrome://settings e 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:blank não era fácil de usar diretamente na cadeia de exploit, porque chrome://policy não abre popups about:blank
  • Ainda assim, inspectedWindow.reload() executava JavaScript em chrome://settings mesmo em situações em que inspectedWindow.eval() falhava
    • Isso mostrava que eval() tinha uma verificação própria equivalente a uma checagem de origin, enquanto reload() não tinha proteção no mesmo nível

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 para chrome://policy e 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 debugger duas vezes seguidas, a aba travava, e uma requisição Page.reload podia 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.reload como exceção
    • Como inspectedWindow.reload() envia internamente uma requisição Page.reload, essa exceção a afetava diretamente
    • O patch da época não impedia que Page.reload pudesse executar scripts
  • 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 em chrome://policy
    • O payload chamava sendWithPromise("setLocalTestPolicies", policy) para definir políticas do usuário
    • Eram configuradas BrowserSwitcherEnabled, BrowserSwitcherUrlList, AlternativeBrowserPath e AlternativeBrowserParameters
    • Em seguida, window.open() ou uma navegação de página acionava o Browser Switcher para executar o comando de shell
  • O PoC usava comandos para abrir a calculadora conforme o sistema operacional
    • Windows: C:\Windows\System32\cmd.exe e calc.exe
    • Linux: /bin/bash e xcalc
    • macOS: /bin/bash e open -na Calculator
  • 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

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.reload funcionava 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

 
GN⁺ 2024-10-18
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 em AlternativeBrowserParameters?

  • Um estudante do ensino médio interessado em programação, desenvolvimento web e cibersegurança — realmente impressionante

    • Talento técnico incrível, perseverança, documentação e capacidade de comunicação, tudo excelente
      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

    • Moro no Missouri e, uma vez, apertei F12, e o governador tentou me prender
  • Isso me lembrou de quando eu usei a mesma API para depurar o shell crosh do Chrome OS, contornar as proteções do SO e até obter acesso root em um dispositivo de desenvolvimento. Foi a CVE-2014-3172
    Dito 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-internals alegando que era complexo demais, e depois adicionou o chrome://policy com suporte meio inacabado para edição de JSON