
Análise de uma vulnerabilidade lógica no cliente SSH do macOS que leva à exposição da frase secreta do cliente a um atacante local
Este documento apresenta tanto uma análise de uma vulnerabilidade lógica identificada no binário ssh no macOS (minha primeira vulnerabilidade de software!) quanto uma análise de patch demonstrando como a Apple corrigiu a vulnerabilidade. Reportei o problema à Apple através do programa Apple Security Bounty no final de 2022, que posteriormente foi corrigido no macOS Ventura 13.5 e resultou no CVE-2023-42829 (🎉). O problema leva a que frases secretas SSH salvas no Keychain 'Login' local do usuário no macOS (no grupo de acesso com.apple.ssh.passphrases) sejam expostas em texto simples a um atacante local.
Aviso:
Este relatório é fornecido apenas para fins educacionais, tendo sido seguidos os processos de divulgação responsável. A análise é oferecida como está, e qualquer publicação ou uso posterior destas informações deve aderir às diretrizes de divulgação responsável.
Fluxo de alto nível corrigido
Fluxo de alto nível não corrigido
| Hardware | Software do SO |
|---|---|
| MacBook Pro M1 2021 | /usr/bin/ssh @ macOS Ventura build 13.0.1 (22A400) |
A seguinte prova de conceito ilustra a exploração bastante simples da vulnerabilidade, passando uma biblioteca dinâmica para a flag -I do binário ssh:
jamesd@local build % ssh -I /Users/jamesd/rome.dylib [email protected]
SSH Private Key Passphrase -> 'SuperSecret!!@@$'
Vamos discutir como isso foi descoberto e por que aconteceu!
Era uma vez, eu estava usando o binário ssh para algo inofensivo e digitei errado a flag -i (usada para passar um arquivo de identidade SSH) com a flag -I, e me deparei com a seguinte saída padrão:
jamesd@local build % ssh -I /Users/jamesd/.ssh/id_rsa something@somewhere
dlopen /Users/jamesd/.ssh/id_rsa failed: dlopen(/Users/jamesd/.ssh/id_rsa, 0x0002): tried: '/Users/jamesd/.ssh/id_rsa' (not a mach-o file), ...
...
Depois de verificar os entitlements do binário ssh...
jamesd@local build % ldid -e /usr/bin/ssh # despejar os entitlements do binário /usr/bin/ssh
<key>com.apple.private.security.clear-library-validation</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>com.apple.ssh.passphrases</string>
</array>
...meu interesse foi despertado – o binário tem entitlements para ler de um grupo de acesso protegido do keychain (com.apple.ssh.passphrases), possui o entitlement com.apple.private.security.clear-library-validation, e o ssh está tentando fazer dlopen() da minha chave privada?
Acontece que o ssh suporta autenticação em um sistema remoto através de algo chamado pkcs11, um padrão para operações criptográficas em módulos de segurança de hardware (HSMs) (https://docs.aws.amazon.com/cloudhsm/latest/userguide/pkcs11-library.html).
Não precisamos nos preocupar com os detalhes específicos do pkcs11 para os fins deste artigo, além do fato de que o cliente fornece uma biblioteca pkcs11 (biblioteca dinâmica) para ssh -I.
com.apple.private.security.clear-library-validationEmbora conceitualmente equivalente, o com.apple.private.security.clear-library-validation difere do entitlement equivalente anterior (com.apple.security.cs.disable-library-validation), pois o com.apple.private.security.clear-library-validation exige que você faça a chamada de sistema csops() passando CS_OPS_CLEAR_LV para controlar a ativação/desativação da validação de bibliotecas, mantendo maior controle sobre a integridade do processo em tempo de execução (em comparação com com.apple.private.security.clear-library-validation, que presumivelmente permitiria carregar qualquer biblioteca sem controle em tempo de execução). (https://github.com/apple-oss-distributions/xnu/blob/main/bsd/kern/kern_proc.c) (https://theevilbit.github.io/posts/com.apple.private.security.clear-library-validation/)

Como csops(CS_OPS_CLEAR_LV) é chamado antes de fazer dlopen() da nossa biblioteca, nossa biblioteca simplesmente carrega e o construtor é executado, permitindo fornecer uma biblioteca dinâmica maliciosa disfarçada de biblioteca pkcs11 e executar código a partir do contexto de /usr/bin/ssh e utilizar o entitlement keychain-access-groups.
Talvez o patch envolvesse verificações realizadas no binário antes de chamar csops() para verificar identidades de assinatura confiáveis específicas?
Não!
Após o lançamento do patch (22G74), comparei a implementação insegura de pkcs11_add_provider() (o método no qual a chamada para dlopen() é feita) e parecia idêntica à versão do patch – mas há um entitlement faltando no /usr/bin/ssh?
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>keychain-access-groups</key>
<array>
<string>com.apple.ssh.passphrases</string>
</array>
</dict>
</plist>
Mas certamente a Apple não removeu o suporte a pkcs11 do SSH? Tentei re-explorar a vulnerabilidade usando meu PoC e descobri que, embora minha biblioteca fosse carregada, ela não conseguia mais ler do Grupo de Acesso do Keychain e parecia estar executando no contexto de /usr/libexec/ssh-apple-pkcs11 (um binário que eu não tinha visto antes) que possui os seguintes entitlements:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.private.security.clear-library-validation</key>
<true/>
</dict>
</plist>
Um comentário na chamada de sistema csops() para CS_OPS_CLEAR_LV (https://github.com/apple-oss-distributions/xnu/blob/main/bsd/kern/kern_proc.c) menciona re-executar em um binário sem validação de biblioteca como alternativa ao CS_OPS_CLEAR_LV, em vez de ser usado em combinação. Então por que a rotina logicamente defeituosa ainda está presente em pkcs11_add_provider() no /usr/bin/ssh se agora há um binário auxiliar adicional? E por que a implementação parece inalterada no patch?
Bem, acontece que o patch não foi adicionar um binário auxiliar, e envolveu a distribuição de dois binários ssh no macOS: /usr/bin/ssh e /usr/libexec/ssh-apple-pkcs11 – ambos são idênticos exceto por seus entitlements (usei o Diaphora para validar isso):

Após realizar uma análise dinâmica no binário ssh corrigido, uma verificação adicional foi adicionada na rotina (bastante grande) start() do ssh, que é usada para, ao usar recursos pkcs11, deduzir se o binário está executando no contexto de /usr/bin/ssh ou /usr/libexec/ssh-apple-pkcs11:

No bloco verde, podemos observar uma chamada para SecTaskCopyValueForEntitlement() (o valor passado é "com.apple.private.security.clear-library-validation") que é então (no bloco laranja) avaliada e causa uma chamada condicional para a rotina de re-execução se o entitlement "com.apple.private.security.clear-library-validation" não estiver presente para a tarefa/processo atual (bloco vermelho).
Isso resulta no binário menos privilegiado ssh-apple-pkcs11 ser usado ao carregar a biblioteca pkcs11 fornecida pelo usuário (efetivamente desabilitando os recursos relacionados ao keychain do ssh), e o ssh sendo utilizado onde bibliotecas não confiáveis não devem ser carregadas (habilitando os recursos relacionados ao keychain do ssh).
Também podemos validar que este é o caso através da depuração do ssh corrigido.
Se depurarmos o binário ssh corrigido (distribuído no macOS 22G74) e definirmos pontos de interrupção em execv(), podemos de fato detectar uma chamada para execv() ao fornecer a flag -I, que não ocorre ao iniciar o ssh sem usar recursos pkcs11:

Reportei o problema à Apple através do programa Apple Security Bounty no final de 2022, e ele foi posteriormente corrigido no macOS Ventura 13.5 e resultou no CVE-2023-42829 (🎉).
Este artigo é uma publicação independente e não foi autorizado, patrocinado ou aprovado de outra forma pela Apple Inc. macOS, iOS e iWork são marcas registradas da Apple Inc.