
Ceci est un POC que j'ai écrit pour CVE-2024-6387
Qualys Security Advisory
regreSSHion: RCE dans le serveur OpenSSH, sur systèmes Linux basés sur glibc (CVE-2024-6387)
Résumé SSH-2.0-OpenSSH_3.4p1 Debian 1:3.4p1-1.woody.3 (Debian 3.0r6, de 2005)
Il suffit d'un acte de foi
-- The Interrupters, "Leap of Faith"
Note préliminaire : OpenSSH est l'un des logiciels les plus sécurisés au monde ; cette vulnérabilité est un faux pas dans une implémentation par ailleurs presque parfaite. Sa conception en profondeur et son code sont un modèle et une source d'inspiration, et nous remercions les développeurs d'OpenSSH pour leur travail exemplaire.
Nous avons découvert une vulnérabilité (une condition de course dans un gestionnaire de signal) dans le serveur OpenSSH (sshd) : si un client ne s'authentifie pas en LoginGraceTime secondes (120 par défaut, 600 dans les anciennes versions d'OpenSSH), alors le gestionnaire SIGALRM de sshd est appelé de manière asynchrone, mais ce gestionnaire de signal appelle diverses fonctions qui ne sont pas sûres pour les signaux asynchrones (par exemple, syslog()). Cette condition de course affecte sshd dans sa configuration par défaut.
En investiguant, nous avons réalisé que cette vulnérabilité est en fait une régression de CVE-2006-5051 ("Condition de course dans le gestionnaire de signal dans OpenSSH avant 4.4 permet à des attaquants distants de provoquer un déni de service (crash), et éventuellement d'exécuter du code arbitraire"), qui a été signalée en 2006 par Mark Dowd.
Cette régression a été introduite en octobre 2020 (OpenSSH 8.5p1) par le commit 752250c ("revised log infrastructure for OpenSSH"), qui a accidentellement supprimé un "#ifdef DO_LOG_SAFE_IN_SIGHAND" dans sigdie(), une fonction directement appelée par le gestionnaire SIGALRM de sshd. En d'autres termes :
OpenSSH < 4.4p1 est vulnérable à cette condition de course dans le gestionnaire de signal, s'il n'est pas patché par rétroportage contre CVE-2006-5051, ou s'il n'est pas patché contre CVE-2008-4109, qui était un correctif incorrect pour CVE-2006-5051 ;
4.4p1 <= OpenSSH < 8.5p1 n'est pas vulnérable à cette condition de course dans le gestionnaire de signal (car le "#ifdef DO_LOG_SAFE_IN_SIGHAND" qui a été ajouté à sigdie() par le patch pour CVE-2006-5051 a transformé cette fonction dangereuse en un appel sûr _exit(1)) ;
8.5p1 <= OpenSSH < 9.8p1 est à nouveau vulnérable à cette condition de course dans le gestionnaire de signal (car le "#ifdef DO_LOG_SAFE_IN_SIGHAND" a été accidentellement supprimé de sigdie()).
Cette vulnérabilité est exploitable à distance sur les systèmes Linux basés sur glibc, où syslog() elle-même appelle des fonctions non sûres pour les signaux asynchrones (par exemple, malloc() et free()) : une exécution de code à distance non authentifiée en tant que root, car elle affecte le code privilégié de sshd, qui n'est pas sandboxé et s'exécute avec tous les privilèges. Nous n'avons pas investigué d'autres libc ou systèmes d'exploitation ; mais OpenBSD n'est notablement pas vulnérable, car son gestionnaire SIGALRM appelle syslog_r(), une version plus sûre pour les signaux asynchrones de syslog() inventée par OpenBSD en 2001.
Pour exploiter cette vulnérabilité à distance (à notre connaissance, CVE-2006-5051 n'a jamais été exploitée avec succès auparavant), nous nous sommes inspirés d'un article visionnaire, "Delivering Signals for Fun and Profit", publié en 2001 par Michal Zalewski :
https://lcamtuf.coredump.cx/signals.txt
Néanmoins, nous avons immédiatement été confrontés à trois problèmes majeurs :
D'un point de vue théorique, nous devons trouver un chemin de code utile qui, s'il est interrompu au bon moment par SIGALRM, laisse sshd dans un état incohérent, et nous devons ensuite exploiter cet état incohérent à l'intérieur du gestionnaire SIGALRM.
D'un point de vue pratique, nous devons trouver un moyen d'atteindre ce chemin de code utile dans sshd, et maximiser nos chances de l'interrompre au bon moment.
D'un point de vue temporel, nous devons trouver un moyen d'augmenter encore nos chances d'interrompre ce chemin de code utile au bon moment, à distance.
Pour nous concentrer sur ces trois problèmes sans avoir à lutter immédiatement contre toutes les protections modernes des systèmes d'exploitation (en particulier ASLR et NX), nous avons décidé d'exploiter d'abord les anciennes versions d'OpenSSH, sur i386, puis, sur la base de cette expérience, les versions récentes :
D'abord, "SSH-2.0-OpenSSH_3.4p1 Debian 1:3.4p1-1.woody.3", de "debian-30r6-dvd-i386-binary-1_NONUS.iso" : c'est la première version Debian qui active la séparation des privilèges par défaut et qui est patchée contre toutes les vulnérabilités critiques de cette époque (en particulier, CVE-2003-0693 et CVE-2002-0640).
Pour exploiter cette version à distance, nous interrompons un appel à free() avec SIGALRM (dans le code d'analyse des clés publiques de sshd), laissons le tas dans un état incohérent, et exploitons cet état incohérent lors d'un autre appel à free(), dans le gestionnaire SIGALRM.
Dans nos expériences, il faut en moyenne ~10 000 tentatives pour gagner cette condition de course ; c'est-à-dire, avec 10 connexions (MaxStartups) acceptées par 600 secondes (LoginGraceTime), il faut en moyenne ~1 semaine pour obtenir un shell root distant.
Ensuite, "SSH-2.0-OpenSSH_4.2p1 Debian-7ubuntu3", de "ubuntu-6.06.1-server-i386.iso" : c'est la dernière version Ubuntu encore vulnérable à CVE-2006-5051 ("Condition de course dans le gestionnaire de signal dans OpenSSH avant 4.4").
Pour exploiter cette version à distance, nous interrompons un appel à pam_start() avec SIGALRM, laissons l'une des structures de PAM dans un état incohérent, et exploitons cet état incohérent lors d'un appel à pam_end(), dans le gestionnaire SIGALRM.
Dans nos expériences, il faut en moyenne ~10 000 tentatives pour gagner cette condition de course ; c'est-à-dire, avec 10 connexions (MaxStartups) acceptées par 120 secondes (LoginGraceTime), il faut en moyenne ~1-2 jours pour obtenir un shell root distant.
Enfin, "SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2", de "debian-12.5.0-i386-DVD-1.iso" : c'est la version stable actuelle de Debian, et elle est vulnérable à la régression de CVE-2006-5051.
Pour exploiter cette version à distance, nous interrompons un appel à malloc() avec SIGALRM (dans le code d'analyse des clés publiques de sshd), laissons le tas dans un état incohérent, et exploitons cet état incohérent lors d'un autre appel à malloc(), dans le gestionnaire SIGALRM (plus précisément, dans syslog()).
Dans nos expériences, il faut en moyenne ~10 000 tentatives pour gagner cette condition de course, donc ~3-4 heures avec 100 connexions (MaxStartups) acceptées par 120 secondes (LoginGraceTime). En fin de compte, il faut en moyenne ~6-8 heures pour obtenir un shell root distant, car nous ne pouvons deviner correctement l'adresse de la glibc que la moitié du temps (à cause de l'ASLR).
Cette recherche est toujours en cours :