Skip to main content

Système des capabilities Linux

Introduction et principes généraux des Capabilities

Lorsque l’on veut lancer un processus avec certains droits on peut soit le lancer en tant que root, ce qui est dangereux car le processus a maintenant tous les droits, soit lui attribuer certaines capabilities. Les capabilities ont été créées à la fin des années 90 et sont une façon, en apparence simple, de diviser les droits du root pour n’attribuer que les autorisations voulues (le terme "en apparence" sera justifié par la suite). Ceci permet de respecter le principe du moindre privilège, c’est à dire n’attribuer à un processus que les droits dont il a besoin pour éviter qu’il est trop de "pouvoir". Cette logique s’inscrit dans le système DAC (Discretionary Access Control) qui consiste à confiner les utilisateurs et processus en ne leur attribuant que les droits nécessaires#1. Par exemple, la capability CAP_NET_BIND_SERVICE autorise l’attachement d’un socket aux ports inférieurs à 1024, ce qui normalement n’est autorisé qu’aux processus systèmes. Il faut voir les capabilities comme une granularité du root.

Prenons un exemple simple : si j’essaie d’exécuter ‘tcpdump’ dans un terminal je vais avoir une erreur car je n’ai pas la permission :

image-1588973903658.png
Maintenant, si je lance avec un sudo cela va marcher, mais en examinant le statut du processus on voit que tcpdump a absolument tous les droits, ce que viole la règle du moindre privilège !

image-1588974025207.pngLa solution est donc de donner juste les capabilities nécessaires au fonctionnement de tcpdump. En l’occurrence, tcpdump a besoin de cap_net_raw pour fonctionner. Voici ce que ça donne :

image-1588974114776.pngimage-1588974131421.pngOn voit la valeur 2000. Si on tape la commande ‘capsh –decode=2000’ pour la décoder on obtient bien cap_net_raw.

image-1588974235999.png

Mais que signifie ces CapInh, CapPrm et autres CapEff dans le statut du processus ? Ou le +ep dans la mise en place des capabilities ? Que se passe t’il si un processus engendre un processus fils ? Tant de questions auxquelles je vais tenter de répondre dans cet article. Je vais dans un premier temps expliquer de manière détaillée le fonctionnement des ensembles de capabilities ; dans un second temps éclaircir le système d’héritage ; et je finirai sur un exemple vraiment complet avec la modification du module login. Des captures d’écrans de la console et de morceaux de codes seront à chaque fois présents pour illustrer mes explications.


Actuellement la gestion des capabilities sur un processus fonctionne avec 5 ensembles : Permitted, Effective, Inheritable, Ambient et le Bounding Set. Nous allons, dans un premier temps, nous intéresser à Permitted et Effective.

Permitted

Permitted est l’ensemble de toutes les capabilities qui peuvent être attribuées à un processus à tout moment par le système. Pour modifier l’ensemble Permitted on va utiliser une autre facette des capabilities, qui sont les capabilities de fichiers. En effet, chaque fichier binaire (donc exécutable) possèdent lui aussi les ensembles Permitted, Effective et Inheritable (en réalité, Effective pour fichier n’est pas un ensemble mais un bit d’activation pour les capabilities, mais cela ne change pas grand-chose pour l’utilisation. C’est surtout intéressant à savoir si on s’intéresse à la gestion des capabilities par le système).

Le principe est simple : on utilise la commande bash ‘setcap’ (permet d’attribuer des capabilities aux fichiers binaires de cette facon : sudo setcap cap1,cap2+eip fichier) qui permet d’attribuer des capabilities aux fichiers. Comme vu précédemment, pour utiliser le service tcpdump il faut que le processus possède la capability cap_net_raw. On va donc, dans un terminal, taper la commande ‘sudo setcap cap_net_raw+ep /usr/sbin/tcpdump’. Le + signifie que l’on ajoute la capability et le ep signifie qu’on l’ajoute dans le Permitted et Effective (on peut donc faire +eip pour ajouter dans les 3 ensembles).

Par contre, il existe une règle très importante sur le système des capabilities qui nous suivra tout le long : lorsqu’un setuid d’un utilisateur root vers un non-root est effectué tous les ensembles de capabilities sont effacés par sécurité...sauf à une seule et unique condition : avec la primitive ‘prctl’ (Prctl est une primitive de sys/prctl.h donnant accès à certains aspects de la gestion des processus. Je conseille de lire le manuel de la commande pour en savoir plus) il est possible de placer un SECUREBIT. Ce bit va permettre de conserver Permitted à travers le setuid. Cependant, le bit sera effacé au moment du setuid (utilisation unique), il faudra donc le replacer pour chaque setuid prévus dans la suite du programme. Ceci est important à retenir, ce bit aura un rôle très important par la suite. Ici, un exemple de passage de l’utilisateur 0 (root), vers le 1000 (mon numéro, non root):

image-1588974489219.pngimage-1588974505379.png                                                                  Avant

image-1588974500200.pngAprès

Maintenant avec le SECUREBIT positionné :

image-1588974629040.pngimage-1588974632029.pngAprès changement d'ID

On voit très bien la conservation de l’ensemble Permitted à travers le setuid.

Attention : il est impossible de modifier l’ensemble Permitted d’un processus en cours d’exécution. Les librairies libcap et libcap-ng devraient le permettre grâce à certaines primitives, mais je n’ai jamais réussi à le faire. Voici un code qui devrait permettre de modifier Permitted en cours d’exécution, ainsi que les valeurs de l’ensemble Permitted avant et après modification. Avant exécution Permitted ne possède que cap_sys_nice, le code devrait rajouter cap_net_raw. Aucun changement n’a lieu, la valeur reste à 800000:

image-1588974837716.png

image-1588974855538.png

image-1588974858131.png

Je ne sais pas s’il s’agit d’un bug ou si cela est voulu, mais cela reste néanmoins étrange et je n’ai trouvé aucune explication concrète à ceci. Je pars donc du principe que Permitted est invariant durant l’exécution.

Effective

L’ensemble Effective représente les capabilities qui sont effectivement attribuées au processus à un instant t, c’est à dire les capabilities qui sont en effet exploitable. Effective est un sous ensemble de Permitted ce qui signifie qu’une capability présente dans Effective est obligatoirement présente dans Permitted. Effective est un ensemble modifiable pendant l’exécution du programme. Cela signifie que deux solutions s’offrent à nous pour le modifier : soit utiliser setcap comme pour Permitted, les capabilities seront ainsi présentes dans Effective dès le début de l’exécution, soit utiliser les primitives des librairies libcap et libcap-ng (libcap et libcap-ng sont les librairies en C permettant de travailler sur la structures des capabilities de manière simplifiée, libcap-ng est une variante de libcap rendant les modifications encore plus aisées) dans le code du programme et ainsi modifier les capabilities en cours d’exécution.

Un exemple de code qui permet de mettre cap_net_raw dans Effective en cours d’exécution. La capability était présente dans Permitted auparavant avec ‘setcap’. Ensuite, deux captures de la console pour montrer l’apparition de la capability dans Effective :

image-1588975004718.png

Avant                                               Après

image-1588975009842.pngimage-1588975015704.png

En résumé, nous avons deux ensembles : Effective qui représente toutes les capabilities qui peuvent être utilisées à cet instant, et Permitted qui représente toutes les capabilities qui pourront être acquises ou héritées par le processus pendant l’exécution. Hérité ? J’ai déjà utilisé ce mot dans l’introduction, mais de quoi s’agit-il ? Et bien, nous allons voir ça tout de suite ! Et c’est ce qui va constituer la plus grande partie de ce rapport.

Alors, l’héritage qu’est-ce que c’est ?

 

Le principe d'héritage

Durant l’exécution d’un processus il peut arriver que ce dernier souhaite lancer un processus fils à partir d’un fichier binaire à l’aide d’une primitive de type ‘exec’ (en réalité, ceci arrive très souvent, il suffit de regarder l’arborescence avec la commande ‘pstree’ pour s’en rendre compte). A partir de là, quelles seront les capabilities du processus fils ? Celles du fichier binaire ? Celles du processus père peut-être ? Et bien c’est un peu plus compliqué que cela, et c’est là que les ensembles Inheritable et Ambient entrent en jeu. Pour l’instant nous allons nous concentrer sur Inheritable.

Inheritable

Inheritable représente l’ensemble des capabilities que le processus père va pouvoir transmettre à son fils. Comme Effective, cet ensemble ne peut contenir que les capabilities se trouvant dans Permitted (donc le processus père peut, au maximum, donner les capabilities qui lui sont permises), sauf à une seule et unique condition : si la capability CAP_SETPCAP se trouve à la fois dans Permitted ET dans Effective, Inheritable pourra contenir des capabilities non présentes dans Permitted. Ceci ne fonctionne que pour Inheritable, pas pour Effective (et ne permet pas non plus de modifier Permitted en cours d’exécution).

La modification d’Inheritable est un peu plus délicate que pour les ensembles précédents et pourrait presque sembler buguée. Pour donner des capabilities Inheritable au fichier binaire qui sera exécuté dans le exec un simple ‘setcap + i’ suffit (avec la capability et le fichier voulu, bien entendu). Cependant, il faut que le processus père ait aussi des capabilities Inheritable. On pourrait penser que mettre des capabilities dans le Inheritable de son binaire puis de le lancer donnera les capabilities voulues au processus… Et bien non ! En réalité le binaire a les capabilities dans Inheritable (on le voit en faisant un ‘getcap’ dessus), mais le processus résultant ne les a pas, et ne peut donc pas les transmettre au fils lors du exec.

Voici les capabilities d’un processus pour lequel j’ai mis des capabilities dans eip. On voit qu’Inheritable n’a pas récupéré la capability :

image-1588975324300.png

La seule solution pour ajouter des capabilities dans l’ensemble Inheritable d’un processus qu’on vient de lancer est de le faire pendant l’exécution de ce dernier à l’aide de libcap et libcap-ng (encore et toujours). Voici un exemple de remplissage d’Inheritable sans SETPCAP (donc la capability est déjà dans Permitted) :

image-1588975384484.pngimage-1588975388263.png

Et un exemple avec SETPCAP :

image-1588975460948.pngimage-1588975463686.png

Au lancement seulement SETPCAP et FOWNER sont présentes dans Permitted et Effective (FOWNER est juste là pour l’exemple, il n’a pas d’utilité ici). On voit sur la deuxième capture que j’ai réussi à passer CAP_SYS_NICE dans Inheritable. Et ceci grâce à SETPCAP et ce code :

 

Bon, c’est bien beau tout ça, on sait comment remplir l’ensemble Inheritable d’un processus qu’on veut lancer...mais maintenant, à quoi sert concrètement cet ensemble ? Il va entrer en jeu dans le calcul suivant, qui permet de connaître les capabilities qu’aura le fils :

P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & cap_bset)

P'(effective) = F(effective) ? P'(permitted) : 0

P'(inheritable) = P(inheritable)

Dans ce calcul, P’ représente le fils, P le père et F le fichier binaire (on retrouve le fait que l’Effective du fichier n’est qu’un bit activé ou non).

cap_bset représente quant à lui le Bounding Set dont j’ai parlé au début. Il s’agit de l’ensemble maximal des capabilities que le système peut attribuer à un processus. Cet ensemble permet simplement de créer des systèmes restreint.

Voyons ce que ça donne concrètement. J’ai donc un programme auquel je donne CAP_SYS_NICE dans le Permitted avec ‘setcap’. Dans le code de ce programme je mets cette même capability dans Inheritable puis je lance un processus fils à l’aide d’un execve sur un binaire possédant la même capability dans Inheritable et Effective. Le processus fils ne fait rien (juste un sleep(20)). Voici le code du processus père et les capabilities du fils :

image-1588975611841.png

image-1588975772250.png

Si on regarde les règles de calculs ainsi que les ensembles où j’ai placé les capabilities on voit que cela marche parfaitement. Il est maintenant l’heure de nous attaquer au deuxième ensemble gérant l’héritage, Ambient. Un deuxième ? Pourquoi faire ?

Ambient

A l’origine seuls les trois ensembles vu précédemment existaient. Seulement, un certain nombre de défauts sont apparus et la création d’un nouvel ensemble avec un fonctionnement différent est devenu nécessaire. Ambient a donc vu le jour en 2015. Pour expliquer la création de l’Ambient, voici une liste sans doute non exhaustive des problèmes :

  • Si le bit Effective du fichier n’est pas activé, aucune capabilities ne pourra aller dans l’Effective du processus fils… Certe le Permitted pourra se remplir mais si l’Effective non ce n’est pas très utile
  • Les capabilities des fichiers exploitent les Extends Attributs (xattrs), sauf que certains fichiers n’exploitent pas ces attributs donc impossible d’y mettre des capabilities. On voit vite le problème au moment du calcul des transmission de capabilities. Il en résulte qu’il est impossible d’exécuter certaines commandes sans être root, comme la commande ‘ping’ par exemple, qui exécute certains fichiers n’exploitant pas les xattrs (on trouve un setuid(0) dans son code)
  • Généralement, si on n’est pas root l’Inheritable du fichier est égale à 0. Soit pour la raison précédemment énoncée, soit parce que les développeurs n’ont pas toujours pensé à mettre toutes les capabilities nécessaires dans leurs binaires. Une solution pourrait être de remplir complètement tous les fI, mais ce n’est pas satisfaisant d’un point de vue sécurité

Ambient a pour objectif de régler ces problèmes, et va permettre de faire ce que l’héritage précédent était censé permettre de faire. Voici donc les règles de calculs maintenant :


P'(ambient) = (file get caps) ? 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)

Maintenant, Ambient est toujours ajouté à Permitted et Effective, permettant un héritage permanent. Surtout qu’Ambient peut être modifié sans problème en cours d’exécution grâce à la primitive ‘prctl’, de la même façon qu’Inheritable avec libcap ou libcap-ng. Voici le lien vers un blog sur lequel se trouve un très bon code pour tester Ambient :

Reprenons le code pour tester Inheritable, mais en rajoutant l’ajout dans Ambient :

 

image-1588976098451.pngimage-1588976102746.png

Par contre, comme on le voit dans le calcul de l’Ambient du fils, pour qu’il récupère l’Ambient du père il faut que le fichier binaire ne contienne absolument aucune capabilities ! (La commande ‘sudo setcap -r nomDuFichier’ permet de les effacer). On retrouve dans cette règle la logique qui a amené à la création d’Ambient : les fichiers n’ayant pas de capabilities bloquent la transmission.

De plus, Ambient respecte l’invariant comme quoi il ne peux contenir que les capabilities se trouvant à la fois dans Permitted et Inheritable, impossible autrement. Une tentative de remplir Ambient avec Inheritable manquant :

image-1588976142179.png

Une petite précision : même si Ambient est un ensemble servant à l’héritage, la capability SETPCAP n’agit pas dessus. Même en la positionnant dans Permitted et Effective il est impossible de passer des capabilities absente de Permitted dans Ambient.

Le code suivant ajoute SETPCAP à Effective (Permitted est ajouté avec ‘setcap’) puis tente d’ajouter une autre capability à Inheritable et Ambient. La première capture de console montre le statut du processus au début et la deuxième après l’ajout de la deuxième capability : Inheritable s’ajoute bien (comme vu précédemment) mais Ambient renvoie une erreur.

image-1588976265895.png

image-1588976270482.png

image-1588976273790.png

Enfin, lorsque qu’un setuid allant d’un id root vers un id non root est effectué Ambient est inévitablement effacé, impossible de le conserver (mais si Permitted a été conservé avec un SECUREBIT, il est toujours possible de le remplir après le setuid).


Maintenant qu’Ambient existe et semble bien fonctionner, on est en droit de se demander à quoi sert encore Inheritable et pourquoi il n’a pas été supprimé. Et bien, au vu de mes recherches et de mes tests, je pense effectivement qu’Inheritable ne sert plus à grand-chose… Il est toujours là car une phase de transition est toujours nécessaire et ce système est très important sur les kernels Unix. C’est aussi pour ça qu’Ambient a toujours besoin d’Inheritable pour pouvoir se remplir, mais je pense que ce dernier est amené à disparaître.

Il ne reste plus qu’une chose à voir avant de passer à l’exemple concret : les capabilities d’utilisateur.

 

Les capabilities d'utilisateur

Et oui, je n’en ai pas encore parlé pour ne pas tout mélanger, mais les utilisateurs peuvent aussi avoir des capabilities qui leur sont attribuées. En théorie (le mot théorie est important ici, on va y revenir plus tard) cela permet de donner des capabilities au programme login lorsque l’utilisateur s’authentifie (login récupère les capabilities de l’utilisateur). Ensuite login exécute le bash, qui devrait donc irriter de ses capabilities et ainsi, tous programmes lancé par l’utilisateur depuis le bash devrait avoir les capabilities du bash (donc de l’utilisateur).

Pour donner des capabilities à un utilisateur, rien de bien compliqué : il suffit de modifier le fichier capability.conf se trouvant généralement dans le dossier /etc/security. La modification se fait comme les exemples déjà positionnés : pour chaque utilisateur, sur une ligne mettre la suite de capabilities voulues séparées par des virgules, suivi du nom de l’utilisateur :

image-1588976406384.png

La lecture dans ce fichier se fait lorsque login appelle PAM.

PAM ( Pluggable Auhentification Modules) est un système permettant, je cite «l’intégration de différents schémas d'authentification de bas niveau dans une API de haut niveau, permettant de ce fait de rendre indépendants du schéma les logiciels réclamant une authentification » (https://fr.wikipedia.org/wiki/Pluggable_Authentication_Modules). Pour faire plus simple, cela permet, lors de l’authentification de l’utilisateur, de charger et d’attribuer à chaque service ses droits et autorisations. De plus, PAM permet de modifier très facilement tous ces paramètres sans avoir à recompiler tous le programme, et c’est indépendant du kernel ! Chaque service voulant disposer de PAM possède un fichier dans /etc/pam.d où tous les paramètres et librairies à appeler sont rentrés.

Donc, lorsque login appelle PAM il fait appelle à la librairie pam_cap.so qui devrait permettre de lui donner les capabilities et ensuite pouvoir les transmettre. Mais comme je l’ai dis, Ambient est récent et n’est donc pas encore implémenter partout, laissant certains services sans possibilité d’héritage...et c’est le cas du login. On a beau rentrer toutes les capabilities que l’on veut dans capability.conf, aucune n’est récupérée par le bash. On essaie avec CAP_NET_RAW pour tcpdump :

image-1588976638126.png

Comme on le voit, tcpdump ne fonctionne pas, alors que l’utilisateur devrait avoir la capability requise. On est donc toujours obligé de faire un ‘setcap cap_net_raw+ep’ pour avoir tcpdump...pas très satisfaisant.

Je vais exploiter cette situation dans un exemple concret et implémenter Ambient dans le module login et pam_cap.so, et ainsi permettre au login de donner ses capabilities au bash. Une fois les capabilities dans l’Ambient du bash tous les programmes lancés par l’utilisateur auront ses capabilities.

 

Exemple concret : le module login

Comme dit précédemment, login utilise la librairie pam_cap.so pour obtenir ses capabilities. Il convient donc d’aller regarder le code de cette librairie se trouvant dans le package libcap pour comprendre ce qu’il s’y passe. Voici donc des captures d’écrans du code mettant en place les capabilities ainsi que les explications nécessaires :

image-1588976788326.pngcap_s récupère les capabilities du processus et alloue la mémoire nécessaire à la structure,

conf_icaps va récupérer les capabilities de l’utilisateur dans le fichier capability.conf grâce à la fonction read_capabilities_for_user écrite juste avant (j’ai fais de multiples tests dessus, elle fonctionne parfaitement),

proc_ecaps va récupérer la version textuelle de cap_s pour que ce soit plus facilement exploitable

 

image-1588976888627.pngcombined_caps est un string qui va récupérer les capabilities que l’on va donner au processus. Pour cela il va faire un "mix" entre les valeurs de proc_ecaps et celles de conf_icaps suivant les valeurs de conf_icaps,

cap_s récupère ensuite la valeur de combined_caps sous forme de structure de capabilities, se trouve ensuite un bloc servant au débogage.

 

image-1588977049702.pngIci nous trouvons la partie où les capabilities cap_s sont mises en place, puis les goto qui permettent le nettoyage de la mémoire.

Nous constatons donc deux choses : premièrement la primitive cap_set_flag qui permet de choisir l’ensemble dans lequel mettre les capabilities n’est pas utilisée. La mise en place dans Inheritable risque de ne pas avoir lieu. Deuxièmement, aucune mise en place dans Ambient n’a lieu avec ‘prctl’. Il convient donc de commencer par remédier à cela : je vais d’abord faire en sorte de récupérer les capabilities sous forme de liste pour pouvoir les mettre dans un cap_set_flag et ainsi remplir Inheritable. Je vais ensuite m’occuper de remplir Ambient avec ‘prctl’. Voici donc mon code :

image-1588977163120.pngcap_s récupère toujours les capabilities du processus

conf_icaps récupère toujours celle du fichier de configuration

proc_epcaps récupère toujours la version textuelle de cap_s.

 

image-1588977321596.pngJe vais faire le mix de proc_epcaps et conf_icaps en fonction des valeurs de conf_icaps. Si conf_icaps est à none, la liste prend NULL, si il est à all, la liste prend tout proc_epcaps. Les boucles avec le strtok sont là pour ne récupérer que les capabilities dans proc_epcaps (proc_epcaps est un string de la forme "= cap,cap,cap+eip").

 

image-1588977377055.pngSi le fichier de configuration contient des capabilities précises je crée une première liste qui va contenir les capabilities du fichiers, et une deuxième liste qui récupère les capabilities du processus.

 

image-1588977458289.pngEt maintenant je rempli la liste finale en prenant les valeurs présentent dans le fichier de configuration et le processus.

Je peux ensuite positionner les flags sur Permitted et Inheritable, et faire le set_proc pour donner les capabilities au processus.

 

image-1588977540096.pngJe passe à la mise en place d’Ambient.

Pour cela, je récupère la liste finale des capabilities sous forme de string.

Dans ce string les capabilities sont de la forme "cap_nomCap", sauf que dans le fichier permettant d’obtenir l’entier correspondant à chaque capability, les #define sont de la forme NOMCAP, sans le CAP_. Il me faut donc retirer le cap_ devant chaque capability.

Une fois ceci fais, je récupère l’entier correspondant à chaque capability grâce à la primitive capng_name_to_capability de la librairie libcap-ng.

Je peux ensuite faire un ‘prctl’ pour mettre la capability dans Ambient.

La liste des #define et #include pour mon code sera en Annexe.

Je teste mon nouveau code dans un fichier de test et je compare les résultats avec ceux de l’ancien code. Effectivement, maintenant ça marche : Inheritable et Ambient sont remplis.

image-1588977672623.pngimage-1588977675567.png

Avant                                             Après

Bien, maintenant que ce code a été modifié, il est temps de l’implémenter.

Attention : je vous conseille très fortement d’effectuer ce qui va suivre dans une machine virtuelle et de régulièrement faire des snapshots...ça vous évitera de rester enfermer en dehors de votre session.

Pour compiler et installer, suivez les instructions du README du package libcap. Ensuite, il va falloir modifier l’appel à pam_cap.so dans PAM. L’appel à pam_cap.so se trouve sur la couche auth de PAM, dans le fichier common-auth (dossier /etc/pam.d), et l’appel à common-auth dans login est fait sous la forme d’un include :

image-1588977909297.png/etc/pam.d/common-auth

image-1588977913008.png/etc/pam.d/login

Une fois le nouveau pam_cap.so installé (en suivant le méthode du README), la librairie se trouve dans /lib64/security. Il est donc nécessaire de modifier common-auth :

image-1588978039714.pngJe conseille de laisser optional, ça évitera que login plante pour un module qui ne le nécessite pas.

Nous avons finit d’implémenter notre nouvelle librairie, il est temps de tester ça. Pour se faire, nous allons devoir passer en mode console (le passage en mode console sera expliqué en annexe). En effet, l’utilisation de l’interface graphique fait que login n’est pas utilisé, c’est un dérivé qui est lancé (systemd-logind), et il change suivant la distribution Linux utilisée. Mes modifications ne sont donc fonctionnelles qu’en mode console.

On redémarre donc la VM en console et lorsque l’on regarde le statut du processus bash...et bien nous n’avons pas les capabilities !

image-1588978118062.pngCela signifie donc que les capabilities ont été perdues à un moment. Il faut donc aller regarder le code de login. Le code de ce programme est beaucoup trop long pour que je l’analyse en détail ici, mais si on regarde dans le main on constate quelque chose de très intéressant : un setuid de 0 vers celui de l’utilisateur a lieu, entraînant ainsi la perte de capabilities.

image-1588978280167.png

C’est là que le SECUREBIT va entrer en jeu. Il va nous permettre de conserver Permitted à travers le setuid, et nous pourrons ensuite remplir Ambient et ainsi transmettre au bash. En observant le code on voit qu’une fonction nommée fork_session est appelée avant le setuid. Allons voir ce qu’il s’y passe :

image-1588978319075.pngici, dans le if du père, on trouve un appel à pam_setcred, qui est une implémentation de la fonction pam_sm_setcred se trouvant dans la librairie pam_cap et gérant les credentials. Ceci ayant lieu juste avant le setuid, il est intéressant d’y implémenter le SECUREBIT.

Voici donc la modification de la fonction pam_sm_setcred pour activer le bit (en fin de fonction, juste avant le return) :

image-1588978409173.pngMaintenant que le Permitted est conservé il ne reste plus qu’à remettre Ambient dans login. Pour cela je vais refaire la même chose que dans pam_cap et appeler ma fonction après le setuid :image-1588978475114.png

image-1588978503623.png

Attention : pensez à modifier le fichier Makemodule.am pour prendre en compte libcap et libcapng avec -lcap-ng -lcap :

image-1588978568095.png

Une fois le nouveau login compilé et installé on peut vérifier le statut du bash en positionnant cap_net_raw dans capability.conf...et ça marche !

Et voici tcpdump qui fonctionne enfin :

image-1588978671635.png

image-1588978678349.png

Ça y est ! Nous avons enfin un héritage complet des capabilities de l’utilisateur vers le bash !

 

Bilan

Comme je l’avais lu dans un article, les capabilities sont un systèmes très complexe ayant subit beaucoup de modifications et de patchs au cours de son évolution et qui mériterait d’être nettoyé. Le système de granularité du root fonctionne sans aucun doute mais le fonctionnement de l’héritage est encore incomplet et Ambient n’a pas encore l’importance qu’il devrait avoir. Nous venons de le voir avec le login, l’absence d’implémentation de cet ensemble entraîne des problèmes de transmission qui rendent l’utilisation des capabilities compliquée et encore floue...obligeant les utilisateurs à utiliser sudo et brisant ainsi le principe de moindre privilège. Cependant, il y a un aspect critique avec ma modification : maintenant, le bash possède toutes les capabilities de l’utilisateur, ce qui signifie que toutes les commandes lancées depuis le bash posséderont ces capabilities. Ce qui n’est pas correct vis-à-vis du principe de moindre privilège. Cela amène donc à la question : faut il garder ce nouveau système ou faut il encore le modifier pour pouvoir décider quelles applications auront le droit d’utiliser les privilèges du bash ?

image-1588978820955.png

Je me suis donné la capability CAP_SETFCAP. Celle-ci permet, par exemple, d’utiliser ‘setcap’ : elle donne l’autorisation de modifier les Extends Attributs de sécurité. Je peux maintenant les capabilities de n’importe quel binaire sans limite. On voit le risque de sécurité présent immédiatement.

Le problème devient encore plus important avec des capabilities telles que CAP_SYS_ADMIN.

 

Annexes

Liste des librairies à posséder pour travailler sur les capabilities :

  • libcap-ng-dev
  • libcap-dev
  • libcap2
  • libcap2-bin

Lien vers les packages à télécharger pour modifier pam_cap.c et login.c

Pour compiler, installer et passer en mode console :

Pour le paquet libcap :

depuis le dossier principal faire : make all && sudo make install

Pour le paquet util-linux:

depuis le dossier principal, faire : ./autogen.sh && ./configure && make pour installer login faire, depuis le dossier principal: sudo rm /bin/login && sudo cp login /bin

Modifier en root le fichier /etc/pam.d/common-auth

remplacer l'appel de pam_cap.so par /lib64/security/pam_cap.so et vérifier que l'appel est bien optional et non required

Avant de passer en mode console, faire un snapshot. Je n'ai pas trouvé comment revenir en mode graphique une fois en console, remettre les anciens paramètres ne fonctionne pas.

Pour passer en mode console : modifier le fichier /etc/default/grub

A la ligne GRUB_CMDLINE_LINUX_DEFAULT="quiet splash", remplacer par

GRUB_CMDLINE_LINUX_DEFAULT="text"

Décommenter la ligne #GRUB_TERMINAL=console

Depuis un terminal : sudo update-grub && sudo systemctl set-default multi-user.target

Redémarrer la VM

 

Liste des #define et #include pour mon code pam_cap.c:

image-1588979323573.png

Liste des #define et #include que j’ai rajouté pour mon code login.c :

image-1588979358297.png

 

Bibliographie