Linux capabilities

De “POSIX capabilities” die door Linux worden ondersteund, bieden een veilig alternatief voor programma’s met het setuid-bit waarvan root de eigenaar is. Helaas wordt bij de meeste distributies nog weinig gebruik gemaakt van dit mechanisme voor de standaard setuid-root-programma’s.

Alles of niets

Sinds UNIX-heugenis krijgt een proces dat draait onder het (effectieve) UID 0 alle privileges. Zo’n proces krijgt dus de mogelijkheid om een reboot te forceren, de systeemklok bij te stellen, ieder ander proces te killen, ieder ander proces een hogere prioriteit te geven, het eigenaarschap van een file of directory te wijzigen, en vele andere geprivilegieerde acties uit te voeren. Een proces dat draait onder een andere identiteit dan UID 0, krijgt van oudsher geen enkel privilege. Traditioneel hanteren UNIX-systemen dus een “alles of niets” beleid: processen met UID 0 mogen alles en andere processen mogen niets
(gepriviligieerds).

Een proces draait in de volgende gevallen met UID 0:

  • Als een programma door een root-gebruiker (superuser) is opgestart.

  • Als een gewone gebruiker gerechtigd is om een specifiek programma onder UID 0 te starten via het sudo commando.

  • Als een gewone gebruiker een programma mag starten waarvoor het setuid-bit is gezet en waarvan root (UID 0) de eigenaar is. Daarmee heeft de programma-eigenaar (root) andere gebruikers het recht gegeven om dit specifieke programma onder de root-identiteit te draaien. Denk bijvoorbeeld aan het commando passwd (moet het wachtwoord van de gebruiker in de /etc/shadow file kunnen modificeren), het commando ping (moet een raw socket kunnen openen), het commando su of sudo (moet via de root-identiteit naar een willekeurig andere identiteit kunnen switchen), het commando chsh (moet de favoriete shell van de gebruiker in de /etc/passwd file kunnen wijzigen), etcetera.

Het “alles of niets” beleid brengt risico’s met zich mee. Zo heeft het ping commando het UID 0 alleen maar nodig om een raw socket naar ICMP te kunnen openen, maar heeft daarmee impliciet ook de mogelijkheid om willekeurige processen te killen of de systeemklok te wijzigen. Dit biedt “interessante” mogelijkheden voor kwaadwillende programmeurs, of voor kwaadwillende gebruikers die op slinkse wijze een “achterdeurtje” vinden om via zo’n programma onbeoogde acties onder UID 0 uit te voeren.
Hetzelfde gevaar geldt voor daemon processen die onder UID 0 draaien omdat ze slechts een enkel privilege nodig hebben. Als zo’n daemon wordt gehackt, krijgt de hacker alle privileges op een dienblaadje aangereikt.

Capabilities in de procesboekhouding

De Linux-kernel is al lang afgestapt van het “alles of niets” beleid door de implementatie van POSIX capabilities (sinds kernel versie 2.2). Capabilities bepalen welke privileges aan een proces zijn toegekend. Hiervoor wordt in de procesboekhouding een bitlijstje bijgehouden waarin ieder bitje een specifiek privilege voorstelt dat al dan niet is gezet. Voorbeelden van deze privileges zijn cap_sys_boot (reboot forceren), cap_sys_time (systeemklok instellen), cap_kill (willekeurig proces killen),
cap_sys_nice (prioriteit wijzigen van willekeurig proces), cap_chown (eigenaarschap van file/directory wijzigen) en cap_net_raw (raw socket openen). Zie de man-pagina van capabilities voor een volledige lijst.
Als er voor een bepaald verzoek een specifiek privilege is vereist, verifieert de kernel of het betreffende capability-bitje is gezet in de procesboekhouding, dus niet of het proces onder UID 0 draait. Uit compatibiliteitsoverwegingen zijn alle capability-bitjes gezet voor een proces dat met UID 0 draait (op een enkele uitzondering na die ik nu even buiten beschouwing laat).

Naast het bitlijstje dat de effective capabilities van een proces aangeeft (de capabilities waarnaar daadwerkelijk wordt gekeken), vind je in de procesboekhouding nog twee andere bitlijstjes:

  • De permitted capabilities.
    De capabilities die het proces effectief mag maken. Een proces zou bijvoorbeeld met een leeg effective bitlijstje kunnen draaien uit veiligheidsoverwegingen en alleen (via bepaalde system calls) een capability effective kunnen maken op het moment dat-ie echt nodig is. Daarvoor moet zo’n capability dan wel permitted zijn.

  • De inheritable capabilities.
    De capabilities die permitted worden, zodra een nieuw programma (executable file) in het proces wordt geladen. Dit gebeurt tijdens de exec.. system call.

Neem bijvoorbeeld de ntpd-daemon die onder de identiteit ntp draait:

$ ps -fp 4069
UID        PID  PPID  C STIME TTY        TIME   CMD
ntp       4069     1  0 11:19 ?      00:00:00   ntpd -u ntp:ntp

De drie capability-bitlijstjes in de procesboekhouding van zo’n proces kun je
zichtbaar maken via de “file” /proc/N/status (voor N het
PID van het gewenste proces):

$ cat /proc/4069/status
....
CapInh: 0000000002000400
CapPrm: 0000000002000400
CapEff: 0000000002000400

Je ziet dat de drie bitlijstjes in dit geval hetzelfde bitpatroon weergeven. Je zou zelfs kunnen achterhalen om welke capabilities het gaat door de bitnummers op ambachtelijke wijze uit te tellen (van rechts naar links,
beginnend bij bitnummer 0 waarbij elke positie in de uitvoer 4 bitjes voorstelt): bitnummer 10 en 25.
In de file /usr/include/linux/capability.h vind je vervolgens dat het gaat om resp. de capabilities cap_set_bind (binding op een poortnummer kleiner dan 1024) en cap_sys_time
(modificatie van de systeemklok). Precies de privileges die ntp nodig heeft om aan UDP-poort 123 te binden en om met de systeemklok te freubelen.

Capabilities voor executable files

Eigenlijk was het capability-mechanisme in de kernel nauwelijks bruikbaar, totdat (veel recenter, in kernel versie 2.6.24) capabilities ook gezet konden worden op executable files. Zo kun je dus capabilities zetten
voor programma’s die een specifiek privilege nodig hebben. Dit ter vervanging van de conventionele
methode van het setuid-bit gecombineerd met eigenaar root, waarbij alle privileges door het programma (mis)bruikbaar zijn.

Zoals eerder genoemd, bepalen de inheritable capabilities in de procesboekhouding wat de permitted capabilities worden na het laden van een nieuw programma. Echter, deze permitted capability-list in de procesboekhouding ondergaat ook nog wijzigingen die bepaald worden door de capability-lists
in de executable file.
De volgende figuur toont de transitie van de procesboekhouding op het moment
dat er een nieuw programma (executable file) geladen wordt:

Proces- en file-capabilities

De volgende stappen worden doorlopen tijdens het laden van een
programma (zie de stapnummers in de figuur):

  1. De inheritable capability-list (I) van het proces wordt tijdens het laden van een nieuw programma overgenomen als permitted capability-list (P) voor het proces.

  2. De inheritable capability-list (I) van de file bepaalt welke capabilities in de permitted list (P) van het proces mogen blijven. Daarmee kunnen de permitted capabilities van het proces dus worden ingeperkt. Als alle bitjes in de inheritable capability-list van de file bijvoorbeeld op 0 zouden staan, dan zouden alle bitjes in de permitted list van het proces gewist worden; als alle bitjes in de inheritable list van de file daarentegen op 1 zouden staan, dan zouden alle bitjes in de permitted list van het proces ongewijzigd blijven.

  3. De permitted capability-list (P) van de file bepaalt welke capabilities aan de permitted list (P) van het proces toegevoegd moeten worden. Daarmee kunnen de permitted capabilities van het proces dus worden uitgebreid.

  4. In plaats van een effective capability-list heeft een executable file een effective capability-bit (E)! Dit bit bepaalt of de permitted list in de procesboekhouding gelijk gekopieerd wordt naar de effective list in de procesboekhouding. Dit bit moet gezet worden voor programma’s die niet “capability-aware” zijn, dus programma’s die niet zelf een permitted capability effective maken op het moment dat dit nodig is.

Je kunt de capabilities van een executable file zetten/wijzigen met
het setcap commando en bekijken met het getcap commando. We zullen
deze commando’s gebruiken in het volgende voorbeeld, waarin ik het pong
commando introduceer als een veiliger alternatief voor het ping commando.

Liever pong dan ping

Als voorbeeld willen we een veiliger variant maken van het ping commando.

$ ls -l /bin/ping
-rwsr-xr-x. 1   root    root    40760  Sep 26  2013  /bin/ping

Zoals je ziet, is het setuid-bit gezet voor ping en is root de eigenaar.
Hierdoor draait het proces met alle privileges, terwijl het alleen het
privilege nodig heeft om een raw socket te openen.
We maken een kopie van dit commando en proberen deze kopie te draaien
als gewone gebruiker (uiteraard wordt het setuid-bit niet overgenomen
voor de kopie):

$ cp /bin/ping pong
$ ls -l pong
-rwxr-xr-x. 1   gerlof  gerlof  40760  Jun  5 09:56  pong

$ pong host42
ping: icmp open socket: Operation not permitted

Het pong commando ontbeert nog het privilege om een raw socket te openen.
We kennen dit specifieke privilege toe door de capability cap_net_raw
te zetten in de permitted capability-list van de file en door tegelijkertijd
het effective bit te zetten:

# setcap cap_net_raw=pe pong

File capabilities kunnen alleen gewijzigd worden door een proces
met het cap_setfcap privilege, dus daarom wordt het setcap commando
door root uitgevoerd.

Besef dat we hier geen inheritable capabilities zetten voor de file,
dus die bitjes staan dan allemaal op 0.
Dit betekent de facto dat stap 2 in de figuur
stap 1 teniet doet, waardoor de permitted capability-list van de file
dus de permitted capability-list van het proces wordt.

$ ls -l pong
-rwxr-xr-x. 1   gerlof  gerlof  40760  Jun  5 09:56  pong

Het ls commando toont de capabilities helaas niet in de
permissies (eerste kolom), maar kleurt de file-naam wel rood als
de color-optie wordt gebruikt (zoals ook gebeurt voor een setuid-root
programma).
Merk op dat een gewone gebruiker nog steeds eigenaar van de
executable file kan zijn.

Het getcap commando toont de capabilities van de file:

$ getcap pong
pong = cap_net_raw+ep

En het pong commando werkt nu naar behoren:

$ pong host42
PING host42 (...) 56(84) bytes of data.
64 bytes from host42 (...): icmp_seq=1 ttl=64 time=0.464 ms
64 bytes from host42 (...): icmp_seq=2 ttl=64 time=0.210 ms

Jammer dat pong zich nog steeds manifesteert als PING,
maar zover reikt de invloedsfeer van de capabilities helaas niet…

Onderwerpen
Actieve filters: Wis alle filters
Loading...