Cold Hard Cache: contornando a segurança de interfaces RPC com violação de cache
Resumo executivo
Os pesquisadores da Akamai encontraram duas vulnerabilidades importantes em serviços remoto que foram atribuídas ao CVE-2022-38034 e CVE-2022-38045 com pontuações básicas de 4,3 e 8,8, respectivamente.
As vulnerabilidades aproveitam uma falha de design que permite o desvio de retornos de chamada de segurança MS-RPC por meio do armazenamento em cache.
Confirmamos que a vulnerabilidade existe em máquinas com Windows 10 e Windows Server 11 sem patch.
As vulnerabilidades foram divulgadas com responsabilidade à Microsoft e abordadas na Patch Tuesday de outubro.
O processo de descoberta de vulnerabilidades é auxiliado por uma ferramenta de automação e por uma metodologia desenvolvida por pesquisadores da Akamai.
Fornecemos uma prova de conceito das vulnerabilidades e ferramentas usadas em nossa pesquisa no repositório do kit de ferramentas RPC.
Introdução
O MS-RPC é um dos pilares do sistema operacional Windows. Lançado nos anos 1990, ele se tornou enraizado na maioria das partes do sistema. O gerente de serviços? RPC. Lsass? RPC. COM? RPC. Mesmo algumas operações de domínio contra o controlador de domínio usam RPC. Considerando como o MS-RPC se tornou comum, seria de se esperar que ele tivesse sido rigorosamente examinado, documentado e pesquisado.
Bem, não exatamente. Embora a documentação da Microsoft sobre o uso de RPC seja muito boa, não foi escrito muito mais que isso sobre o assunto, e há ainda menos documentação escrita por pesquisadores que analisam o RPC, especificamente no que diz respeito à sua segurança. Isso provavelmente poderia ser atribuído ao fato de que o RPC (não apenas o MS-RPC, embora a Microsoft certamente tenha adicionado ao mix) é muito complexo, tornando a pesquisa e a compreensão tarefas assustadoras.
Mas estamos sempre à altura do desafio, por isso decidimos mergulhar de cabeça no fundo do mar do MS-RPC. Não só porque esse é um tópico de pesquisa interessante, mas também por causa de suas implicações de segurança. Mesmo agora, as técnicas comuns de ataque dependem do RPC (T1021.003 acontece no MS-COM, T1053.005 é MS-TSCH, T1543.003 é MS-SCMR, para citar alguns). Existem mecanismos de segurança incorporados ao MS-RPC, mas e se houver vulnerabilidades que possam permitir que sejam contornados ou violados, ou que permitam que um serviço RPC exposto seja violado para afetar máquinas de uma forma indesejada?
Na verdade, conseguimos encontrar uma maneira de contornar um mecanismo de segurança por meio do armazenamento em cache. Por meio dele, encontramos alguns serviços que poderíamos violar para escalar privilégios em servidores remotos, sem muitas condições necessárias (que vamos analisar mais tarde na publicação). No momento, podemos compartilhar informações sobre dois exemplos reais de exploração potencial, WksSvc e SrvSvc. Publicaremos atualizações sobre as outras vulnerabilidades que encontramos quando o processo de divulgação for concluído.
Nesta publicação do blog, focaremos no mecanismo de retorno de chamada de segurança dos servidores RPC, em como ele pode ser contornado pelo armazenamento em cache e em como nós automatizamos nossa pesquisa para sinalizar os serviços do Windows como potencialmente vulneráveis. Nossas ferramentas de automação, bem como sua saída bruta, também podem ser encontradas em nosso kit de ferramentas RPC, que é compartilhado em nosso repositório do GitHub. Nosso repositório também inclui links para outras referências úteis e trabalho feito por outros pesquisadores nos quais confiamos.
Retornos de chamada de segurança
Antes de discutirmos as vulnerabilidades em si, é importante esclarecer um dos mecanismos de segurança mais fundamentais que o MS-RPC implementa: retornos de chamada de segurança. Os retornos de chamada de segurança permitem que os desenvolvedores de servidores RPC restrinjam o acesso a uma interface RPC. Isso permite que eles apliquem sua própria lógica para permitir o acesso a usuários específicos, imponham tipos de autenticação ou de transporte ou impeçam o acesso a opnums específicos (as funções expostas pelo servidor são representadas usando opnums; ou seja, números operacionais).
Esse retorno de chamada é disparado pelo tempo de execução RPC cada vez que o cliente chama uma função exposta no servidor.
Em nossa pesquisa, nos concentramos na interação remota cliente-servidor. Mencionamos isso porque as implementações do código no lado do servidor de tempo de execução RPC diferem entre um ponto de extremidade ALPC e um ponto de extremidade remoto tal como um pipe nomeado.
Armazenamento em cache
O tempo de execução RPC implementa o armazenamento em cache do resultado do retorno de chamada de segurança para melhor desempenho e utilização. Isso significa basicamente que o tempo de execução tentará usar uma entrada em cache antes de chamar o retorno de chamada de segurança em todas as vezes. Vamos nos aprofundar na implementação.
Antes que a RPC_INTERFACE::DoSyncSecurityCallback chame o retorno de chamada de segurança, primeiro verifica se existe uma entrada de cache. Isso é feito chamando OSF_SCALL::FindOrCreateCacheEntry.
OSF_SCALL::FindOrCreateCacheEntry executa as seguintes operações:
Busca o contexto de segurança do cliente a partir da SCALL (um objeto que representa uma chamada de cliente).
Busca o dicionário de cache do contexto de segurança do cliente.
Usa o indicador de interface como uma chave para o dicionário. O valor é a entrada de cache.
Se não houver nenhuma entrada de cache, ela criará uma.
Uma entrada de cache tem três campos importantes: o número de procedimentos na interface, um bitmap e a geração da interface.
Durante a vida útil de um servidor RPC, a interface pode ser alterada, por exemplo, se o servidor chamar RpcServerRegisterIf3 em uma interface existente. Isso, por sua vez, chama RPC_INTERFACE::UpdateRpcInterfaceInformation, que atualiza a interface e incrementa a geração da interface. Dessa forma, o cache sabe que precisa ser "redefinido", pois as entradas de cache podem ser da interface antiga.
O mecanismo de cache pode funcionar em dois modos: em uma base de interface (que é o comportamento padrão) e em uma base de chamada.
Cache baseado em interface
Nesse modo, o armazenamento em cache funciona em uma base de interface. Isso significa que, do ponto de vista do cache, não há diferença entre duas chamadas para duas funções diferentes, desde que estejam na mesma interface.
Para saber se a entrada de cache pode ser usada em vez de chamar o retorno de chamada de segurança, o tempo de execução RPC compara a geração de interface que é salva na entrada de cache com a geração de interface real. Como a inicialização da entrada de cache zera a geração da interface, na primeira vez que a comparação for realizada, as gerações de interface serão diferentes e, portanto, o retorno de chamada de segurança será chamado. Se o retorno de chamada for retornado com êxito, o tempo de execução RPC atualizará a geração de interface da entrada de cache (e, portanto, será "marcada" como uma entrada de cache bem-sucedida, que permite o acesso à interface sem chamar o retorno de segurança novamente). Na próxima vez que o cliente chamar uma função na mesma interface, a entrada de cache será usada.
Cache baseado em chamadas
Esse modo é usado quando a interface RPC é registrada com o sinalizador RPC_IF_SEC_CACHE_PER_PROC. Nesse modo, o armazenamento em cache é baseado em um bitmap que rastreia a quais procedimentos o retorno de chamada de segurança permitiu o acesso. Portanto, se o cliente chamou a função Foo e o retorno de chamada de segurança foi retornado com êxito, teremos uma entrada de cache para Foo. Se o cliente chamar Bar, o retorno de chamada de segurança será chamado novamente.
Requisitos de armazenamento em cache
Então, o que precisamos ter para que o armazenamento em cache funcione? Primeiro, precisamos esclarecer um pouco da terminologia. O MS-RPC representa uma conexão lógica entre um cliente e um servidor usando um identificador de vinculação. O cliente e o servidor podem manipular os dados de vinculação usando funções designadas.
Uma vinculação pode ser autenticada. Isso acontece quando o servidor registra as informações de autenticação (chamando RpcServerRegisterAuthInfo) e, em seguida, o cliente define as informações de autenticação na vinculação. Isso permite que o servidor recupere informações sobre a identidade do cliente. A saída desse processo de autenticação é um objeto de contexto de segurança criado para o cliente.
Todo o mecanismo de cache é baseado nesse contexto de segurança. Isso significa que, se a vinculação não for autenticada, então não será criado um contexto de segurança para o cliente e, portanto, o armazenamento em cache não será habilitado. Para que o armazenamento em cache funcione, o servidor e o cliente precisam registrar e definir as informações de autenticação.
Mas e se o servidor não registrou as informações de autenticação? O armazenamento em cache ainda pode ser habilitado? Introdução: multiplexação.
Multiplexação
Até o Windows 10, versão 1703, um serviço poderia compartilhar o mesmo processo svchost com outros serviços. Esse comportamento afeta a segurança do MS-RPC, pois alguns objetos de tempo de execução RPC são compartilhados entre todas as interfaces. Por exemplo, ao registrar um ponto de extremidade (como a porta TCP 7777), esse ponto de extremidade pode ser usado para acessar todas as interfaces que estão sendo executadas no mesmo processo. Portanto, outros serviços que esperam ser acessados localmente agora também podem ser acessados remotamente. Isso também é descrito nesta página pela Microsoft.
Embora o fato de que os pontos de extremidade são multiplexados já seja um pouco conhecido e documentado, gostaríamos de apresentar outro comportamento semelhante, a multiplexação SSPI. Como parte do registro das informações de autenticação, o servidor deve especificar o serviço de autenticação a ser usado. O serviço de autenticação é um Provedor de suporte de segurança (SSP), que é um pacote que processa as informações de autenticação recebidas do cliente. Na maioria dos casos, será o NTLM SSP, o Kerberos SSP ou o Microsoft Negotiate SSP que escolherá a melhor opção disponível entre o Kerberos e o NTLM.
O tempo de execução RPC salva as informações de autenticação globalmente. Isso significa que se dois servidores RPC compartilharem o mesmo processo e um deles registrar informações de autenticação, o outro servidor também terá informações de autenticação. Um cliente agora pode autenticar a vinculação ao acessar cada um dos servidores. Do ponto de vista da segurança, os servidores que não registraram as informações de autenticação e que, portanto, talvez não esperassem que os clientes autenticassem a vinculação ou que ocorresse o armazenamento em cache, podem ter essas ações forçadas sobre eles.
CVE-2022-38045 — srvsvc
Munidos de nosso novo conhecimento sobre retornos de chamada de segurança RPC e armazenamento em cache, decidimos ver se podemos realmente violar o mecanismo no mundo real. Voltamos para o Srvsvc, no qual já encontramos uma vulnerabilidade isolada no passado.
O Srvsvc expõe a interface MS-SRVS. O serviço Servidor (também chamado LanmanServer) é um serviço do Windows responsável pelo gerenciamento de compartilhamentos SMB. Compartilhamentos são recursos (arquivos, impressoras e árvores de diretórios) disponibilizados pela rede por um servidor do CIFS (Common Internet File System). Em essência, os compartilhamentos de rede permitem que os usuários utilizem outros dispositivos na rede para executar várias tarefas diárias.
Quando analisamos o retorno de chamada de segurança de Srvsvc, percebemos que a função pode ter outra vulnerabilidade, diferente da que já encontramos. Vamos dar uma olhada na lógica do retorno de chamada de segurança:
Como visto acima, a lógica do retorno de chamada de segurança do Srvsvc é a seguinte:
Se um cliente remoto tentar acessar uma função no intervalo de 64 a 73 (incluído): recusar acesso
Se um cliente remoto, que não é uma conta de cluster, tentar acessar uma função no intervalo de 58 a 63 (incluído): negar acesso
Portanto, em essência, os clientes remotos são impedidos de acessar essas funções específicas da interface. Essa verificação de intervalo indica que as funções restritas são sensíveis e devem ser invocadas apenas pelos processos esperados (locais).
Embora essa verificação tente impedir o acesso remoto a essas funções, um invasor remoto pode contornar essa verificação violando o cache. Primeiro, um invasor remoto chama uma função que não está nesse intervalo. Uma função disponível remotamente. Como a função de retorno de chamada de segurança retorna RPC_S_OK, o tempo de execução RPC armazenará em cache o resultado como bem-sucedido. Como a interface não está registrada com o sinalizador RPC_IF_SEC_CACHE_PER_PROC, o cache será baseado em interface. Como resultado, na próxima vez que o invasor chamar qualquer função na mesma interface, a entrada de cache será usada e o acesso será concedido. Isso significa que o invasor agora pode chamar as funções locais às quais não deve ter acesso, e o retorno de chamada de segurança não será chamado.
O Srvsvc não registra informações de autenticação e, portanto, em circunstâncias normais, os clientes não podem autenticar a vinculação e, portanto, o armazenamento em cache não é habilitado. À medida que isso acontece, o Srvsvc compartilha o mesmo processo svchost com outros serviços quando a máquina do servidor tem menos de 3,5 GB de RAM. Os serviços "AD Harvest Sites and Subnets Service" e "Remote Desktop Configuration Service" registram informações de autenticação e, portanto, o Srvsvc agora está vulnerável a ataques de cache.
Neste cenário específico, um invasor pode acessar funções restritas com os números operacionais 58 a 74. Uma das coisas que um invasor pode fazer com essas funções é coagir a autenticação da máquina remota.
Uma caça ao tesouro
Depois de entender que a violação do mecanismo de cache do retorno de chamada de segurança pode gerar vulnerabilidades reais, decidimos tentar encontrar outras interfaces que poderiam ser vulneráveis a um ataque de cache. Mas encontrar todas as interfaces manualmente seria uma tarefa longa e árdua, por isso queríamos encontrar uma maneira de automatizá-la.
Temos duas abordagens que podemos adotar ao procurar interfaces RPC: por meio de processos em execução atuais ou por meio do sistema de arquivos.
Com processos em execução, podemos examinar os servidores RPC já carregados na memória, seja em um servidor remoto, consultando o mapeador de ponto de extremidade remoto (com rpcmap ou rpcdumpdo Impacket, por exemplo) ou localmente (usando ferramentas como RpcEnum). No entanto, há um problema com essa abordagem: Deixaremos passar qualquer interface que não esteja carregada no momento e não poderemos ver as interfaces do cliente, pois elas não estão registradas.
Como alternativa, podemos vasculhar o sistema de arquivos do Windows e procurar interfaces RPC compiladas dentro deles. Para cada interface, analisamos suas informações de registro analisando os argumentos passados para o RpcServerRegisterIf. Essa é uma abordagem semelhante ao que é feito em RpcEnum, mas vasculhamos o sistema de arquivos em vez da memória.
Em nossa pesquisa, escolhemos o método de sistema de arquivos para incluir interfaces que não foram necessariamente carregadas na memória. Escrevemos vários scripts e ferramentas para automatizar o processo, que estão disponíveis em nosso repositório do kit de ferramentas RPC.
Para encontrar interfaces com o armazenamento em cache habilitado, não precisamos analisar a própria interface RPC. Todas as informações necessárias podem ser extraídas da chamada de registro do servidor RPC. A função de registro aceita a estrutura da interface RPC, os sinalizadores de registro e o indicador de retorno de chamada de segurança. Ainda assim, a análise da estrutura da interface RPC pode fornecer informações úteis, como as funções expostas pela interface ou se ela é usada por um servidor ou cliente RPC. Embora estejamos interessados principalmente em servidores RPC (onde pode existir uma vulnerabilidade), os clientes RPC fornecem uma boa percepção sobre a chamada para o servidor, à qual podemos fazer referência para exploração.
A estrutura da interface do servidor RPC é documentada, por isso não temos que adivinhar os seus campos. Além disso, o campo de tamanho e a sintaxe de transferência são constantes (na verdade, há duas sintaxes de transferência possíveis, DCE NDR e NDR64, mas só nos deparamos com a DCE NDR).
É uma questão simples encontrar todas as estruturas de interface RPC procurando essas duas constantes (usando a Yara ou expressões regulares). Uma vez encontradas, podemos usar o campo de informações do intérprete para ver qual funcionalidade o servidor implementa.
Mas ainda faltam informações sobre o retorno de chamada de segurança da interface (se houver) e se ele está armazenado em cache. Para isso, temos de recorrer aos nossos amigos fiéis, os desmontadores. Cada desmontador que se preze terá uma funcionalidade xref, portanto, é simples encontrar todas as chamadas de função de registro de interface em um servidor RPC. A partir daí, precisamos apenas analisar os argumentos de chamada de função para extrair o endereço da estrutura de interface (para que possamos fazer referência cruzada com nossos dados do servidor RPC capturados), o endereço de retorno de chamada de segurança (se existir) e os sinalizadores da interface RPC.
Publicamos nossos scripts de captura, que fazem exatamente isso; eles estão disponíveis em nosso kit de ferramentas RPC, ao lado de suas saídas do Windows Server 2012 e Server 2022.
CVEs ou isso não aconteceu
Todas essas metodologias e teorias são boas, mas elas realmente produzem resultados?
A resposta para isso é sim. Há mais de 120 interfaces com um retorno de chamada de segurança e armazenamento em cache, muitas deles não documentadas. Isso, por si só, não é motivo para entrar em pânico, pois na maioria das vezes o retorno de chamada de segurança não será afetado muito pelo armazenamento em cache. Normalmente, as verificações feitas pelo retorno de chamada de segurança são realizadas em valores que não são armazenáveis em cache, como a sequência do protocolo de transporte (por exemplo, TCP) ou o nível de autenticação. Qualquer alteração nesse contexto requer, de qualquer forma, um novo contexto de segurança, já que uma nova conexão precisa ser estabelecida, o que redefine o cache e anula qualquer possível desvio de cache.
Encontramos algumas vulnerabilidades por meio dessa abordagem de pesquisa. Só podemos discutir uma delas no momento, pois o restante ainda está em processo de divulgação.
WksSvc
Pontuação CVE-2022-38034 CVSS: 4,3
O WksSvc expõe a interface MS-WKST. O serviço é responsável por gerenciar associações de domínio, nomes de computador e conexões com os redirecionadores de rede SMB, como os servidores de impressora SMB. Olhando para o retorno de chamada de segurança da interface, podemos ver que algumas funções são tratadas de forma diferente das restantes.
As funções cujo número operacional está entre 8 e 11 também são verificadas nas chamadas por um cliente local, o que significa que não é permitido chamá-las remotamente. Mas como temos armazenamento em cache, o que aconteceria se chamássemos primeiramente uma função diferente, que é permitida remotamente, e depois chamássemos uma das funções restritas?
Você adivinhou: Poderíamos chamar remotamente as funções localmente restritas devido ao armazenamento em cache do resultado da primeira chamada. A pergunta agora é: essas funções são importantes o suficiente para garantir que sejam restritas somente a clientes locais?
As funções expostas são NetrUseAdd, NetrUseGetInfo, NetrUseDele NetrUseEnum. Se elas soarem familiares, é porque podem ser acessados por meio do netapi32.dll (veja a NetUseAdd, por exemplo).
Isso é bom, pois nos dá uma dica sobre o que podemos fazer com esse ataque. Especificamente, podemos conectar o servidor remoto a uma pasta compartilhada de rede de nossa escolha e até mapeá-lo para uma letra de unidade lógica de nossa escolha, de maneira semelhante ao uso líquido. (Coincidência? Provavelmente não.)
Isso nos fornece dois cenários de ataque:
1. Podemos exigir autenticação em nossa pasta compartilhada; podemos retransmiti-la a um servidor diferente para um ataque de retransmissão NTLM ou armazenar os tokens e quebrar a senha offline.
2. Ou podemos mascarar um servidor de arquivos existente (ou fingir ser um novo) com arquivos interessantes ou úteis. Como esses arquivos estão sob nosso controle, podemos armá-los como considerarmos adequado, permitindo-nos infectar o usuário-alvo.
Esses dois cenários, e poder chamar remotamente funções localmente restritas, foram suficientes para que a Microsoft categorizasse essa vulnerabilidade como EoP, com uma pontuação de 4,3.
No entanto, esse não é o fim da história. Ainda temos alguns alertas que precisamos de superar.
Contexto de segurança
O servidor RPC em WksSvc não faz nenhum registro de autenticação por si só. Se o serviço estiver sendo executado por conta própria, nenhuma autenticação do lado do cliente será possível (isso resultará no erro RPC_S_UNKNOWN_AUTHN_SERVICE). Sendo assim, precisamos que o serviço esteja em execução com outros serviços para violar também a multiplexação SSPI. Isso limita nossas versões afetadas do Windows para aquelas anteriores ao Windows 10, versão 1703, ou versões mais recentes que estejam sendo executadas com menos de 3,5 GB de RAM.
Sessões de logon
Outro problema, que é incorporado em como as pastas mapeadas em rede funcionam, é que elas são limitadas à sessão de logon do usuário que as está criando. Como precisamos fazer logon em primeiro lugar para obter a vinculação e o armazenamento em cache de segurança, isso significa que sempre criaremos uma sessão de logon diferente da sessão (interativa) existente na máquina visada. Para todos os fins e propósitos, isso significa que nossa vulnerabilidade não tem efeito. O mapeamento de rede que criamos está sob nossa sessão de logon de curta duração, e não aquele criado por um usuário regular quando ele se conectou à máquina, portanto, não ficará visível.
Para superar isso, tivemos que nos aprofundar um pouco mais no código de NetrUseAdd. À medida que isso acontece, há sinalizações que podemos passar o NetrUseAdd que o direcionam para criar o mapeamento no namespace global, que afeta todos os usuários. Esses sinalizadores são encontrados até mesmo no arquivo de cabeçalho disponível LMUse.h:
Armado com os sinalizadores, nosso código agora cria com sucesso um mapeamento global, que afetará a sessão interativa, terminando nossa tentativa de exploração.
Resumo
O MS-RPC é um protocolo grande e complexo. Ele também atende a algumas das principais funcionalidades do Windows. Embora o protocolo tenha recursos de segurança que os desenvolvedores podem usar para proteger seus servidores RPC, esse é um tópico interessante para os pesquisadores de segurança precisamente porque ele contém uma vulnerabilidade que pode ter um impacto na segurança.
Apesar disso, não foi realizada muita pesquisa pública sobre o assunto. Nesta postagem do blog, abordamos um grande mecanismo de segurança no MS-RPC, os retornos de chamada de segurança, e encontramos um desvio na forma de cache de resultado de retorno de chamada. Também detalhamos nossa metodologia de pesquisa para encontrar servidores RPC vulneráveis e demonstramos algumas de nossas conclusões com descrições de vulnerabilidade.
Esperamos que esta publicação e o repositório do kit de ferramentas RPC anexopossam ajudar outras pessoas na pesquisa de servidores RPC e mecanismos de segurança.