Pelicanux

Just A Few Random Words

Durcissement Système Avec Les Capabilities Linux

Introduction aux capabilities Linux

La source de documentation la plus complète concernant les capabilities Linux est fournie par sa page de manuel : man capabilities.

Le noyau Linux distingue deux catégories de processus :

  • Les processus privilégiés, c’est-à-dire lancés par l’utilisateur root : ces processus disposent de la totalité des droits sur le système,
  • Les processus non-privilégiés : Ces processus doivent obéir aux restrictions imposés par le système, comme les droits d’accès sur les fichiers, par exemple.

Cependant, cette architecture n’est pas modulaire. Par exemple, sous Unix, seul root a accès aux ports privilégiés. Historiquement, cela permettait de savoir que l’on parle à root et non pas à un utilisateur ayant des privilèges restreints sur la machine pour réaliser des opérations parfois sensibles d’authentification (telnet, ftp, ssh, …). La logique étant que si on parle à un processus écoutant sur un port inférieur à 1024, alors on parle à root. Ce modèle de sécurité avait sa pertinence dans les années 80, mais il est maintenant largement obsolète. La conséquence est qu’un processus en écoute sur un port inférieur à 1024 devra par défaut disposer des droits complets sur le système. La compromission d’un tel processus implique la compromission totale du système.

Différentes techniques ont été mises en place pour éviter ce soucis :

  • Apache doit pouvoir se binder aux ports 80 ou 443. De nombreuses distributions Linux choisissent par défaut de le démarrer par l’utilisateur root pour l’écoute sur ces ports http(s). Ce processus lance des processus non-privilégiés qui traiteront effectivement les requêtes, typiquement sous l’identité www-data. La compromission d’un processus traitant une requête HTTP n’aboutit pas à la compromission totale du système. Plus de détails sur cette problématique ici.
  • sshd doit également se binder au port 22 inférieur à 1024. Cependant, la problématique est complexifiée par l’authentification, qui peut nécessiter l’accès au fichier /etc/shadow par exemple. Également, le démon sshd doit utiliser la commande login pour effectivement logguer l’utilisateur… Cette commande ne peut être utilisée que par l’utilisateur root. L’architecture, illustrée ici, est du coup plus complexe. Le processus sshd privilégié écoute sur le port 22 et fork lors de la connexion d’un utilisateur un processus non-privilégié (que ps affiche avec le label [net]) pour la gérer. Ce processus valide l’identité vis-à-vis du processus privilégié (qui typiquement a les droits de lire dans /etc/shadow, et dans les ~/.ssh/authorized_keys des utilisateurs). Si cette identité est validée, le processus privilégié fork un second processus non privilégié mais fonctionnant sous l’identité de l’utilisateur authentifié.

Les capabilities permettent de diviser les privilèges traditionnellement associés à root en unités distinctes. Ces capabilities peuvent être gérées de manière indépendante. Elles mettent en place un système générique pour attribuer le privilège supplémentaire dont une application a besoin pour fonctionner sans pour autant lui attribuer la totalité des droits root. Une courte mais très intéressante introduction peut être trouvée ici (blog.siphos.be)

Les capabilities permettent un contrôle plus fin des droits d’administration. Il s’agit d’éviter d’avoir recours à l’utilisation du compte root (sudo ou setuid root). Il suffit d’attribuer une capability donnée et limitée à un binaire pour permettre l’exécution dans un contexte (presque) non privilégié.

La liste complète des capabilities peut être obtenue via capabilities(7). Cependant, lors de l’utilisation de cette fonctionnalité, plutôt que d’utiliser le bit setuid root, de très nombreux développeurs associent directement la capability CAP_SYS_ADMIN, privilège équivalent aux droits root. Ceci n’aboutit qu’à déplacer le problème. Outre CAP_SYS_ADMIN, de nombreuses capabilities sont finalement équivalentes aux privilèges de root. Cet article permet de lister ces capabilities et notamment celles qui sont équivalentes aux privilèges de root.

Utilisation pratique des capabilities

Il est important de noter que les capabilities sont gérées par thread : À chaque thread est associé un ensemble de 4 drapeaux :

  • Effective E : Le système vérifie si le bit est égal à 1 pour la capability considérée et autorise ou non le processus à l’utiliser. Un peu équivalent à placer un bit suid root mais uniquement pour la capability en question,
  • Permitted P : Les capabilities que le processus peut acquérir, c’est un sur-ensemble d’Effective,
  • Inheritable I : Les capabilities qui seront propagées aux processus fils lors de forks,
  • bset

Afin de manipuler ces capabilities, deux appels systèmes sont définis :

  • capset,
  • capget.

Pour lister les capabilities associés au processus courant :

1
2
3
4
5
6
7
8
9
10
11
12
13
# grep ^Cap /proc/$$/status
grep ^Cap /proc/$$/status
CapInh:  0000000000000000
CapPrm:  0000003fffffffff
CapEff:  0000003fffffffff
CapBnd:  0000003fffffffff
CapAmb:  0000000000000000
$ grep ^Cap /proc/$$/status
CapInh:  0000000000000000
CapPrm:  0000000000000000
CapEff:  0000000000000000
CapBnd:  0000003fffffffff
CapAmb:  0000000000000000

Pour obtenir la liste des capabilities à partir de ces valeurs :

1
capsh --decode=0000003fffffffff

La généralisation des capabilities aux fichiers est apparue plus tard, à partir du noyau 2.6.24 (il y a plus de 10 ans!). Il a fallu d’abord attendre que le système de fichiers supporte l’ajout d’attribut étendus au niveau des inodes. À partir de là, un processus qui lance un exécutable gagne les capabilities associés à l’exécutable lancé.

Pour lister les capabilities de l’ensemble des fichiers :

1
getcap -r / 2>&1 | grep -v "Failed to get capabilities of file"

Transformation des capabilities lors d’un execve()

L’algorithme suivant est utilisé par le noyau pour calculer les nouvelles capabilities du processus (cf. man capabilities) :

1
2
3
4
 * P'(ambient)     = (file is privileged) ? 0 : P(ambient)
 * P'(permitted)   = (P(inheritable) & F(inheritable)) | (F(permitted) & cap_bset) | P'(ambient)
 * P'(effective)   = F(effective) ? P'(permitted) : P'(ambient)
 * P'(inheritable) = P(inheritable)    [i.e., unchanged]

ce mécanisme est également décrit dans cette super réponse sur stackoverflow).

Utilisation pour la commande ping

La commande ping nécessite l’utilisation des raw sockets, et pour cela doit avoir des privilèges supplémentaires. Suivant la distribution, l’attribution de ces privilèges pour permettre à un utilisateur non privilégié d’utiliser cette commande suit différentes stratégies :

  • sous Debian, le binaire /bin/ping est setuid root, les droits 755 root:root ne permettant pas à un utilisateur quelconque de le modifié,
  • sous CentOS, le binaire /usr/bin/ping n’est pas suid root :
1
getcap /usr/bin/ping -> cap_net_admin,cap_net_raw+p

Sur CentOS, on peut aller plus loin et supprimer également la capability cap_net_admin :

1
setcap cap_net_raw=p /usr/bin/ping

Attention au fait que les mises à jour ce ce paquet vont réecraser ces attributs (stockés sur l’inode du fichier).

Utilisation pour wireshark

Il est possible de restreindre les privilèges associés au processus utilisant wireshark. L’objectif consiste à permettre à un utilisateur particulier d’exécuter ce binaire en utilisant le moins de privilèges possibles. Ceci permet de restreindre l’étendue d’une compromission, plutôt que de voir l’utilisateur root lancer ce binaire.

C’est déjà ce que propose Debian lors de l’installation de wireshark (plus précisément du package wireshark-common). Il est possible de reconfigurer cette disposition après installation :

1
2
3
4
5
6
7
dpkg-reconfigure wireshark-common
# Sous le capot :
set cap_net_raw,cap_net_admin=eip /usr/bin/dumpcap
addgroup wireshark
chown root:wireshark /usr/bin/dumpcap
chmod 750 /usr/bin/dumpcap
usermod -a wireshark $USER

Aller plus loin pour durcir OpenVPN

L’installation d’OpenVPN nécessite au préalable l’activation des dépôts epel-release sous CentOS (Extra Packages for Enterprise Linux) :

1
2
yum install epel-release
yum install openvpn

Par défaut, OpenVPN est démarré par l’utilisateur root, puisque l’outil peut avoir à créer des routes. Pour restreindre ses capabilities, il est nécessaire de savoir d’abord quelles sont celles qu’il doit utiliser.

Il n’existe pas de façon simple de lister les capabilities nécessaires à un programme pour fonctionner. La technique que j’emploie s’appuie sur systemtap et utilise le script container_check.stp récupéré sur github :

1
2
3
./container_check.stp \
   -DKRETACTIVE=100 \
   -c "openvpn --config /etc/openvpn/client_key.conf"

Les résultats suivants s’affichent une fois le processus openvpn terminé (Ctrl+C éventuellement) :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  executable:      prob capability

          ip:        cap_net_admin

     openvpn:        cap_net_admin

  executable,              syscall (       capability ) :            count
          ip,              sendmsg ( cap_net_admin ) : 3
          ip,               sendto ( cap_net_admin ) : 1
     openvpn,                ioctl ( cap_net_admin ) : 2

  executable,              syscall:            count

  executable,              syscall =            errno:            count
          ip,               access =           ENOENT:                9
     openvpn,               access =           ENOENT:               18
     openvpn,              connect =      ENETUNREACH:                1
     openvpn,              connect =           ENOENT:                1
     openvpn,               statfs =           ENOENT:                2
     openvpn,         rt_sigreturn =            EINTR:                1
     openvpn,                 poll =                 :                1
      stapio,         rt_sigreturn =            EINTR:                1
      stapio,               execve =           ENOENT:                2

On voit ici que l’exécutable OpenVPN nécessite la capability CAP_NET_ADMIN, mais qu’il en est de même pour ip.

Pour permettre l’utilisation à partir d’un utilisateur non-privilégié quelconque :

1
2
setcap cap_net_admin=pe /usr/sbin/openvpn
setcap cap_net_admin=pe /usr/sbin/ip

Configuration pour un utilisateur spécifique

Nous avons un peu affaire à un mode setuid-like. Pour ne permettre qu’à un utilisateur ciblé d’obtenir cette capability (par exemple un administrateur réseau), la technique est très bien expliquée sur stackoverflow et se résume dans le cas des distributions Debian-like à :

1
2
3
4
5
6
7
8
9
10
aptitude install libpcap-dev
setcap cap_net_admin=ie /usr/sbin/openvpn
setcap cap_net_admin=ie /usr/sbin/ip
# Ensuite, fichier /etc/security/capability.conf :
none *
cap_net_admin chibollo
# Enfin, chargement du module PAM imposé au login des utilisateurs
# pour la prise en compte du fichier /etc/security/capability.conf
# /etc/pam.d/login ajouter
auth required pam_cap.so

Dans le cas CentOS, la présence de SELinux complexifie légèrement la configuration.