...

Linux-capabilities in plaats van root: het minimaliteitsprincipe voor serverdiensten

Ik stel Linux-capabilities om serverdiensten volgens het minimaliteitsbeginsel te beheren en zo alleen de absoluut noodzakelijke deelrechten toe te kennen. Hierdoor verminder ik de Aanvalsoppervlak merkbaar, zonder functies te blokkeren.

Centrale punten

  • Minst bevoorrecht consequent: diensten krijgen alleen precies de vaardigheden die ze nodig hebben.
  • Fijnkorrelig in plaats van Root: ongeveer 40–50 capabilities vervangen volledige toegang.
  • Scheiding van processen: scheiding van bevoegdheden beperkt de schade bij exploits.
  • Bestand Mogelijkheden: rechten rechtstreeks aan binaire bestanden koppelen.
  • Controleerbaar: getcap biedt een duidelijk overzicht van speciale rechten.

Waarom root-toegang riskant is – en hoe ‘capabilities’ daar verandering in brengen

Vroeger draaide bijna elke serverdienst op Wortelrechten, wat bij een inbreuk onmiddellijk tot een overname van het systeem kon leiden. Tegenwoordig verdeel ik rechten doelgericht door gebruik te maken van capabilities zoals CAP_NET_BIND_SERVICE voor poorten onder 1024 toekennen en alle andere uitgebreide rechten intrekken. Zo mag de webserver wel een verbinding tot stand brengen, maar geen kernelmodules laden of de eigenaar van bestanden wijzigen, wat de Beveiliging aanzienlijk verhoogt. Een duidelijke scheiding van taken maakt aanvallen minder effectief, omdat een misbruikt proces slechts een beperkt aantal acties mag uitvoeren. Wie meer structuur in dit concept wil aanbrengen, kan rechten zeer nauwkeurig in detail opsplitsen en zo kritieke bewerkingen systematisch beperken. Een monolithische root-dienst wordt zo omgevormd tot een reeks diensten met minimale, duidelijk omschreven bevoegdheden.

Zo werken capability-sets in de kernel

Elk proces heeft meerdere Capability-sets, die de kernel controleert bij gevoelige acties. De Effective-set bepaalt wat een proces op dit moment mag doen, terwijl de Permitted-set de verzameling van mogelijke rechten bevat. Via de Inheritable-set kan ik bepalen wat bij de execve() wordt doorgegeven aan subprocessen, wat vooral bij wrappers en opstartscripts van belang is. De Bounding-Set definieert een strikte bovengrens, zodat bepaalde capabilities nooit meer kunnen worden verkregen, zelfs niet bij fouten in de toepassing. Met de Ambient-Set geef ik capabilities zonder SUID door aan reguliere programma’s en houd ik de Aanvalsroute klein. Deze sets samen bieden mij een zeer nauwkeurige regeling, die veel verder gaat dan het klassieke ‘alles of niets’ van UID 0.

Set Doel Typisch gebruik Risico op verkeerde configuratie
Effectief Nu bruikbare vaardigheden Controle van elke bevoorrechte bewerking Het proces kan meteen te veel worden
Toegestaan Repertoire van toegestane vaardigheden Bron voor de Effective-Set Onnodige reserves blijven beschikbaar
Overerfbaar Erfbare vaardigheden Gecontroleerde overdracht bij execve() Kinderen erven onnodig rechten
Bounding Bovengrens van alle rechten Permanente uitsluitingen definiëren Terugvordering van omvangrijke rechten mogelijk
Ambient Doorsturen zonder SUID Reguliere programma's krijgen capabilities Een bredere, stille toekenning van rechten

In de praktijk zijn er nog twee aspecten van belang: ten eerste is het van belang dat Securebits over de vraag of een proces na een wijziging van de gebruikers-ID (bijvoorbeeld via setuid()) zijn mogelijkheden behoudt. Met PR_SET_KEEPCAPS kan dit doelgericht worden geregeld – typisch verloop: kortstondig als root opstarten, de benodigde sockets of bronnen aanmaken, de UID wijzigen naar een gebruiker zonder privileges en alleen de vereiste capabilities behouden. Ten tweede geldt: het Bounding-set is definitief opgenomen in de huidige proceslijn. Wie hier vroeg in het opstarttraject overbodige bevoegdheden verwijdert, kan later zelfs door verkeerde configuraties geen „verboden“ rechten meer verkrijgen.

Bestandsrechten beheren met File Capabilities

In plaats van een tijdelijke dienst Bijzondere rechten Om dit toe te geven, koppel ik ze liever rechtstreeks aan het binaire bestand. Via setcap cap_net_bind_service=+eip /usr/bin/node sta ik poortkoppelingen toe zonder dat het proces als root hoeft te draaien. Met getcap /usr/bin/node of recursief getcap -r / 2>/dev/null controleer ik de toewijzing en houd ik de controle. Verwijderen gaat via setcap -r /pad/naar/binary, zodat ik de tijdelijke rechten na afloop van de inzet weer intrek. Bij het kopiëren gaan capabilities vaak verloren, daarom sla ik ze tijdens de implementatie expliciet op, om Regressies te voorkomen. Zo blijven builds reproduceerbaar en worden rechten altijd op een traceerbare manier gedocumenteerd.

Bestandsmogelijkheden worden opgeslagen als uitgebreide attributen (beveiligingscapaciteit) op het bestandssysteem. Hiervoor zijn een geschikt bestandssysteem en de juiste mount-opties vereist. Tools zoals tar en rsync moeten XAttrs expliciet worden meegenomen (bijv. tar --xattrs, rsync -XA), anders verdwijnen rechten zonder dat dit wordt gemeld. Pakketbeheerders kunnen capabilities instellen in stappen na de installatie; ik leg dit bij voorkeur vast in het build-/release-proces om verrassingen bij upgrades te voorkomen. Een ander cruciaal punt is dat interpreter-scripts (bijvoorbeeld met shebang) geen bestandscapabilities overnemen, in tegenstelling tot ELF-binaries. Krachtige capabilities op tolk Dat is sowieso riskant – ik koppel het liever los en werk met speciale, kleine hulpbinaries.

Het minimaliteitsprincipe voor serverdiensten in de praktijk

Ik start de webserver als gebruiker zonder beheerdersrechten en verleen uitsluitend CAP_NET_BIND_SERVICE, zodat het proces zich aan 80/443 mag binden en er geen andere Privileges met zich meebrengt. Bestanden en mappen beheer ik verder via POSIX-rechten en, optioneel, MAC-profielen, waardoor configuratie en inhoud gescheiden en beveiligd blijven. Monitoring- of logging-agents krijgen gerichte netwerkrechten en leesrechten voor logs, maar geen enkele bevoegdheid om wijzigingen aan het systeem aan te brengen. In containeromgevingen beperk ik de set van capabilities bovendien en combineer ik deze met filters voor systeemaanroepen om het gedrag strak te houden. Deze combinatie vermindert de impact van succesvolle exploits en verhoogt de Transparantie de daadwerkelijke bevoegdheden. De diensten blijven functioneren, maar de speelruimte blijft beperkt.

In plaats van capabilities toe te wijzen, schrap ik ze soms helemaal: socket-activering stelt bevoorrechte listeners (bijv. 443/tcp) beschikbaar via het init-proces en geeft alleen de geopende bestandsdescriptor door aan de dienst. Het applicatieproces heeft dan geen CAP_NET_BIND_SERVICE meer. Ook kunnen eenmalige root-acties (bijvoorbeeld het aanmaken van de PID-map) vooraf worden uitgevoerd, waarna de rechten consequent kunnen worden teruggegeven. Hoe minder bevoegdheden over het algemeen hoe meer elementen er in het spel zijn, hoe beter het systeem bestand is tegen kettingfouten.

Privilege Separation correct implementeren

Ik verdeel omvangrijke diensten in meerdere Deelprocessen, die elk alleen over de benodigde bevoegdheden beschikken. Een frontend-proces beëindigt TLS-verbindingen en maakt verbinding met poorten, maar beschikt niet over bestandssysteemrechten voor kritieke wijzigingen. Een backend-proces verwerkt gegevens intern, heeft minimale leesrechten voor de configuratie en communiceert met databases zonder eigen netwerkmogelijkheden. Beheertaken zoals logrotatie of onderhoud worden uitgevoerd via speciale tools met tijdelijk beperkte bevoegdheden. Als een aanvaller een onderdeel aanvalt, blijft de rest van het systeem onaangetast, omdat de Autorisaties streng gedefinieerd zijn. Zo wordt de beveiliging afgestemd op de structuur van de applicatie in plaats van op almachtige systeemrechten.

Voor deze indeling is een duidelijke startcoördinatie aangewezen. In klassieke opstellingen wordt dit door een supervisor afgehandeld; op hedendaagse systemen geef ik de voorkeur aan systemd, omdat het capabilities, cgroups en namespaces direct integreert. Zo kan ik de netwerk-frontend, workers en beheertools elk in hun eigen sandbox starten, de beschikbare resources beperken en ze bij een storing automatisch opnieuw laten opstarten – zonder ooit root-rechten algemeen toe te kennen.

Beveiligingsmaatregelen combineren: POSIX, MAC en capabilities

Capabilities werken het beste als ik ze combineer met klassieke Bestandsrechten en MAC-systemen combineer. SELinux of AppArmor kunnen acties ondanks toegewezen capabilities verder beperken en zo meervoudige bescherming bieden. Zo mag een proces weliswaar aan een poort binden, maar wordt het door een beleid verhinderd om gevoelige bestanden te lezen. Wie het verschil tussen deze benaderingen nader wil bestuderen, vindt een duidelijke vergelijking in SELinux versus AppArmor en kan vervolgens een passende beleidsstrategie kiezen. Al met al ontstaat zo een verdedigingsnetwerk dat aanvallen op meerdere niveaus tegenhoudt en de Aanvalsoppervlak verder verkleind. De toewijzing van rechten blijft daarmee controleerbaar, herhaalbaar en consistent.

Het wordt pas echt streng als ik bovendien NoNewPrivileges Activeer: processen en kinderen mogen dan geen nieuwe privileges verkrijgen (bijvoorbeeld via SUID of nieuw ingestelde bestandscapabilities). In combinatie met een strikte capability-bounding-lijst ontstaat zo een beveiligingsbarrière die, zelfs bij een foutieve configuratie, latere uitbreiding van privileges voorkomt.

Functies veilig toewijzen en controleren

Ik beschouw de toegewezen Vaardighedenpakket zo klein mogelijk en vermijd alles wat klinkt als een „tweede root“, bijvoorbeeld CAP_SYS_ADMIN. Interpreters zoals Python, Perl of shells krijgen geen krachtige mogelijkheden, omdat hun functionaliteit gemakkelijk kan worden misbruikt. Door middel van regelmatige audits via getcap -r / 2>/dev/null spoor ik afwijkingen op en breng ik orde in de boel. Binaire bestanden met capabilities zijn schrijfbeveiligd, zijn eigendom van root en bevinden zich niet in paden die gewone gebruikers mogen wijzigen. Daarnaast controleer ik mijn eigen binaire bestanden vóór elke release en documenteer ik wijzigingen, zodat Beoordeling en de reproductie verloopt betrouwbaar. Zo blijft de toekenning van rechten overzichtelijk en blijven aanpassingen traceerbaar.

Tijdens de uitvoering controleer ik processen via /proc//status (Velden CapEff, CapPrm, CapInh). Dit geeft de hexadecimale waarden van de actieve sets weer en laat direct zien of een toepassing meer kan dan bedoeld is. Hulpmiddelen zoals capsh --print of getpcaps maken het opsporen van fouten eenvoudiger. Met het Linux-auditsubsysteem registreer ik bovendien wijzigingen in capabilities of in beveiligingscapaciteit-attributen van bestanden om manipulaties te traceren. Wie capabilities als configuratieobject behandelt en wijzigingen streng controleert, maakt audits reproduceerbaar en vereenvoudigt het aantonen van naleving.

Veelvoorkomende valkuilen en hoe ik ze vermijd

Een veelvoorkomende valkuil: tijdens het kopiëren Attributen verloren gaan, waardoor diensten plotseling niet meer opstarten of juist te weinig worden beperkt. Ik leg capabilities daarom expliciet vast in de build, of ik wijs ze automatisch toe in de stap na de installatie. Een andere fout is het overvloedige gebruik van algemene capabilities, die meer toegang verlenen dan nodig is. Het is beter om specifieke capabilities te gebruiken, zoals CAP_NET_RAW of CAP_CHOWN alleen daar te gebruiken waar ze een daadwerkelijke functie activeren. Ook de ambient-set gebruik ik spaarzaam, zodat er geen ongewenste Doorgeven wijdverbreid. Wie doelgericht het aantal functies beperkt en regelmatig controleert, voorkomt beveiligingslekken als gevolg van bedieningsfouten.

Ook belangrijk: SUID-binaire bestanden systematisch afschaffen. Waar vroeger SUID nodig was (bijvoorbeeld voor het verzenden van ICMP), kan dit vaak worden opgelost met CAP_NET_RAW werken – of, beter nog, de functie uitbesteden aan een zo klein mogelijk hulpproces met een zeer strikt functieprofiel. Daarnaast vermijd ik het om capabilities op tijdelijke of door gebruikers beschrijfbare paden te plaatsen. Een strikt regime voor eigenaarschap en implementatie (Root:root, 0755/0555, onveranderlijke paden) voorkomt het „verlies“ van rechten door vervangen binaire bestanden.

Mogelijkheden in containers en DevSecOps

In containeromgevingen verminder ik de Mogelijkheden agressief en verwijder ik alles wat de workload niet absoluut nodig heeft. Daarnaast maak ik een Seccomp-profiel dat risicovolle systeemaanroepen blokkeert en zo een extra barrière opwerpt. In build-pijplijnen definieer ik capabilities op declaratieve wijze, test ze in de staging-omgeving en registreer ze met versienummers. Dit komt de compliance ten goede, omdat ik het ‘least privilege’-principe kan aantonen en wijzigingen in rechten volledig kan documenteren. Zo blijven containers strak beheerd, zonder dat hun taken worden belemmerd, en de Aanvalsoppervlak blijft klein. In combinatie met afbeeldingen die alleen het hoogstnodige bevatten, neemt de veiligheid nog verder toe.

Belangrijk in de context van containers: capabilities bevinden zich in namespaces Relatief. Binnen een gebruikersnaamruimte kan een proces weliswaar „root“ zijn, maar zijn bevoegdheden gelden alleen voor de bijbehorende naamruimten – dat beperkt de omvang van de schade aanzienlijk. Daar staat tegenover dat „--bevoorrecht“ vrijwel altijd taboe: het schakelt de strenge begrenzing uit en opent veel meer dan nodig is. Ik start containers daarom standaard met „alles verwijderen, gericht toevoegen“ en vul aan NoNewPrivileges, cgroup-limieten en schrijfbeveiligde mounts. Voor diensten die alleen hoeven te luisteren, gebruik ik socket-activering of sidecars om helemaal zonder extra capabilities te kunnen werken.

Systemd-voorbeeld: capabilities declaratief beperken

In service-units leg ik vast wat een proces maximaal mag doen – duidelijk, herhaalbaar en met versiebeheer. Een beknopt voorbeeld van een webservice die alleen op poort 443 mag worden gekoppeld en verder sterk beperkt is:

[Unit]
Description=Minimale webservice zonder 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

De combinatie van AmbientCapabilities en een harde CapabilityBoundingSet zorgt ervoor dat de dienst alleen de ene noodzakelijke bevoegdheid krijgt en niets meer dan dat. NoNewPrivileges voorkomt dat Privilega achteraf wordt opgewaardeerd, ProtectSystem en ReadWritePaths regelen de schrijftoegang, en een strikte filter voor systeemaanroepen voorkomt onnodige toegangspunten tot de kernel.

Veelgebruikte functionaliteiten – en veilige alternatieven

  • CAP_NET_BIND_SERVICE: Koppelen aan poorten <1024. Alternatief: socket-activering, reverse proxy ervoor plaatsen.
  • CAP_NET_RAW: Raw-sockets (Ping, DHCP). Alternatief: een klein hulpproces in plaats van uitgebreide interpreterrechten.
  • CAP_CHOWN/CAP_FOWNER: Eigenaar/ACL-aanpassingen. Alternatief: vooraf ingestelde mappen, speciale onderhoudstools.
  • CAP_SYS_PTRACE: Debugging/tracing – alleen in de staging-omgeving, nooit op grote schaal in de productieomgeving.
  • CAP_SYS_ADMIN: „Tweede root“ – vermijden; concreet aangeven wat er werkelijk nodig is.

Ik kies altijd de kleinste hoeveelheid die precies voldoende is om de benodigde functie te activeren. Als een capability meerdere aanvalsroutes opent (bijvoorbeeld RAW-sockets), sluit ik de functie in een afzonderlijk, kortstondig proces in en trek ik de rechten weer in zodra het werk is voltooid.

Praktische checklist voor robuuste capaciteiten

  • Start de dienst zonder root-rechten? Zo nee: waarom niet – en kan dit worden opgelost met socket-activering of kleine hulpprogramma's?
  • Zijn alle Zijn de toegewezen capabilities aantoonbaar noodzakelijk (functiebewijs, testcases)?
  • Is de bounding-set zo strak en zo vroeg mogelijk vastgesteld?
  • Worden XAttrs bij builds, deployments en back-ups consistent behouden (rsync/tar-vlaggen, pakketscripts)?
  • Zie ik consequent af van capabilities bij interpreters en SUID-binaries?
  • Zijn de eigendoms- en bestandsrechten (Root:root, 0755/0555) en de paden beveiligd tegen wijziging?
  • Werken aanvullende controles (NoNewPrivileges, Seccomp, MAC-profielen)?
  • Worden procescapabilities tijdens de uitvoering gecontroleerd (/proc//status, getpcaps) en zijn de wijzigingen gedocumenteerd?
  • Zijn containers standaard geconfigureerd met „drop all, add minimal“ en zonder „privileged“?

Kort samengevat

Linux Capabilities splitsen klassieke root-rechten op in kleine, beheersbare eenheden en passen zo het minimaliteitsprincipe technisch correct toe. Ik wijs diensten alleen de bevoegdheden toe die ze echt nodig hebben, en combineer dit met POSIX-rechten en MAC-beleidsregels. Bestandscapabilities zorgen ervoor dat rechten direct aan binaire bestanden zijn gekoppeld en dat audits duidelijk laten zien wie wat mag doen. Met privilege-scheiding, beperkte containerrechten en systemcall-filters beperk ik de schade als er een kwetsbaarheid wordt uitgebuit. Regelmatige controles, strikte eigendoms- en schrijfrechten en een gedocumenteerd releaseproces houden de toewijzing gestroomlijnd. Zo blijft de serverdienst functioneel, maar de Manoeuvreerruimte voor aanvallers wordt consequent beperkt gehouden.

Huidige artikelen