Avec une installation Linux, la majorité de la sécurité s’effectue au travers de la gestion d’accès à un objet par un processus donné. Concrètement parlant, on assigne des droits d’écritures, lectures ou exécutions à un utilisateur via lequel une application est exécutée. Malheureusement, ce système montre vite ses limites dans la mesure ou l’affinement des autorisations ne peut se faire que sur trois niveaux : l’utilisateur propriétaire d’un fichier, le groupe propriétaire et tous les autres.
La gestion des accès n’est pas l’élément remis en question ici, mais plutôt la façon dont on les définit. On rencontre deux principaux problèmes. Premièrement, c’est à l’administrateur système d’estimer qui peut faire quoi alors qu’il n’est pas l’auteur de l’application. Il n’a donc pas forcément la sensibilité nécessaire pour effectuer correctement cette tâche. C’est ainsi que l’on se retrouve, par exemple, avec l’accès intégral à /etc en lecture pour tout le monde ou encore que l’on a un utilisateur par démon pour tenter de restreindre au moins les droits d’écriture de façon assez fine pour être utile. La notion de groupe, elle, semble souvent ignorée, ou pire, utilisée comme utilisateur de substitution. Un exemple ? On confine l’édition des fichiers de base de données à l’utilisateur MySQL, mais aussi au groupe backup, car on aimerait pouvoir effectuer correctement des restaurations. Les choses se retrouvent vite bancales quand plus de deux acteurs doivent modifier les données. — Le second problème réside dans les scénarios où nous pouvons avoir plusieurs applications exécutées par un utilisateur commun. En effet, dans ces cas, la compartimentation devient totalement vaine. Comment faire pour empêcher Firefox d’accéder sur notre ordinateur de bureau aux fichiers manipulés par Gnucash, le logiciel gérant notre compatibilité ? Les deux provenant du même utilisateur (celui s’ayant logué via un login manager), il n’y a aucun moyen pour différencier les droits d’accès.
Ce fonctionnement était acceptable à l’époque ou le nombre de services par ordinateur était faible, et surtout dans un monde déconnecté dans lequel ce genre de failles ne pouvaient que difficilement être exploitées. Malheureusement, ce n’est plus le cas aujourd’hui. Des solutions de consolidation ont donc été mises en place, comme GRsecurity, SElinux, seccomp, RSBAC ou encore AppArmor, la technologie dont j’aimerais m’attarder dans cet article.
L’instalation
AppArmor se présente sous la forme d’un module noyau qui complète la gestion classique des droits. Concrètement, l’administrateur assigne un profil à un logiciel donné qui pour le coup, sera indépendant du contexte : peu importe l’utilisateur chargé de l’exécution. Avant tout, il faudra veiller à ce que votre distribution possède bien les outils nécessaires. Sous Debian, il faut installer les paquets apparmor et apparmor-utils puis redémarrer. Si tout s’est bien passés, le lancement en root de la commande aa-status devrait avoir une sortie semblable à celle-ci :
# aa-status apparmor module is loaded. xx profiles are loaded. x profiles are in enforce mode. /usr/bin/lxc-start [...] x profiles are in complain mode. /sbin/klogd /sbin/syslog-ng [...] 0 processes have profiles defined. 0 processes are in enforce mode. 0 processes are in complain mode. 0 processes are unconfined but have a profile defined.
Pour illustrer le fonctionnement d’AppArmor, prenons l’exemple du script suivant :
#!/usr/bin/env perl open $file, '<', '/etc/ssh/sshd_config' or die $!; map {print} <$file>; sleep 60;
Fichier /opt/reader.pl
Celui-ci se charge simplement de lire la configuration de notre serveur ssh et de l’écrire sur stdout. On le fera ensuite attendre une minute pour nous laisser le temps de surveiller le processus. Il permet d’illustrer qu’à la moindre compromission d’un logiciel présent sur notre ordinateur auquel un vilain aurait accès, il pourrait facilement lire l’intégralité des fichiers de configuration (et probablement bien plus encore !).
# /opt/reader.pl # Package generated configuration file # See the sshd_config(5) manpage for details […] UsePAM yes
Pour protéger l’exécution de ce petit script à l’aide d’AppArmor, il nous faudra écrire le profile /etc/apparmor.d/opt.reader.pl. Il doit par défaut se trouver dans /etc/apparmor.d, par contre le nom du fichier n’est pas imposé. Par convention, il s’agit du chemin de l’exécutable qu’il représente avec des points en remplacement des slashs.
Contentez-vous pour le moment d’y ajouter le contenu suivant :
/opt/reader.pl { }
Fichier : /etc/apparmor.d/opt.reader.pl
Et d’exécuter la commande aa-complain /opt/reader.pl
. Vous venez de charger un profil pour notre application. La seule information que celui-ci possède réside dans le chemin de l’exécutable qu’il gère.
L’utilisation
La commande précédente a donc chargé un profil AppArmor pour notre petit logiciel /opt/reader.pl. Par défaut, une application n’a aucun droit, et le fait de l’avoir configuré en mode plaintif (via aa-complain) fait qu’AppArmor se contentera de journaliser l’accès aux ressources non autorisées plutôt que de réellement tout bloquer.
# tail -f /var/log/syslog | grep apparmor & /opt/reader.pl 1>/dev/null [1] 3208 Oct 25 19:39:24 Debian kernel: [ 2381.936333] audit: type=1400 audit(1477417164.940:47): apparmor="ALLOWED" operation="open" profile="/opt/reader.pl" name="/etc/ld.so.cache" pid=3202 comm="reader.pl" requested_mask="r" denied_mask="r" fsuid=0 ouid=0 Oct 25 19:39:24 Debian kernel: [ 2381.936505] audit: type=1400 audit(1477417164.940:48): apparmor="ALLOWED" operation="getattr" profile="/opt/reader.pl" name="/etc/ld.so.cache" pid=3202 comm="reader.pl" requested_mask="r" denied_mask="r" fsuid=0 ouid=0 Oct 25 19:39:24 Debian kernel: [ 2381.936661] audit: type=1400 audit(1477417164.940:49): apparmor="ALLOWED" operation="open" profile="/opt/reader.pl" name="/lib/x86_64-linux-gnu/libc-2.19.so" pid=3202 comm="reader.pl" requested_mask="r" denied_mask="r" fsuid=0 ouid=0 Oct 25 19:39:24 Debian kernel: [ 2381.936813] audit: type=1400 audit(1477417164.940:50): apparmor="ALLOWED" operation="getattr" profile="/opt/reader.pl" name="/lib/x86_64-linux-gnu/libc-2.19.so" pid=3202 comm="reader.pl" requested_mask="r" denied_mask="r" fsuid=0 ouid=0 Oct 25 19:39:24 Debian kernel: [ 2381.936958] audit: type=1400 audit(1477417164.940:51): apparmor="ALLOWED" operation="file_mmap" profile="/opt/reader.pl" name="/lib/x86_64-linux-gnu/libc-2.19.so" pid=3202 comm="reader.pl" requested_mask="mr" denied_mask="mr" fsuid=0 ouid=0 […]
Ces informations sont très utiles pour l’élaboration concrète du profil, car nous voyons exactement les points qui posent problème. Il nous faudra à présent manipuler le programme de façon exhaustive pour couvrir le plus de comportements possibles et être ainsi certain d’avoir récupéré la liste de tous les droits à obtenir. On aura plus qu’à rédiger les règles correspondantes. Des commandes comme aa-genprof nous permettent d’être assisté dans cette tâche et de rendre l’élaboration de ces règles interactive. Bien qu’il soit du coup plus simple d’utiliser ce genre d’outils, je trouve que rien ne vaut l’écriture manuelle pour les affiner au maximum et en profiter pour en même temps, auditer le programme.
Pour notre logiciel de test, voici un profil normalement fonctionnel sur Debian 8.6 :
/opt/reader.pl { #Nécessaire pour Perl /dev/urandom r, /etc/ld.so.cache r, /etc/locale.alias r, /etc/perl/sitecustomize.pl r, /lib/x86_64-linux-gnu/libc-* rm, /lib/x86_64-linux-gnu/libdl-* rm, /lib/x86_64-linux-gnu/libm-* rm, /lib/x86_64-linux-gnu/libcrypt* rm, /lib/x86_64-linux-gnu/libpthread-* rm, /usr/bin/perl ix, /usr/lib/locale/locale-archive r, /usr/lib/x86_64-linux-gnu/libperl.so* rm, #Nécessaire pour l'application /opt/reader.pl r, /etc/ssh/sshd_config r, }
Fichier : /etc/apparmor.d/opt.reader.pl
Pour plus d’informations concernant la syntaxe des profils, je vous invite à lire la page de manuel apparmor.d, qui me semble plutôt complète. On pourrait par exemple y découvrir que AppArmor supporte la factorisation de règles. Du coup, on pourrait écrire celle de notre application sous cette forme :
/opt/reader.pl { #include <abstractions/perl.aa> #Nécessaire pour l'application /opt/reader.pl r, /etc/ssh/sshd_config r, }
Fichier : /etc/apparmor.d/opt.reader.pl
De cette façon, les règles propres à l’utilisation de Perl peuvent éviter de polluer visuellement celles spécifiques à notre application et peuvent être partagées !
Une fois le profil rédigé, ou obtenu, il faudra passer en mode exécution pour véritablement protéger le système : aa-enforce /opt/reader.pl
.
Si à ce moment, un accès n’est pas explicitement inscrit dans le profil, l’application voit sa demande refusée, ce qui souvent nuira au bon fonctionnement de celle-ci, ou la fera carrément lamentablement planter. Si ça arrive, notez qu’AppArmor continuera à journaliser ce genre de comportement. Il vous suffira donc de retourner lire les logs et d’affiner les réglages.
Le partage
La forme des profils AppArmor incite volontairement à les partager. Certaines applications les fournissent donc directement, car les développeurs sensibilisés prennent parfois la peine de les écrire. Après tout, ce sont les mieux placés pour ce boulot. 🙂 Sur la même optique, on trouve des lots de profils par distribution. Ainsi, vous pouvez installer les paquets apparmor-profiles et apparmor-profiles-extra sur Debian pour récupérer ceux créés par la communauté.
Un autre avantage indirect pour l’utilisateur réside dans le fait que les règles étant claires, il devient aisé pour lui de voir avec précision ce que l’application qu’il s’apprête à installer pourra véritablement faire sur son ordinateur.
Le monitoring
Le gros point faible d’AppArmor réside dans le fait qu’une application sans profil est libre de tout faire. Pire : une application pourtant verrouillée, mais qui n’est pas à l’emplacement spécifié est elle aussi sans le moindre confinement.
Imaginez vouloir récupérer la liste de tous les systèmes de fichiers montés sur l’ordinateur à la place de la configuration de sshd via notre logiciel de test. Si vous remplacez le chemin et que vous l’exécutiez, vous aurez droit, comme prévu, à un crash car la lecture du fichier /proc/mounts ne vous est pas permise. Par contre, il suffira de renommer l’exécutable pour contourner le problème.
Pire : nous avons confiné /opt/reader.pl, et pas le code concret qui le constitue. De cette façon, un proxy pourra encore exécuter les instructions sans le moindre contrôle, en passant par exemple par la commande perl… :
# /opt/reader.pl Permission denied at /opt/reader.pl line 3. # ln /opt/reader.pl /opt/reader_bypass.pl # /opt/reader_bypass.pl rootfs / rootfs rw 0 0 sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0 proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0 […] # perl /opt/reader.pl rootfs / rootfs rw 0 0 sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0 […]
Pour utiliser AppArmor de façon efficace, il faut donc rester extrêmement vigilant vis-à-vis des applications pouvant être lancées sur le système… Pour nous aider dans la découverte de failles par non-confinement, l’utilitaire aa-unconfined peut nous donner de précieuses indications en nous listant les logiciels installés ayant accès à l’extérieur et ne possédant aucune restriction.
Pour encore plus d’affinement, vous pouvez employer l’option -Z
de ps
pour voir les informations concernant les profils utilisés par tous les processus en cours d’exécution :
# ps aux -Z | grep unconfined unconfined root 1 0.0 0.1 15492 1800 ? Ss 18:06 0:00 init [2] unconfined root 2 0.0 0.0 0 0 ? S 18:06 0:00 [kthreadd] […]
Et voilà ! Vous avez toutes les billes pour commencer à vous amuser avec cet outil à la fois simple et efficace !
Crédits images :
- Le labyrinthe en couverture par kokoda39.
Publication originale :