Fragmenter le pouvoir de root
Les capabilities Linux appliquent le principe de moindre privilège à un endroit où il faisait historiquement défaut : l’opposition binaire entre un processus root, omnipotent, et un processus non privilégié, dépourvu de tout pouvoir spécial. Les capabilities divisent ce pouvoir omnipotent en unités distinctes et plus petites, permettant d’accorder à un processus le seul sous-ensemble de privilèges dont il a réellement besoin.
Les cinq ensembles
Le noyau gère les capabilities via cinq ensembles distincts par processus, complétés par des attributs sur les fichiers binaires eux-mêmes.
Permitted (CapPrm) fixe le plafond maximal de privilèges qu’un processus peut posséder — la limite supérieure de ce qu’il peut élever dans son ensemble Effective. Effective (CapEff) est l’ensemble réellement consulté par le noyau lors des vérifications de droits, par exemple à l’ouverture d’une socket brute. Inheritable (CapInh) détermine quelles capabilities se transmettent du parent à l’enfant lors d’une exécution. Bounding (CapBnd) agit comme une barrière ultime, plafonnant ce qu’un processus pourra jamais acquérir, même en exécutant un binaire privilégié. Ambient (CapAmb), introduit plus récemment, permet à un programme non-root de conserver certains privilèges après un execve — un cas auparavant difficile à traiter sans recourir au bit setuid.
Ces informations résident dans les attributs étendus des fichiers sur disque, et sont maintenues en mémoire par thread, consultables via /proc/<pid>/status.
Manipuler les capabilities
getcap et setcap lisent ou attribuent des privilèges à des binaires spécifiques ; capsh décode les valeurs hexadécimales ou lance des shells à privilèges restreints pour test ; getpcaps affiche les capabilities d’un processus en cours d’exécution.
Un TP concret : ping et tcpdump
Rien ne vaut un exemple manipulé directement pour saisir l’intérêt réel des capabilities.
ping n’a besoin ni de root, ni de setuid intégral pour envoyer des paquets ICMP : il lui suffit de la capability cap_net_raw, comme le révèle getcap :
$ getcap /usr/bin/ping
/usr/bin/ping cap_net_raw=eptcpdump, lui, échoue sans privilège suffisant :
$ tcpdump
tcpdump: ens2: You don't have permission to perform this capture on that device
(socket: Operation not permitted)Le lancer via sudo fonctionne, mais accorde bien plus que nécessaire — l’intégralité des privilèges root, pour une seule opération de capture réseau. L’option -Z de tcpdump illustre une alternative plus disciplinée : après avoir ouvert le périphérique de capture, le processus renonce à ses privilèges root en basculant vers un utilisateur non privilégié. strace confirme ce renoncement explicite :
setgid(1000) = 0
setuid(1000) = 0
Pour accorder à tous les utilisateurs la possibilité d’écouter le réseau sans passer par sudo, deux approches coexistent : ajouter les utilisateurs concernés à un groupe dédié aux permissions requises, ou attribuer directement la capability nécessaire au binaire :
setcap cap_net_raw,cap_net_admin=eip /usr/sbin/tcpdumpLa seconde approche, à granularité fixée sur le binaire lui-même plutôt que sur l’utilisateur, s’inscrit mieux dans une logique de moindre privilège durable.
Ce que révèle man capabilities
Toutes les capabilities ne se valent pas en termes de risque. CAP_SYS_ADMIN regroupe un ensemble de pouvoirs si vaste qu’elle mérite, dans la documentation même du noyau, une mise en garde explicite :
Notes to kernel developers
It can plausibly be called “the new root”, since on the one hand, it confers a wide range of powers, and on the other hand, its broad scope means that this is the capability that is required by many privileged programs. Don’t make the problem worse.
CAP_SYS_ADMIN symbolise, en somme, le serpent qui se mord la queue : elle contrevient directement à la granularité et à la modularité recherchées par le mécanisme même des capabilities, en reconstituant sous un autre nom l’omnipotence monolithique de root. CAP_NET_ADMIN, dans une moindre mesure mais tout aussi significative, agrège de nombreuses opérations réseau critiques — configuration d’interfaces, routage, filtrage, manipulation de namespaces réseau —, ce qui en fait une capability à haut enjeu.
Le gain n'est réel que si le privilège reste strictement nécessaire
Une mauvaise configuration mène directement à une escalade de privilèges. Accorder
cap_setuidà un interpréteur comme Python, par exemple, permet à n’importe quel utilisateur de forcer l’UID du processus à 0 — obtenant un shell root sans jamais toucher au mot de passe root lui-même1.
Applications et outillage
libcap-ng offre une gestion fine des capabilities au sein même d’un programme, dans un esprit similaire à seccomp : réduire la surface d’exposition en abandonnant volontairement (drop) les capabilities non nécessaires, plutôt que de compter sur une configuration externe. Dans les conteneurs, Docker restreint fortement les capabilities par défaut ; la bonne pratique consiste à tout supprimer (--cap-drop all) puis à n’ajouter que le strict nécessaire — cap_net_bind_service, par exemple, pour qu’un serveur web puisse se lier au port 80 sans être root.
Le problème du député confus
Une seconde classe de vulnérabilité, distincte des capabilities mais tout aussi fondamentale, mérite d’être posée ici : le problème du député confus (confused deputy problem).
La vulnérabilité
Un sujet mandaté pour agir au nom d’un client utilise ses propres droits au détriment de ceux du client. Par exemple, le client demande au sujet de lire un document ; les permissions du sujet le permettent, mais celles du client, non — et pourtant l’opération aboutit, parce que c’est l’autorité du sujet, et non celle du client, qui a été consultée.
La racine du problème
Ce piège survient lorsque la sécurité est gérée par une partie du système externe au logiciel lui-même — typiquement un moniteur de référence, dont la tâche est de décider quel sujet peut utiliser quel autre sujet. Le problème fondamental est que le programme intermédiaire ne peut pas distinguer quelle autorité il doit mobiliser pour une tâche donnée : la sienne, ou celle de son appelant.
La solution : les capacités plutôt que les noms
L’utilisation de capacités (au sens large du terme, pas seulement les capabilities Linux) plutôt que de simples noms de fichiers résout structurellement le problème. Une capacité est un jeton qui désigne à la fois un objet et le droit d’agir sur lui — identification et autorisation fusionnées en une seule structure, plutôt que séparées en un nom (chaîne ASCII) d’un côté et une vérification de permission de l’autre. Si l’utilisateur ne détient pas la capacité correspondant à un fichier, il ne peut tout simplement pas la transmettre au programme intermédiaire, et l’opération échoue naturellement — sans que ce dernier n’ait à effectuer de vérification complexe pour distinguer sa propre autorité de celle de son client.
Résumé
Le problème du député confus survient quand une autorité est transmise de manière invisible à un programme incapable de différencier ses propres privilèges de ceux de son utilisateur. Les capacités, en fusionnant désignation et droit d’accès, referment structurellement cette ambiguïté.