
Analyse d'une vulnérabilité logique dans le client SSH macOS entraînant l'exposition de la phrase de passe du client à un attaquant local
Ce document présente à la fois une analyse d’une vulnérabilité logique identifiée dans le binaire ssh sous macOS (ma première vulnérabilité logicielle !) et une analyse du correctif montrant comment Apple a corrigé la vulnérabilité. J’ai signalé le problème à Apple via le programme Apple Security Bounty fin 2022, qui a ensuite été corrigé dans macOS Ventura 13.5 et a donné lieu à CVE-2023-42829 (🎉). Le problème conduit à ce que les phrases de passe SSH enregistrées dans le trousseau « Connexion » local de l’utilisateur macOS (dans le groupe d’accès com.apple.ssh.passphrases) soient exposées en texte clair à un attaquant local.
Avertissement :
Ce rapport est fourni à des fins éducatives uniquement, les processus de divulgation responsable ayant été suivis. L’analyse est fournie en l’état, et toute publication ou utilisation ultérieure de ces informations doit respecter les directives de divulgation responsable.
Flux haut niveau corrigé
Flux haut niveau non corrigé
com.apple.private.security.clear-library-validation| Matériel | Logiciel OS |
|---|---|
| MacBook Pro M1 2021 | /usr/bin/ssh sous macOS Ventura build 13.0.1 (22A400) |
La preuve de concept suivante illustre la simplicité relative d’exploitation de la vulnérabilité, en passant une bibliothèque dynamique au paramètre -I du binaire ssh :
jamesd@local build % ssh -I /Users/jamesd/rome.dylib [email protected]
SSH Private Key Passphrase -> 'SuperSecret!!@@$'
Parlons de la découverte et des raisons de ce problème !
Il était une fois où j’utilisais le binaire ssh pour une action anodine et j’ai fait une faute de frappe en utilisant le paramètre -i (utilisé pour passer un fichier d’identité SSH) avec le paramètre -I et j’ai obtenu la sortie standard suivante :
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), ...
...
Après avoir vérifié les entitlements du binaire ssh...
jamesd@local build % ldid -e /usr/bin/ssh # vider les entitlements du binaire /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>
...mon intérêt a été piqué : le binaire possède des entitlements pour lire un groupe d’accès protégé du trousseau (com.apple.ssh.passphrases), a l’entitlement com.apple.private.security.clear-library-validation, et ssh tente d’appeler dlopen() sur ma clé privée ?
Il s’avère que ssh prend en charge l’authentification à un système distant via un mécanisme appelé pkcs11, une norme pour les opérations cryptographiques sur les modules de sécurité matériels (HSM) (https://docs.aws.amazon.com/cloudhsm/latest/userguide/pkcs11-library.html).
Nous n’avons pas besoin de nous attarder sur les détails de pkcs11 pour ce compte rendu, si ce n’est que le client fournit une bibliothèque pkcs11 (bibliothèque dynamique) à ssh -I.
com.apple.private.security.clear-library-validationBien que conceptuellement équivalent, com.apple.private.security.clear-library-validation diffère de l’entitlement équivalent précédent (com.apple.security.cs.disable-library-validation) car com.apple.private.security.clear-library-validation nécessite d’appeler l’appel système csops() en passant CS_OPS_CLEAR_LV pour contrôler l’activation/désactivation de la validation de bibliothèque, afin de mieux contrôler l’intégrité du processus à l’exécution (contrairement à com.apple.private.security.clear-library-validation qui permettrait probablement de charger n’importe quelle bibliothèque sans contrôle à l’exécution). (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/)
Comme csops(CS_OPS_CLEAR_LV) est appelé avant dlopen() de notre bibliothèque, celle-ci se charge simplement et le constructeur s’exécute, ce qui nous permet de fournir une bibliothèque dynamique malveillante se faisant passer pour une bibliothèque pkcs11 et d’exécuter du code dans le contexte de /usr/bin/ssh et d’utiliser l’entitlement keychain-access-groups.
Peut-être que le correctif impliquait des vérifications effectuées sur le binaire avant d’appeler csops() pour s’assurer qu’il possède une identité de signature de confiance spécifique ?
Non !
Après la publication du correctif (22G74), j’ai comparé l’implémentation non sécurisée de pkcs11_add_provider() (la méthode dans laquelle l’appel à dlopen() est effectué) et elle semblait identique à la version corrigée – mais il manque un entitlement dans /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>
Mais Apple n’a sûrement pas supprimé la prise en charge de pkcs11 de SSH ? J’ai essayé de ré-exploiter la vulnérabilité à l’aide de mon PoC et j’ai constaté que, bien que ma bibliothèque soit chargée, elle ne pouvait plus lire le groupe d’accès du trousseau et semblait s’exécuter dans le contexte de /usr/libexec/ssh-apple-pkcs11 (un binaire que je n’avais jamais vu auparavant) qui possède les entitlements suivants :
<?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>
Un commentaire dans l’appel système csops() pour CS_OPS_CLEAR_LV (https://github.com/apple-oss-distributions/xnu/blob/main/bsd/kern/kern_proc.c) mentionne le ré-exécution dans un binaire sans validation de bibliothèque comme alternative à CS_OPS_CLEAR_LV, plutôt que d’être utilisé en combinaison. Alors pourquoi la routine logiquement défectueuse est-elle toujours présente dans pkcs11_add_provider() de /usr/bin/ssh s’il existe désormais un binaire auxiliaire supplémentaire ? Et pourquoi l’implémentation semble inchangée dans le correctif ?
Eh bien, il s’avère que le correctif n’a pas consisté à ajouter un binaire auxiliaire, mais à distribuer deux binaires ssh dans macOS : /usr/bin/ssh et /usr/libexec/ssh-apple-pkcs11 – les deux sont identiques à l’exception de leurs entitlements (j’ai utilisé Diaphora pour le vérifier) :
Après avoir effectué une analyse dynamique sur le binaire ssh corrigé, une vérification supplémentaire a été ajoutée dans la routine (plutôt volumineuse) start() de ssh, utilisée pour déterminer, lors de l’utilisation des fonctionnalités pkcs11, si le binaire s’exécute dans le contexte de /usr/bin/ssh ou de /usr/libexec/ssh-apple-pkcs11 :
Dans le bloc vert, on observe un appel à SecTaskCopyValueForEntitlement() (la valeur passée est "com.apple.private.security.clear-library-validation") qui est ensuite (dans le bloc orange) évaluée et provoque un appel conditionnel à la routine de ré-exécution si l’entitlement "com.apple.private.security.clear-library-validation" n’est pas présent pour la tâche/le processus courant (bloc rouge).
Cela fait que le binaire ssh-apple-pkcs11, moins privilégié, est utilisé lors du chargement de la bibliothèque pkcs11 fournie par l’utilisateur (désactivant ainsi les fonctionnalités liées au trousseau de ssh), et que ssh est utilisé lorsque des bibliothèques non fiables ne doivent pas être chargées (activant les fonctionnalités liées au trousseau de ssh).
Nous pouvons également valider cela en déboguant le binaire ssh corrigé.
Si nous déboguons le binaire ssh corrigé (distribué dans macOS 22G74) et définissons des points d’arrêt sur execv(), nous pouvons effectivement repérer un appel à execv() lorsque nous fournissons le paramètre -I, ce qui ne se produit pas lors du lancement de ssh sans utiliser les fonctionnalités pkcs11 :
J’ai signalé le problème à Apple via le programme Apple Security Bounty fin 2022, et il a été corrigé dans macOS Ventura 13.5 et a donné lieu à CVE-2023-42829 (🎉).
Ce compte rendu est une publication indépendante et n’a été ni autorisé, ni parrainé, ni approuvé par Apple Inc. macOS, iOS et iWork sont des marques d’Apple Inc.