Introduction aux capabilities Linux
La source de documentation la plus complète concernant les capabilities Linux est fournie par sa page de manuel : man capabilities.
Le noyau Linux distingue deux catégories de processus :
- Les processus privilégiés, c’est-à-dire lancés par l’utilisateur
root
: ces processus disposent de la totalité des droits sur le système, - Les processus non-privilégiés : Ces processus doivent obéir aux restrictions imposés par le système, comme les droits d’accès sur les fichiers, par exemple.
Cependant, cette architecture n’est pas modulaire. Par exemple, sous Unix, seul root
a accès aux ports privilégiés. Historiquement, cela permettait de savoir que l’on parle à root
et non pas à un utilisateur ayant des privilèges restreints sur la machine pour réaliser des opérations parfois sensibles d’authentification (telnet
, ftp
, ssh
, etc.). La logique étant que si on parle à un processus écoutant sur un port inférieur à 1024, alors on parle à root
. Ce modèle de sécurité avait sa pertinence dans les années 80, mais il est maintenant largement obsolète. La conséquence reste qu’un processus en écoute sur un port inférieur à 1024 devra par défaut disposer des droits complets sur le système. La compromission d’un tel processus implique la compromission totale du système.
Différentes techniques ont été mises en place pour éviter ce soucis :
Apache
doit pouvoir se binder aux ports 80 ou 443. De nombreuses distributions Linux choisissent par défaut de le démarrer par l’utilisateurroot
pour l’écoute sur ces ports http(s). Ce processus lance des processus non-privilégiés qui traiteront effectivement les requêtes, typiquement sous l’identitéwww-data
. La compromission d’un processus traitant une requête HTTP n’aboutit pas à la compromission totale du système. Plus de détails sur cette problématique [https://askubuntu.com/questions/694036/apache-as-non-root][ICI].sshd
doit également se binder au port 22 inférieur à 1024. Cependant, la problématique est complexifiée par l’authentification, qui peut nécessiter l’accès au fichier/etc/shadow
par exemple. Également, le démonsshd
doit utiliser la commandelogin
pour effectivement logguer l’utilisateur… Et seulroot
peut utiliser cette commande ! L’architecture devient du coup plus complexe. Le processussshd
privilégié écoute sur le port 22 et fork lors de la connexion d’un utilisateur un processus non-privilégié (queps
affiche avec le label[net]
) pour la gérer. Ce processus valide l’identité vis-à-vis du processus privilégié (qui typiquement a les droits de lire dans/etc/shadow
, et dans les~/.ssh/authorized_keys
des utilisateurs). Si cette identité est validée, le processus privilégié fork un second processus non privilégié mais fonctionnant sous l’identité de l’utilisateur authentifié.
Les capabilities permettent de diviser les privilèges traditionnellement associés à root
en unités distinctes. Ces capabilities peuvent être gérées de manière indépendante. Elles mettent en place un système générique pour attribuer le privilège supplémentaire dont une application a besoin pour fonctionner sans pour autant lui attribuer la totalité des droits root
. Une courte mais très intéressante introduction peut être trouvée [blog.siphos.be][ICI].
Les capabilities permettent un contrôle plus fin des droits d’administration. Il s’agit d’éviter d’avoir recours à l’utilisation du compte root
(sudo
ou setuid root
). Il suffit d’attribuer une capability donnée et limitée à un binaire pour permettre l’exécution dans un contexte (presque) non privilégié.
La liste complète des capabilities peut être obtenue via capabilities(7)
. Cependant, lors de l’utilisation de cette fonctionnalité, plutôt que d’utiliser le bit setuid root
, de très nombreux développeurs associent directement la capability CAP_SYS_ADMIN
, privilège équivalent aux droits root
. Ceci n’aboutit qu’à déplacer le problème. Outre CAP_SYS_ADMIN
, de nombreuses capabilities restent finalement équivalentes aux privilèges de root
. Cet article permet de lister ces capabilities et notamment celles qui sont équivalentes aux privilèges de root
.
Il est important de noter que les capabilities sont gérées par thread : À chaque thread est associé un ensemble de 4 drapeaux :
- Effective E : Le système vérifie si le bit est égal à 1 pour la capability considérée et autorise ou non le processus à l’utiliser. Un peu équivalent à placer un bit
suid root
mais uniquement pour la capability en question, - Permitted P : Les capabilities que le processus peut acquérir, c’est un sur-ensemble d’Effective,
- Inheritable I : Les capabilities qui seront propagées aux processus fils lors de forks,
- bset
Afin de manipuler ces capabilities, deux appels systèmes sont définis :
capset
,capget
.
Pour lister les capabilities associés au processus courant :
# grep ^Cap /proc/$$/status
grep ^Cap /proc/$$/status
CapInh: 0000000000000000
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
$ grep ^Cap /proc/$$/status
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
Pour obtenir la liste des capabilities à partir de ces valeurs :
capsh --decode=0000003fffffffff
La généralisation des capabilities aux fichiers est apparue plus tard, à partir du noyau 2.6.24 (il y a plus de 20 ans!). Il a fallu d’abord attendre que le système de fichiers supporte l’ajout d’attribut étendus au niveau des inodes. À partir de là, un processus qui lance un exécutable gagne les capabilities associés à l’exécutable lancé.
Pour lister les capabilities de l’ensemble des fichiers :
getcap -r / 2>&1 | grep -v "Failed to get capabilities of file"
Transformation des capabilities lors d’un execve()
L’algorithme suivant est utilisé par le noyau pour calculer les nouvelles capabilities du processus (cf. man capabilities
) :
* P'(ambient) = (file is privileged) ? 0 : P(ambient)
* P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & cap_bset) | P'(ambient)
* P'(effective) = F(effective) ? P'(permitted) : P'(ambient)
* P'(inheritable) = P(inheritable) [i.e., unchanged]
ce mécanisme est également décrit dans [https://stackoverflow.com/questions/1956732/is-it-possible-to-configure-linux-capabilities-per-user/17685265#17685265][cette super réponse sur stackoverflow].
Utilisation pour la commande ping
La commande ping
nécessite l’utilisation des raw sockets
, et pour cela doit avoir des privilèges supplémentaires. Suivant la distribution, l’attribution de ces privilèges pour permettre à un utilisateur non privilégié d’utiliser cette commande suit différentes stratégies :
- sous Debian, le binaire
/bin/ping
estsetuid root
, les droits 755root:root
ne permettant pas à un utilisateur quelconque de le modifier, - sous CentOS, le binaire
/usr/bin/ping
n’est passuid root
:
getcap /usr/bin/ping -> cap_net_admin,cap_net_raw+p
Sur CentOS, on peut aller plus loin et supprimer également la capability cap_net_admin
:
setcap cap_net_raw=p /usr/bin/ping
Attention au fait que les mises à jour ce ce paquet vont réecraser ces attributs (stockés sur l’inode du fichier).
Utiliastion pour wireshark
Il est possible de restreindre les privilèges associés au processus utilisant wireshark
. L’objectif consiste à permettre à un utilisateur particulier d’exécuter ce binaire en utilisant le moins de privilèges possibles. Ceci permet de restreindre l’étendue d’une compromission, plutôt que de voir l’utilisateur root
lancer ce binaire.
C’est déjà ce que propose Debian lors de l’installation de wireshark
(plus précisément du package wireshark-common
). Il est possible de reconfigurer cette disposition après installation :
dpkg-reconfigure wireshark-common
# Sous le capot :
set cap_net_raw,cap_net_admin=eip /usr/bin/dumpcap
addgroup wireshark
chown root:wireshark /usr/bin/dumpcap
chmod 750 /usr/bin/dumpcap
usermod -a wireshark $USER
Aller plus loin pour durcir OpenVPN
L’installation d’OpenVPN
nécessite au préalable l’activation des dépôts epel-release
sous CentOS (Extra Packages for Enterprise Linux) :
yum install epel-release
yum install openvpn
Par défaut, OpenVPN
est démarré par l’utilisateur root
, puisque l’outil peut avoir à créer des routes. Pour restreindre ses capabilities, il est nécessaire de savoir d’abord quelles sont celles qu’il doit utiliser.
Il n’existe pas de façon simple de lister les capabilities nécessaires à un programme pour fonctionner. La technique que j’emploie s’appuie sur systemtap
et utilise le script container_check.stp
récupéré sur github :
./container_check.stp \
-DKRETACTIVE=100 \
-c "openvpn --config /etc/openvpn/client_key.conf"
Les résultats suivants s’affichent une fois le processus openvpn
terminé (Ctrl+C éventuellement) :
executable: prob capability
ip: cap_net_admin
openvpn: cap_net_admin
executable, syscall ( capability ) : count
ip, sendmsg ( cap_net_admin ) : 3
ip, sendto ( cap_net_admin ) : 1
openvpn, ioctl ( cap_net_admin ) : 2
executable, syscall: count/jekyll/update/2025/06/29/welcome-to-jekyll.html
executable, syscall = errno: count
ip, access = ENOENT: 9
openvpn, access = ENOENT: 18
openvpn, connect = ENETUNREACH: 1
openvpn, connect = ENOENT: 1
openvpn, statfs = ENOENT: 2
openvpn, rt_sigreturn = EINTR: 1
openvpn, poll = : 1
stapio, rt_sigreturn = EINTR: 1
stapio, execve = ENOENT: 2
On voit ici que l’exécutable openvpn
nécessite la capability CAP_NET_ADMIN
, mais qu’il en est de même pour ip
.
Pour permettre l’utilisation à partir d’un utilisateur non-privilégié quelconque :
setcap cap_net_admin=pe /usr/sbin/openvpn
setcap cap_net_admin=pe /usr/sbin/ip
Configuration pour un utilisateur spécifique
Nous avons un peu affaire à un mode setuid-
like. Pour ne permettre qu’à un utilisateur ciblé d’obtenir cette capability (par exemple un administrateur réseau), la technique se résume dans le cas des distributions Debian-like à :
aptitude install libpcap-dev
setcap cap_net_admin=ie /usr/sbin/openvpn
setcap cap_net_admin=ie /usr/sbin/ip
Ensuite, au niveau du fichier /etc/security/capability.conf
:
none *
cap_net_admin chibollo
Enfin, chargement du module PAM imposé au login des utilisateurs pour la prise en compte du fichier /etc/security/capability.conf
au niveau du fichier /etc/pam.d/login
ajouter :
auth required pam_cap.so
Dans le cas CentOS, la présence de SELinux
complexifie légèrement la configuration.