Je mets capacités Linux afin d'exploiter les services du serveur selon le principe du minimum nécessaire et de n'attribuer ainsi que les droits partiels absolument indispensables. Je réduis ainsi la Surface d'attaque de manière perceptible, sans bloquer les fonctions.
Points centraux
- Moindre privilège De manière cohérente : les services ne disposent que des capacités strictement nécessaires.
- À granulométrie fine Au lieu de « Root » : environ 40 à 50 capacités remplacent les droits d'accès complets.
- Séparation au niveau des processus : la séparation des privilèges limite les dégâts en cas d'exploitation de failles.
- Fichier Fonctionnalités : associer des droits directement aux fichiers binaires.
- Vérifiable: getcap offre une vue d'ensemble claire des privilèges.
Pourquoi le root est risqué – et comment les « capabilities » changent la donne
Autrefois, presque tous les services de serveur fonctionnaient avec Droits d'accès root, ce qui, en cas de compromission, pouvait entraîner immédiatement la prise de contrôle du système. Aujourd’hui, je répartis les droits de manière ciblée en utilisant des capacités telles que CAP_NET_BIND_SERVICE pour les ports inférieurs à 1024 et supprimer tous les autres droits étendus. Ainsi, le serveur Web peut se lier à un port, mais ne peut ni charger de modules du noyau ni modifier les propriétaires de fichiers, ce qui Sécurité augmente considérablement. Une séparation claire des tâches rend les attaques moins efficaces, car un processus compromis ne peut effectuer qu'un nombre limité d'actions. Ceux qui souhaitent structurer davantage ce concept peuvent définir les droits de manière très précise répartir de manière granulaire et ainsi limiter systématiquement les opérations critiques. Un service racine monolithique se transforme ainsi en un ensemble de services dotés de privilèges minimaux et clairement définis.
Voici comment fonctionnent les ensembles de capacités dans le noyau
Chaque processus comporte plusieurs Ensembles de capacités, que le noyau vérifie lors d'actions sensibles. L'« Effective-Set » détermine ce qu'un processus est autorisé à faire immédiatement, tandis que l'« Permitted-Set » contient l'ensemble des droits possibles. L'« Inheritable-Set » me permet de contrôler ce qui, lors du execve() est transmis aux processus enfants, ce qui est particulièrement important pour les wrappers et les scripts de démarrage. Le « Bounding-Set » définit une limite maximale stricte, de sorte que certaines capacités ne peuvent plus jamais être acquises, même en cas d'erreurs dans l'application. Avec l'« Ambient-Set », je transmets des capacités sans SUID à des programmes classiques et je conserve le Voie d'attaque petit. Ensemble, ces ensembles me permettent un contrôle très précis, qui va bien au-delà du classique « tout ou rien » de l'UID 0.
| Lot | Objectif | Utilisation typique | Risque en cas de mauvaise configuration |
|---|---|---|---|
| En vigueur | Compétences désormais efficaces | Vérification de chaque opération privilégiée | Le processus peut immédiatement entraîner une surcharge |
| Autorisé | Ensemble des compétences autorisées | Source de l'« Effective Set » | Les réserves superflues restent disponibles |
| Héréditaire | Compétences héréditaires | Transmission contrôlée avec execve() | Les enfants héritent de droits sans que cela soit nécessaire |
| Délimitation | Limite supérieure de tous les droits | Définir des exclusions permanentes | Il est possible de récupérer des droits importants |
| Ambient | Transmission sans SUID | Les programmes standard se voient attribuer des capacités | Une attribution des droits plus large et discrète |
Deux aspects supplémentaires sont importants dans la pratique : premièrement, il faut décider Securebits pour déterminer si un processus, après un changement d'identifiant utilisateur (par exemple via setuid()) conserve ses capacités. Avec PR_SET_KEEPCAPS cela peut être géré de manière ciblée – procédure type : démarrer brièvement en tant que root, créer les sockets ou ressources nécessaires, passer à l'UID d'un utilisateur sans privilèges et ne conserver que les capacités requises. Deuxièmement, il convient de noter que le Ensemble limite est définitivement intégré dans la chaîne de traitement actuelle. En supprimant dès le début du chemin de démarrage les capacités superflues, il n'est plus possible, par la suite, d'obtenir des droits „ interdits “, même en cas de mauvaise configuration.
Contrôler les droits d'accès aux fichiers à l'aide des capacités de fichier
Au lieu d'un service permanent Droits spéciaux Pour gagner de la place, je préfère les lier directement au fichier binaire. Via setcap cap_net_bind_service=+eip /usr/bin/node je permets les liaisons de ports sans que le processus ait besoin de s'exécuter en tant que root. Avec getcap /usr/bin/node ou de manière récursive getcap -r / 2>/dev/null je vérifie l'attribution et garde le contrôle. La suppression s'effectue via setcap -r /chemin/vers/le/fichier-binaire, de sorte que je retire ces droits temporaires une fois la mission terminée. Les capacités sont souvent perdues lors de la copie ; c'est pourquoi je les sauvegarde explicitement lors du déploiement afin de Régressions à éviter. Ainsi, les builds restent reproductibles et les droits sont toujours documentés de manière traçable.
Les capacités de fichier sont définies sous forme d'attributs étendus (security.capability) sur le système de fichiers. Cela nécessite un système de fichiers compatible et des options de montage adaptées. Des outils tels que tar et rsync il faut inclure explicitement les XAttrs (par exemple,. tar --xattrs, rsync -XA), sinon les droits disparaissent tacitement. Les gestionnaires de paquets peuvent définir des capacités lors des étapes post-installation ; je préfère les définir dans le processus de compilation/publication afin d'éviter les surprises lors des mises à jour. Autre point crucial : les scripts d’interpréteur (par exemple avec un shebang) n’héritent pas des capacités de fichier comme les binaires ELF. Les capacités puissantes sur interprète C'est de toute façon risqué – je préfère dissocier les éléments et travailler avec de petits binaires auxiliaires dédiés.
Le principe de minimalisme appliqué aux services serveur
Je lance le serveur web en tant qu'utilisateur sans privilèges et j'accorde exclusivement CAP_NET_BIND_SERVICE, afin que le processus puisse se lier au port 80/443 et qu'aucun autre Privilèges . Je continue à gérer les fichiers et les répertoires via les droits POSIX ainsi que, de manière facultative, via des profils MAC, ce qui permet de sécuriser séparément la configuration et le contenu. Les agents de surveillance ou de journalisation se voient attribuer des droits réseau ciblés et des droits de lecture sur les journaux, mais aucune autorisation pour modifier le système. Dans les environnements de conteneurs, je réduis en outre l’ensemble des capacités et je le combine avec des filtres d’appels système afin de restreindre strictement le comportement. Cette combinaison limite l’impact des exploits réussis et augmente la Transparence des pouvoirs effectifs. Les services continuent de fonctionner, mais la marge de manœuvre reste limitée.
Au lieu d'attribuer des « capabilities », il m'arrive parfois de les supprimer complètement : l'activation de socket met à disposition des écouteurs privilégiés (par exemple 443/tcp) via le processus « init » et ne transmet au service que le descripteur de fichier ouvert. Le processus d'application n'a alors pas besoin de CAP_NET_BIND_SERVICE plus. De même, les opérations root ponctuelles (par exemple, la création du répertoire PID) peuvent être effectuées au préalable, puis les droits peuvent être systématiquement restitués. Moins il y a de capacités du tout plus il y en a en jeu, plus le système résiste aux erreurs en chaîne.
Mettre en œuvre correctement la séparation des privilèges
Je décompose les services volumineux en plusieurs sous-processus, qui ne disposent chacun que des capacités nécessaires. Un processus frontal gère le protocole TLS et s'attache à des ports, mais ne dispose d'aucun droit d'accès au système de fichiers pour effectuer des modifications critiques. Un processus backend traite les données en interne, dispose de droits de lecture minimaux sur la configuration et communique avec les bases de données sans capacités réseau propres. Les tâches administratives telles que la rotation des journaux ou la maintenance sont exécutées via des outils dédiés dotés de capacités limitées dans le temps. Si un attaquant s'en prend à une partie du système, le reste reste intact, car la Autorisations sont définis de manière stricte. Ainsi, la sécurité s'adapte à la structure de l'application plutôt qu'à des droits système illimités.
Pour cette répartition, une orchestration claire dès le départ s'impose. Dans les configurations classiques, cette tâche est assurée par un superviseur ; sur les systèmes actuels, je privilégie systemd, car il intègre directement les capacités, les cgroups et les espaces de noms. Je peux ainsi démarrer le frontend réseau, les workers et les outils d’administration chacun dans leur propre sandbox, limiter leurs ressources et les faire redémarrer automatiquement en cas d’erreur – sans jamais attribuer de droits root de manière générale.
Combiner les contrôles de sécurité : POSIX, MAC et capacités
Les « Capabilities » sont plus efficaces lorsque je les associe à des Droits d'accès aux fichiers et les systèmes MAC. SELinux ou AppArmor peuvent restreindre davantage les actions malgré l'attribution de capacités, créant ainsi une protection à plusieurs niveaux. Ainsi, un processus peut certes se lier à un port, mais une politique l'empêche de lire des fichiers sensibles. Si vous souhaitez approfondir la différence entre ces deux approches, vous trouverez une comparaison claire dans SELinux vs AppArmor et peut alors choisir une stratégie de politique adaptée. Au final, on obtient un système de défense intégré qui bloque les attaques à plusieurs niveaux et qui Surface d'attaque encore réduite. L'attribution des droits reste ainsi vérifiable, reproductible et cohérente.
La situation devient particulièrement restrictive lorsque, en plus, je NoNewPrivileges Activer : les processus et les processus enfants ne peuvent alors plus obtenir de nouveaux privilèges (par exemple via SUID ou des capacités de fichier nouvellement définies). Associé à une liste stricte de limites de capacités, cela crée une barrière de sécurité qui empêche toute extension ultérieure des privilèges, même en cas de configuration erronée.
Répartir et contrôler les capacités en toute sécurité
Je considère que le ensemble de compétences aussi petit que possible et évite tout ce qui ressemble à un „ deuxième root “, par exemple CAP_SYS_ADMIN. Les interpréteurs tels que Python, Perl ou les shells ne disposent pas de fonctionnalités puissantes, car leurs capacités peuvent facilement faire l'objet d'abus. Grâce à des audits réguliers via getcap -r / 2>/dev/null je repère les anomalies et je les corrige. Les fichiers binaires dotés de capacités sont en lecture seule, appartiennent à root et ne se trouvent pas dans des chemins d'accès que les utilisateurs normaux sont autorisés à modifier. De plus, je vérifie mes propres binaires avant chaque version et je documente les modifications afin que Revue et que la reproduction se déroule sans problème. Cela permet de garder le contrôle sur l'octroi des droits et de garantir la traçabilité des modifications.
Au moment de l'exécution, je vérifie les processus via /proc//status (Champs CapEff, CapPrm, CapInh). Cela permet d'obtenir les valeurs hexadécimales des ensembles actifs et indique immédiatement si une application en fait plus que prévu. Des outils tels que capsh --print ou getpcaps facilitent le débogage. Grâce au sous-système d'audit de Linux, j'enregistre également les modifications apportées aux capacités ou à security.capability- les attributs des fichiers, afin de suivre les manipulations. En traitant les capacités comme des objets de configuration et en soumettant rigoureusement les modifications à un contrôle, on rend les audits reproductibles et on simplifie la démonstration de la conformité.
Les pièges courants et comment les éviter
Un piège classique : lors de la copie, Attributs perdues, ce qui peut entraîner l'arrêt soudain des services ou, à l'inverse, des restrictions insuffisantes. Je sécurise donc explicitement les capacités lors de la compilation, ou je les attribue automatiquement lors de l'étape post-installation. Une autre erreur consiste à utiliser de manière excessive des capacités « passe-partout », qui ouvrent plus de possibilités que nécessaire. Il est préférable d’utiliser des capacités spécifiques telles que CAP_NET_RAW ou CAP_CHOWN de ne les utiliser que là où ils activent une fonction réelle. J'utilise également l'Ambient-Set avec parcimonie, afin d'éviter tout effet indésirable Transmission répandues. En réduisant de manière ciblée et en effectuant des contrôles réguliers, on évite les failles de sécurité dues à des erreurs de manipulation.
Autre point important : supprimer systématiquement les binaires SUID. Là où le SUID était auparavant nécessaire (par exemple pour envoyer des paquets ICMP), il est souvent possible d'utiliser CAP_NET_RAW travailler – ou, mieux encore, externaliser cette fonction dans un processus auxiliaire aussi petit que possible, avec un cahier des charges très strict. De plus, j'évite de placer les capacités sur des chemins temporaires ou accessibles en écriture par les utilisateurs. Un régime strict de propriété et de déploiement (Root:root, 0755/0555, chemins d’accès immuables) empêche la „ perte “ de droits due au remplacement des binaires.
Fonctionnalités dans les conteneurs et DevSecOps
Dans les environnements de conteneurs, je réduis la Capabilités de manière agressive et je supprime tout ce dont la charge de travail n'a pas absolument besoin. De plus, je crée un Profil Seccomp qui bloque les appels système à risque et ajoute ainsi un obstacle supplémentaire. Dans les pipelines de build, je définis les capacités de manière déclarative, je les teste en environnement de préproduction et je les enregistre avec un numéro de version. La conformité en bénéficie, car je peux démontrer le respect du principe du privilège minimal et documenter de manière exhaustive les modifications de droits. Ainsi, les conteneurs restent étroitement contrôlés, sans entraver leurs tâches, et la Surface d'attaque reste petite. Associée à des images ne contenant que le strict nécessaire, la sécurité s'en trouve encore renforcée.
Point important dans le contexte des conteneurs : les capacités se trouvent dans des espaces de noms relativement. Au sein d'un espace de noms utilisateur, un processus peut certes avoir les privilèges „ root “, mais ses capacités ne s'appliquent qu'aux espaces de noms associés, ce qui réduit considérablement la portée des dommages. En revanche, „--privilégié“ C'est pratiquement toujours à éviter : cela désactive la limite de délimitation stricte et ouvre bien plus que nécessaire. Je lance donc les conteneurs par défaut avec „ tout supprimer, ajouter de manière ciblée “ et j'ajoute NoNewPrivileges, les limites cgroup et les montages en lecture seule. Pour les services qui doivent uniquement être à l'écoute, j'utilise l'activation par socket ou des sidecars afin de me passer complètement de capacités supplémentaires.
Exemple Systemd : limiter les capacités de manière déclarative
Dans les unités de service, je définis les limites maximales autorisées pour un processus – de manière claire, reproductible et contrôlable via un système de gestion de versions. Voici un exemple concis de service web qui ne peut se connecter qu'au port 443 et qui est par ailleurs fortement restreint :
[Unit]
Description=Service web minimal sans droits root
[Service]
User=web
Group=web
ExecStart=/usr/bin/my-web
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
ReadWritePaths=/var/lib/my-web
RestrictAddressFamilies=AF_INET AF_INET6
SystemCallFilter=@basic-io @network-io
LockPersonality=yes
MemoryDenyWriteExecute=yes
[Install]
WantedBy=multi-user.target
La combinaison de AmbientCapabilities et une dure CapabilityBoundingSet veille à ce que le service ne reçoive que la capacité nécessaire, et rien de plus. NoNewPrivileges empêche toute revalorisation ultérieure des privilèges, ProtectSystem et ReadWritePaths régulent l'accès en écriture, et un filtre strict des appels système empêche les points d'entrée inutiles dans le noyau.
Fonctionnalités couramment utilisées – et alternatives sécurisées
- CAP_NET_BIND_SERVICE: Attribution des ports <1024. Alternative : activation des sockets, mise en place d'un proxy inverse en amont.
- CAP_NET_RAW: Sockets bruts (Ping, DHCP). Alternative : un petit processus auxiliaire plutôt que des droits d'interpréteur étendus.
- CAP_CHOWN/CAP_FOWNER: Propriétaire/ajustements ACL. Alternative : répertoires préconfigurés, outils de maintenance dédiés.
- CAP_SYS_PTRACE: Débogage/traçage – uniquement en environnement de préproduction, jamais à grande échelle en production.
- CAP_SYS_ADMIN: „ Deuxième racine “ – à éviter ; préciser ce dont on a réellement besoin.
Je choisis toujours le minimum nécessaire pour activer précisément la fonctionnalité requise. Si une capacité ouvre plusieurs vecteurs d'attaque (par exemple, les sockets RAW), j'encapsule la fonctionnalité dans un processus distinct et éphémère, puis je retire les droits une fois la tâche terminée.
Liste de contrôle pratique pour des capacités robustes
- Le service démarre-t-il sans droits root ? Si ce n'est pas le cas : pourquoi ? Et est-il possible de résoudre ce problème en activant les sockets ou à l'aide de petits binaires d'assistance ?
- Sont tous Les capacités attribuées sont-elles manifestement nécessaires (preuve de fonctionnalité, cas de test) ?
- L'ensemble de délimitation est-il défini de manière aussi étroite et précoce que possible ?
- Les XAttrs sont-ils conservés de manière cohérente lors de la compilation, des déploiements et des sauvegardes (options rsync/tar, scripts de paquets) ?
- Est-ce que je renonce systématiquement aux « capabilities » pour les interpréteurs et les binaires SUID ?
- Les droits de propriété et d'accès aux fichiers (Root:root, 0755/0555) ainsi que les chemins d'accès sont-ils protégés contre toute modification ?
- Les contrôles supplémentaires (NoNewPrivileges, Seccomp, profils MAC) sont-ils efficaces ?
- Les capacités des processus font-elles l'objet d'un audit pendant l'exécution (
/proc//status, getpcaps) et les modifications ont-elles été documentées ? - Les conteneurs sont-ils configurés par défaut avec „ drop all, add minimal “ et sans „ privileged “ ?
En bref
Linux Les « Capabilities » décomposent les droits root classiques en petites unités contrôlables et permettent ainsi une mise en œuvre techniquement rigoureuse du principe de minimalisme. Je n’attribue aux services que les capacités dont ils ont réellement besoin, et je combine cela avec les droits POSIX ainsi que les politiques MAC. Les capacités de fichiers garantissent que les droits sont directement associés aux binaires et que les audits indiquent clairement qui est autorisé à faire quoi. Grâce à la séparation des privilèges, à la réduction des droits des conteneurs et aux filtres d’appels système, je limite les dégâts en cas d’exploitation d’une vulnérabilité. Des contrôles réguliers, des droits de propriété et d’écriture stricts, ainsi qu’un processus de publication documenté permettent de maintenir une attribution allégée. Ainsi, le service serveur reste opérationnel, mais le Salle de jeux est systématiquement maintenue à un niveau très bas pour les attaquants.


