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.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Lire les cinq ensembles de capabilities d'un processus via
/procetgetpcaps. - 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é.
Ce qu'est une capability
Section intitulée « Ce qu'est une capability »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 :
getcap -r /usr/bin /usr/sbin 2>/dev/nullSortie réelle sur un poste Ubuntu :
/usr/bin/mtr-packet cap_net_raw=epUn 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.
Les cinq ensembles de capabilities d'un processus
Section intitulée « Les cinq ensembles de capabilities d'un processus »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 :
grep -E '^Cap' /proc/1/statusCapInh: 0000000000000000CapPrm: 000001ffffffffffCapEff: 000001ffffffffffCapBnd: 000001ffffffffffCapAmb: 0000000000000000Chaque ligne est un masque de bits en hexadécimal, un bit par capability. Voici ce que chacune désigne :
Ligne /proc | Ensemble | Ce qu'il signifie |
|---|---|---|
CapPrm | Permitted | Le plafond de ce que le processus peut activer. Une capability absente d'ici est définitivement hors de portée. |
CapEff | Effective | Ce que le noyau vérifie maintenant. C'est l'ensemble réellement utilisé pour les contrôles de permission. |
CapInh | Inheritable | Ce qui survit à un execve(), mais uniquement en combinaison avec les capabilities du fichier exécuté. Peu utilisé en pratique. |
CapBnd | Bounding set | Le plafond absolu : une capability retirée d'ici ne pourra plus jamais être regagnée, ni par le processus, ni par ses descendants. |
CapAmb | Ambient set | Les 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 :
grep -E '^Cap' /proc/self/statusCapInh: 0000000000000000CapPrm: 0000000000000000CapEff: 0000000000000000CapBnd: 000001ffffffffffCapAmb: 0000000000000000Tout 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.
Décoder les masques avec capsh
Section intitulée « Décoder les masques avec capsh »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 :
capsh --decode=0x00000000000020000x0000000000002000=cap_net_rawLe 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 :
getpcaps 11: =epLa 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.
Poser une capability sur un exécutable
Section intitulée « Poser une capability sur un exécutable »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 :
sudo setcap cap_net_bind_service=+ep /usr/local/bin/mon-serviceLe 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 :
getcap /usr/local/bin/mon-servicePour retirer l'attribut, utilisez -r :
sudo setcap -r /usr/local/bin/mon-serviceAttention à 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.
Deux pièges qui coûtent cher
Section intitulée « Deux pièges qui coûtent cher »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.
Cas concret : Nginx sur le port 80 sans root
Section intitulée « Cas concret : Nginx sur le port 80 sans root »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.
-
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 -
Accorder la capability via systemd, sans toucher au binaire.
AmbientCapabilities=accordeCAP_NET_BIND_SERVICEau processus au démarrage, même s'il tourne sous un compte non privilégié. Le manuelsystemd.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 ». Aucunsetcapn'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. -
Écrire la surcharge systemd avec
sudo systemctl edit nginx.service.[Service]Type=simplePIDFile=User=nginx-svcGroup=nginx-svcRuntimeDirectory=nginxRuntimeDirectoryMode=0750AmbientCapabilities=CAP_NET_BIND_SERVICECapabilityBoundingSet=CAP_NET_BIND_SERVICEExecStartPre=ExecStart=ExecStart=/usr/sbin/nginx -g 'daemon off; master_process on;'ExecReload=ExecReload=/bin/kill -s HUP $MAINPIDLes lignes
ExecStart=etExecReload=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, votreExecStarts'ajoute à celui du paquet et systemd refuse l'unité. Le passage àType=simpledécoule dedaemon off;: Nginx reste au premier plan, il ne fork plus, doncType=forkingferait attendre systemd indéfiniment.PIDFile=vide supprime la référence devenue inutile. -
Adapter
/etc/nginx/nginx.conf, qui reste édité parroot.Deux directives posent problème sous un compte non privilégié. Le fichier PID pointe par défaut sur
/run/nginx.pid, dans un/runappartenant àroot: le compte de service ne peut pas y écrire. Redirigez-le vers le répertoire queRuntimeDirectory=crée pour vous, avec les bons droits :pid /run/nginx/nginx.pid;Ensuite, commentez la directive
user. Elle ne sert qu'à un masterrootqui 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. -
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/nginxcontient 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/nginxest créé automatiquement au bon propriétaire parRuntimeDirectory=, vous n'avez rien à faire. -
Laisser la configuration à
root, en lecture seule pour le service.Fenêtre de terminal sudo chown -R root:root /etc/nginxsudo chmod -R go-w /etc/nginxSi 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.keysudo chmod 0640 /etc/ssl/private/mon-site.key -
Recharger, démarrer et vérifier que le service écoute bien sans être
root.Fenêtre de terminal sudo systemctl daemon-reloadsudo systemctl start nginxss -tulpn | grep ':80'getpcaps "$(systemctl show -p MainPID --value nginx)"La sortie de
getpcapsdoit montrercap_net_bind_serviceet rien d'autre. Si elle affiche=ep, le service tourne encore enrootet 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.
Les capabilities les plus courantes
Section intitulée « Les capabilities les plus courantes »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.
| Capability | Ce qu'elle autorise | Usage typique |
|---|---|---|
CAP_NET_BIND_SERVICE | Lier une socket à un port inférieur à 1024. | Serveur web sur le port 80 ou 443. |
CAP_NET_RAW | Ouvrir des sockets brutes et forger des paquets. | ping, mtr, tcpdump. |
CAP_CHOWN | Changer le propriétaire d'un fichier. | Outils d'administration de systèmes de fichiers. |
CAP_SYS_TIME | Modifier l'horloge système. | Démons de synchronisation de temps. |
CAP_SYS_PTRACE | Attacher un débogueur à un autre processus. | strace, gdb. Sensible : donne accès à la mémoire d'autrui. |
CAP_DAC_OVERRIDE | Ignorer les permissions classiques des fichiers. | Dangereuse : équivaut à lire et écrire n'importe quel fichier. |
CAP_SYS_ADMIN | Montage, 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.
Dépannage
Section intitulée « Dépannage »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ôme | Cause probable | Solution |
|---|---|---|
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éussi | Le 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= setting | La 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 expire | Type=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 upgrade | Le 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. |
Aux origines des capabilities
Section intitulée « Aux origines des capabilities »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.
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »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
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
Vérification
(0/0)Profil de compétences
Quoi faire maintenant
Ressources pour progresser
Des indices pour retenter votre chance ?
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications
À retenir
Section intitulée « À retenir »- Une capability est une tranche de
root. AccorderCAP_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, etdaemon off;impose de passer enType=simple. - Trois capabilities sont à traiter comme
root:CAP_SYS_ADMIN(fourre-tout),CAP_DAC_OVERRIDE(contourne les permissions) etCAP_SYS_PTRACE(lit la mémoire des autres processus). - Réflexe d'audit :
getcap -r / 2>/dev/nullliste les binaires privilégiés,getpcaps <PID>donne les capabilities d'un processus vivant.