Aller au contenu
Administration Linux medium

Comprendre les Linux Capabilities

21 min de lecture

Une capability est une tranche des pouvoirs de root : au lieu de donner tout root à un processus, le noyau lui accorde exactement le privilège dont il a besoin. Un serveur web qui doit écouter sur le port 80 reçoit CAP_NET_BIND_SERVICE et rien d'autre. S'il est compromis, l'attaquant hérite du droit d'ouvrir un port privilégié, pas du droit de lire /etc/shadow ni de charger un module noyau.

Cette page vous montre comment lire les capabilities d'un processus déjà en cours, comment en poser une sur un exécutable, et comment faire tourner Nginx sur le port 80 sous un compte non privilégié. Elle sert aussi de socle aux namespaces et à la sécurité des conteneurs.

  • Lire les cinq ensembles de capabilities d'un processus via /proc et getpcaps.
  • Décoder les masques hexadécimaux affichés par le noyau avec capsh.
  • Poser et retirer une file capability sur un binaire avec setcap.
  • Identifier les capabilities dangereuses à ne jamais accorder à la légère.
  • Configurer un service systemd non privilégié qui écoute sur un port privilégié.

Historiquement, un processus Linux était soit root (tout permis), soit un utilisateur normal (rien de privilégié). Ce modèle binaire posait un problème simple : un serveur web n'a besoin que d'ouvrir le port 80, mais pour y arriver il devait démarrer en root, donc avec le droit de tout faire, y compris ce dont il n'aurait jamais l'usage.

Les capabilities découpent ce bloc monolithique en une quarantaine de privilèges indépendants. Sur la machine qui a servi à écrire cette page, le noyau en expose 41, de CAP_CHOWN à CAP_CHECKPOINT_RESTORE. Chacune correspond à une vérification précise dans le noyau : CAP_NET_BIND_SERVICE autorise à lier une socket à un port inférieur à 1024, CAP_SYS_TIME à modifier l'horloge système, CAP_SYS_MODULE à charger un module noyau.

Le bénéfice est directement mesurable en surface d'attaque. Un processus qui ne détient que CAP_NET_BIND_SERVICE et qui se fait compromettre ne donne à l'attaquant qu'un seul pouvoir supplémentaire : ouvrir un port bas. Le même processus lancé en root lui offrirait les 41.

Premier usage : voir les capabilities déjà posées

Section intitulée « Premier usage : voir les capabilities déjà posées »

Avant de poser quoi que ce soit, regardez ce que votre système utilise déjà. La commande getcap lit les capabilities attachées à un fichier exécutable, et l'option -r parcourt une arborescence en récursif :

Fenêtre de terminal
getcap -r /usr/bin /usr/sbin 2>/dev/null

Sortie réelle sur un poste Ubuntu :

/usr/bin/mtr-packet cap_net_raw=ep

Un seul binaire porte une capability, et c'est cohérent : mtr a besoin de forger des paquets réseau bruts, ce qu'autorise CAP_NET_RAW. Sans capability, il faudrait le rendre setuid root, ce qui lui donnerait bien plus que nécessaire. C'est exactement l'usage pour lequel les capabilities ont été conçues. La redirection 2>/dev/null masque les erreurs de permission sur les répertoires que vous ne pouvez pas lire ; elle ne cache aucun résultat utile.

Retenez ce réflexe : getcap -r / 2>/dev/null sur un serveur inconnu vous dit en une commande quels binaires sont privilégiés sans être setuid. C'est un point de contrôle d'audit systématique.

C'est le point qui bloque la plupart des lecteurs, parce que beaucoup de documentations n'en présentent que trois. Un processus Linux porte en réalité cinq ensembles de capabilities, et vous devez les connaître tous les cinq pour lire la sortie que le noyau vous montre.

Regardez la sortie brute pour le processus systemd (PID 1), qui tourne en root :

Fenêtre de terminal
grep -E '^Cap' /proc/1/status
CapInh: 0000000000000000
CapPrm: 000001ffffffffff
CapEff: 000001ffffffffff
CapBnd: 000001ffffffffff
CapAmb: 0000000000000000

Chaque ligne est un masque de bits en hexadécimal, un bit par capability. Voici ce que chacune désigne :

Ligne /procEnsembleCe qu'il signifie
CapPrmPermittedLe plafond de ce que le processus peut activer. Une capability absente d'ici est définitivement hors de portée.
CapEffEffectiveCe que le noyau vérifie maintenant. C'est l'ensemble réellement utilisé pour les contrôles de permission.
CapInhInheritableCe qui survit à un execve(), mais uniquement en combinaison avec les capabilities du fichier exécuté. Peu utilisé en pratique.
CapBndBounding setLe plafond absolu : une capability retirée d'ici ne pourra plus jamais être regagnée, ni par le processus, ni par ses descendants.
CapAmbAmbient setLes capabilities préservées à travers l'exécution d'un programme non privilégié, sans file capability sur le binaire.

La lecture devient immédiate. systemd a tout en Permitted et Effective : c'est root, il peut tout. Son bounding set est complet, donc il peut transmettre n'importe quelle capability à ses enfants. Son ambient set est vide, ce qui est normal pour un processus déjà privilégié.

Comparez avec un shell utilisateur ordinaire :

Fenêtre de terminal
grep -E '^Cap' /proc/self/status
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 000001ffffffffff
CapAmb: 0000000000000000

Tout est à zéro sauf le bounding set. C'est la signature d'un processus non privilégié : il ne détient aucune capability, mais le plafond reste haut, ce qui signifie qu'un binaire setuid ou porteur d'une file capability pourrait encore lui en octroyer. Un durcissement consiste justement à rabaisser ce bounding set.

Les masques hexadécimaux sont illisibles tels quels. L'outil capsh les traduit en noms de capabilities, ce qui est indispensable dès que vous auditez une sortie /proc :

Fenêtre de terminal
capsh --decode=0x0000000000002000
0x0000000000002000=cap_net_raw

Le masque complet 0x000001ffffffffff décode les 41 capabilities du noyau. Plus rapide encore, getpcaps interroge directement un PID et affiche le résultat en notation textuelle :

Fenêtre de terminal
getpcaps 1
1: =ep

La notation =ep se lit : toutes les capabilities (=) sont présentes en Effective (e) et en Permitted (p). C'est la forme compacte de ce que /proc/1/status affichait en hexadécimal.

Une file capability est une capability stockée dans un attribut étendu du fichier (security.capability), et non dans le processus. Quand le noyau exécute ce fichier, il en tient compte pour construire les ensembles du nouveau processus. C'est ce qui permet à mtr-packet d'obtenir CAP_NET_RAW sans être setuid.

La commande setcap pose cette capability :

Fenêtre de terminal
sudo setcap cap_net_bind_service=+ep /usr/local/bin/mon-service

Le suffixe +ep combine deux drapeaux. Le p (permitted) place la capability dans l'ensemble Permitted du processus au moment de l'execve(). Le e (effective) est un bit unique qui demande au noyau de lever immédiatement cette capability en Effective, sans que le programme ait à la réclamer lui-même. Sans le e, un binaire non conscient des capabilities démarrerait avec la capability disponible mais inactive.

Vérifiez toujours après avoir posé, avec getcap :

Fenêtre de terminal
getcap /usr/local/bin/mon-service

Pour retirer l'attribut, utilisez -r :

Fenêtre de terminal
sudo setcap -r /usr/local/bin/mon-service

Attention à la nuance : d'après setcap(8), poser un ensemble vide n'est pas équivalent à supprimer l'attribut. Un fichier avec un attribut vide reste un fichier « à capabilities » du point de vue du noyau. Utilisez -r pour un nettoyage franc.

Le premier piège est le plus grave, et il est fréquemment reproduit tel quel dans des tutoriels.

Le second piège est silencieux, et c'est ce qui le rend redoutable. Les file capabilities vivent dans un attribut étendu de l'inode. Quand apt ou dnf met à jour le paquet, il remplace le fichier par un nouvel inode issu de l'archive, et cette archive ne contient pas votre attribut. Votre capability est perdue sans le moindre avertissement. Le service repart en erreur au prochain redémarrage, souvent des semaines plus tard, avec un Permission denied sur le bind que personne ne relie à la mise à jour.

C'est l'argument décisif en faveur de AmbientCapabilities= : la déclaration vit dans l'unité systemd, un fichier de configuration que les mises à jour de paquet ne remplacent pas. Elle est rejouée à chaque démarrage, donc immunisée contre ce piège.

Objectif : faire écouter Nginx sur le port 80 en tournant sous un compte de service non privilégié, au lieu du montage classique où le master tourne en root.

Deux détails de l'unité Nginx empaquetée par Debian et Ubuntu conditionnent toute la manipulation. Elle est déclarée Type=forking avec un PIDFile=/run/nginx.pid, et son ExecStart vaut /usr/sbin/nginx -g 'daemon on; master_process on;'. Autrement dit, Nginx se détache en arrière-plan et systemd le suit par son fichier PID. Dès que vous passez à daemon off; pour le garder au premier plan, ce contrat est rompu et le Type= doit changer.

  1. Créer le compte de service dédié, sans shell ni répertoire personnel.

    Fenêtre de terminal
    sudo useradd --system --no-create-home --shell /usr/sbin/nologin nginx-svc
  2. Accorder la capability via systemd, sans toucher au binaire.

    AmbientCapabilities= accorde CAP_NET_BIND_SERVICE au processus au démarrage, même s'il tourne sous un compte non privilégié. Le manuel systemd.exec(5) est clair sur son usage : « Ambient capability sets are useful if you want to execute a process as a non-privileged user but still want to give it some capabilities ». Aucun setcap n'est nécessaire, donc rien à perdre lors des mises à jour du paquet Nginx.

    CapabilityBoundingSet= abaisse en prime le plafond du service : même compromis, il ne pourra jamais réclamer autre chose.

  3. Écrire la surcharge systemd avec sudo systemctl edit nginx.service.

    [Service]
    Type=simple
    PIDFile=
    User=nginx-svc
    Group=nginx-svc
    RuntimeDirectory=nginx
    RuntimeDirectoryMode=0750
    AmbientCapabilities=CAP_NET_BIND_SERVICE
    CapabilityBoundingSet=CAP_NET_BIND_SERVICE
    ExecStartPre=
    ExecStart=
    ExecStart=/usr/sbin/nginx -g 'daemon off; master_process on;'
    ExecReload=
    ExecReload=/bin/kill -s HUP $MAINPID

    Les lignes ExecStart= et ExecReload= vides ne sont pas une coquille : elles sont indispensables. systemd.service(5) impose que « Unless Type= is oneshot, exactly one command must be given ». Sans la ligne vide qui réinitialise la liste, votre ExecStart s'ajoute à celui du paquet et systemd refuse l'unité. Le passage à Type=simple découle de daemon off; : Nginx reste au premier plan, il ne fork plus, donc Type=forking ferait attendre systemd indéfiniment. PIDFile= vide supprime la référence devenue inutile.

  4. Adapter /etc/nginx/nginx.conf, qui reste édité par root.

    Deux directives posent problème sous un compte non privilégié. Le fichier PID pointe par défaut sur /run/nginx.pid, dans un /run appartenant à root : le compte de service ne peut pas y écrire. Redirigez-le vers le répertoire que RuntimeDirectory= crée pour vous, avec les bons droits :

    pid /run/nginx/nginx.pid;

    Ensuite, commentez la directive user. Elle ne sert qu'à un master root qui veut faire tourner ses workers sous un autre compte. Un master déjà non privilégié ne peut pas changer d'identité et Nginx émettra un avertissement à chaque démarrage.

  5. Donner au compte de service uniquement ce qu'il doit écrire, à savoir les journaux et les répertoires temporaires.

    Fenêtre de terminal
    sudo chown -R nginx-svc:nginx-svc /var/log/nginx /var/lib/nginx

    /var/lib/nginx contient les tampons de requêtes (body, proxy) sur Debian et Ubuntu : sans droit d'écriture, Nginx échoue dès la première requête volumineuse. /run/nginx est créé automatiquement au bon propriétaire par RuntimeDirectory=, vous n'avez rien à faire.

  6. Laisser la configuration à root, en lecture seule pour le service.

    Fenêtre de terminal
    sudo chown -R root:root /etc/nginx
    sudo chmod -R go-w /etc/nginx

    Si vous servez du TLS, la clé privée fait exception : le master non privilégié doit pouvoir la lire. Donnez-la au groupe du service, et à lui seul, plutôt que de la rendre lisible par tous.

    Fenêtre de terminal
    sudo chown root:nginx-svc /etc/ssl/private/mon-site.key
    sudo chmod 0640 /etc/ssl/private/mon-site.key
  7. Recharger, démarrer et vérifier que le service écoute bien sans être root.

    Fenêtre de terminal
    sudo systemctl daemon-reload
    sudo systemctl start nginx
    ss -tulpn | grep ':80'
    getpcaps "$(systemctl show -p MainPID --value nginx)"

    La sortie de getpcaps doit montrer cap_net_bind_service et rien d'autre. Si elle affiche =ep, le service tourne encore en root et la surcharge n'a pas été prise en compte.

Une erreur classique mérite d'être écartée explicitement : ne faites pas chmod 750 /usr/sbin/nginx. Le binaire appartient à root:root ; en mode 750, seuls root et le groupe root conservent le droit de l'exécuter. Votre compte nginx-svc n'est ni l'un ni l'autre : il perd le droit d'exécution et le service ne démarre plus du tout. Ce durcissement apparent casse précisément ce qu'on cherchait à construire. Laissez le binaire aux droits 0755 du paquet et pilotez l'accès par le compte de service et le bounding set.

Une quarantaine de capabilities existent, mais une poignée revient en permanence en administration et en sécurité des conteneurs. Le tableau ci-dessous les classe par usage, avec un repère pour les plus sensibles.

CapabilityCe qu'elle autoriseUsage typique
CAP_NET_BIND_SERVICELier une socket à un port inférieur à 1024.Serveur web sur le port 80 ou 443.
CAP_NET_RAWOuvrir des sockets brutes et forger des paquets.ping, mtr, tcpdump.
CAP_CHOWNChanger le propriétaire d'un fichier.Outils d'administration de systèmes de fichiers.
CAP_SYS_TIMEModifier l'horloge système.Démons de synchronisation de temps.
CAP_SYS_PTRACEAttacher un débogueur à un autre processus.strace, gdb. Sensible : donne accès à la mémoire d'autrui.
CAP_DAC_OVERRIDEIgnorer les permissions classiques des fichiers.Dangereuse : équivaut à lire et écrire n'importe quel fichier.
CAP_SYS_ADMINMontage, namespaces, et des dizaines d'opérations.À éviter : fourre-tout si large qu'elle vaut quasiment root.

Trois d'entre elles méritent une vigilance particulière. CAP_SYS_ADMIN est devenue au fil des versions du noyau un fourre-tout qui couvre tant d'opérations que l'accorder revient pratiquement à donner root ; c'est pour cette raison qu'elle est régulièrement à l'origine d'évasions de conteneurs. CAP_DAC_OVERRIDE court-circuite tout le modèle de permissions des fichiers, donc lire /etc/shadow devient trivial. CAP_SYS_PTRACE permet de s'attacher à un autre processus et d'en lire la mémoire, secrets compris. Aucune des trois n'a sa place dans un service applicatif ordinaire.

Les symptômes ci-dessous couvrent l'essentiel de ce que vous rencontrerez en posant vos premières capabilities. Le point commun de la plupart : le noyau échoue silencieusement ou avec un message générique, sans jamais nommer la capability manquante.

SymptômeCause probableSolution
bind() to 0.0.0.0:80 failed (13: Permission denied)La capability n'a pas été accordée, ou elle a été perdue lors d'une mise à jour du paquet.Vérifier avec getpcaps <PID> ; privilégier AmbientCapabilities= dans l'unité plutôt que setcap.
getcap ne renvoie rien après un setcap réussiLe système de fichiers ne gère pas les attributs étendus, ou il est monté en lecture seule.Vérifier le montage avec findmnt ; les file capabilities exigent le support des attributs étendus.
systemd: Service has more than one ExecStart= settingLa surcharge redéfinit ExecStart sans réinitialiser la liste au préalable.Ajouter une ligne ExecStart= vide avant la nouvelle définition.
Le service reste bloqué en activating puis expireType=forking conservé alors que le programme tourne au premier plan avec daemon off;.Passer à Type=simple et vider PIDFile=.
La capability disparaît après un apt upgradeLe paquet a remplacé le binaire, donc l'attribut étendu qui portait la file capability.Basculer sur AmbientCapabilities= dans l'unité systemd, qui survit aux mises à jour.

Les capabilities apparaissent avec le noyau 2.2, en 1999, pour répondre au problème du « tout ou rien » du modèle root. L'implémentation s'inspire du brouillon POSIX 1003.1e, un projet de normalisation qui a été retiré avant d'aboutir. C'est la raison pour laquelle le comportement des capabilities n'est portable vers aucun autre Unix et pour laquelle la terminologie varie d'une documentation à l'autre.

Le modèle s'est enrichi ensuite. Le bounding set est devenu par processus à partir du noyau 2.6.25, ce qui a rendu possible le confinement durable d'une arborescence de processus. L'ambient set, introduit dans le noyau 4.3 en 2015, a comblé le dernier manque : accorder une capability à un service non privilégié sans file capability sur le binaire. C'est sur cette brique que reposent aujourd'hui AmbientCapabilities= de systemd et le modèle de privilèges des conteneurs.

Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

  • Une capability est une tranche de root. Accorder CAP_NET_BIND_SERVICE à un serveur web, c'est lui donner le port 80 sans lui donner /etc/shadow.
  • Un processus porte cinq ensembles, pas trois : Permitted (le plafond activable), Effective (ce que le noyau vérifie maintenant), Inheritable (rarement utile), Bounding set (le plafond absolu, irréversible) et Ambient set (ce qui survit à l'exécution d'un binaire non privilégié).
  • Les capabilities suivent un brouillon POSIX retiré, pas un standard. Aucune portabilité vers un autre Unix n'est garantie.
  • Ne posez jamais de capability sur un interpréteur (python3, perl, node) : elle profiterait à tout code lancé par n'importe quel utilisateur local, qui pourrait alors usurper un service sur un port privilégié.
  • Les file capabilities sont perdues à chaque mise à jour du paquet qui remplace le binaire. La panne survient des semaines plus tard, sans avertissement.
  • Préférez AmbientCapabilities= dans l'unité systemd : la capability est accordée au service seul, rejouée à chaque démarrage, et immunisée contre les mises à jour de paquet.
  • Sur une surcharge systemd, une ligne ExecStart= vide est obligatoire avant de redéfinir la commande, et daemon off; impose de passer en Type=simple.
  • Trois capabilities sont à traiter comme root : CAP_SYS_ADMIN (fourre-tout), CAP_DAC_OVERRIDE (contourne les permissions) et CAP_SYS_PTRACE (lit la mémoire des autres processus).
  • Réflexe d'audit : getcap -r / 2>/dev/null liste les binaires privilégiés, getpcaps <PID> donne les capabilities d'un processus vivant.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn