tuto3: Research about capabilities

This commit is contained in:
nemunaire 2022-10-18 01:15:22 +02:00
parent 9c81bbbd63
commit b98d1b144c
1 changed files with 519 additions and 87 deletions

View File

@ -1,5 +1,3 @@
\newpage
Les *capabilities*
------------------
@ -15,11 +13,63 @@ Lors des différents tests de permission fait par le noyau, les processus
privilégiés outrepassaient ces tests, tandis que les autres devaient passer les
tests de l'effective UID, effective GID, et autres groupes supplémentaires...
Depuis Linux 2.2 (en 1998), les processus privilégiés peuvent activer ou
désactiver des *capabilities*, chacune donnant accès à un groupe d'actions
privilégiées au sein du noyau.
Dans les années 90, ce système s'est rélévé être un peu trop basique et
conduisait régulièrement à des abus, au moyen de vulnérabilités trouvées dans
les programmes *setuid root*.
On trouve par exemple :
Avant de regarder plus en détail les solutions qui ont été apportées à ce
problème, commençons par mettre le doigt sur les difficultés.
### `setuid` : être `root` le temps de l'exécution
De manière générale, un processus s'exécute dans le contexte de privilège de
l'utilisateur qui l'a démarré. Ainsi, lorsque l'on souhaite supprimer un
fichier ou un répertoire, en tant que simple utilisateur on ne pourra supprimer
que ses propres fichiers, et en aucun cas on ne pourra supprimer ... la racine
par exemple.
Il y a cependant des tâches nécessitant des privilèges, que l'on souhaite
pouvoir réaliser en tant que simple utilisateur. C'est le cas notamment de la
modification de son mot de passe : il serait inconcevable de devoir demander à
son administrateur à chaque fois que l'on souhaite modifier son mot de
passe. Pourtant bien que l'on puisse lire le fichier `/etc/passwd`, seul `root`
peut y apporter des modifications (il en est de même pour `/etc/shadow` qui
contient les hashs des mots de passe).
C'est ainsi qu'est apparu le `suid-bit` parmi les modes des fichiers. Lorsque
ce bit est défini sur un binaire exécutable, au moment de l'exécution, le
contexte passe à celui du propriétaire du fichier (`root` si le propriétaire
est `root`, mais cela fonctionne quelque soit le propriétaire du fichier : on
ne devient pas `root`, mais bien l'utilisateur propriétaire).\
Un autre exemple : pour émettre un ping, il est nécessaire d'envoyer des
paquets ICMP. À la différence des datagrammes UDP ou des segments TCP, il n'est
pas forcément simple d'envoyer des paquets ICMP lorsque l'on est simple
utilisateur, car l'usage du protocole ICMP dans une socket est restreint : il
faut soit être super-utilisateur, soit que le noyau ait été configuré pour
autoriser certains utilisateurs à envoyer des `ECHO_REQUEST`.
Pour permettre à tous les utilisateurs de pouvoir envoyer des ping, le
programme est donc généralement *setuid root*, pour permettre à n'importe quel
utilisateur de prendre les droits du super-utilisateur, le temps de l'exécution
du programme.\
Les problèmes surviennent lorsque l'on découvre des vulnérabilités dans les
programmes *setuid root*. En effet, s'il devient possible pour un utilisateur
d'exécuter du code arbitraire, ce code sera exécuté avec les privilèges de
l'utilisateur *root* ! Dans le cas de `ping`, on se retrouverait alors à
pouvoir lire l'intégralité de la mémoire, alors que l'on avait juste besoin
d'écrire sur une interface réseau.
### Et ainsi les privilèges furent séparés
Depuis Linux 2.2 (en 1998), les différentes actions réclamant des privilèges
sont regroupés en catégories que l'on appelle *capabilities*. Chacune donne
accès à un certain nombre d'actions, on trouve notamment :
* `CAP_CHOWN` : permet de modifier le propriétaire d'un fichier de manière
arbitraire ;
@ -29,93 +79,105 @@ On trouve par exemple :
* et beaucoup d'autres, il y en a environ 41 en tout (ça dépend de la
version du noyau) !
::::: {.more}
### `ping`
Petit point historique, Linux n'est pas à l'origine du concept de
*capabilities*, il s'agit au départ de la norme POSIX 1003.1e, mais celle-ci
s'est éteinte auprès le 17e *draft*. Il devait y être standardisé de nombreux
composants améliorant la sécurité des systèmes POSIX, notamment les
*capabilities* POSIX, les *ACL POSIX*, ...
Pour émettre un ping, il est nécessaire d'envoyer des paquets ICMP. À la
différence des datagrammes UDP ou des segments TCP, il n'est pas forcément
simple d'envoyer des paquets ICMP lorsque l'on est simple utilisateur, car
l'usage du protocole ICMP dans une soket est restreint : il faut soit être
super-utilisateur, soit que le noyau ait été configuré pour autoriser certains
utilisateurs à envoyer des `ECHO_REQUEST`.
:::::
Pour permettre à tous les utilisateurs de pouvoir envoyer des ping, le
programme est donc généralement *Setuid root*. Cela permet à n'importe quel
utilisateur de prendre les droits du super-utilisateur, le temps de l'exécution
du programme.
Ainsi, `ping` pourrait se contenter de `CAP_NET_RAW`, une *capability* qui
permet notamment d'envoyer des données brutes sur n'importe quelle *socket*,
sans passer par les types de *socket* plus restreints, mais accessibles aux
utilisateurs.
Les problèmes surviennent lorsque l'on découvre des vulnérabilités dans les
programmes *Setuid root*. En effet, s'il devient possible pour un utilisateur
d'exécuter du code arbitraire, ce code sera exécuté avec les privilèges de
l'utilisateur *root* ! Dans le cas de `ping`, on se retrouverait alors à
pouvoir lire l'intégralité de la mémoire, alors que l'on avait juste besoin
d'écrire sur une interface réseau.
C'est peut-être encore beaucoup, mais au moins, une vulnérabilité dans `ping`
ne permettrait plus d'accéder à tous les fichiers ou à toute la mémoire.
C'est donc à ce moment que les *capabilities* entrent en jeu : un processus (ou
même un thread) privilégié peut décider, généralement à son lancement, de
réduire ses *capabilities*, pour ne garder que celles dont il a réellement
besoin. Ainsi, `ping` pourrait se contenter de `CAP_NET_RAW`.
::::: {.question}
#### Peut-on faire mieux pour `ping` ? {-}
Un paramètre existe bien [depuis 2011 dans le
noyau](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=c319b4d76b9e583a5d88d6bf190e079c4e43213d) :
`net.ipv4.ping_group_range`. Mais ce n'est que [depuis
2020](https://github.com/systemd/systemd/pull/13141) que les distributions
comme Fedora et Ubuntu se mettent à fournir par défaut une configuration qui
permette de se passer de *capabilities* pour lancer `ping`.
Cette option prend un intervalle d'identifiant numérique de groupes autorisés à
créer de `ECHO_REQUEST`. Par défaut la valeur invalide de `1 0` est définie, ce
qui signifie qu'aucun groupe n'est autorisé à créer des `ECHO_REQUEST` à moins
d'être privilégié.
:::::
::::: {.warning}
Bien que ce paramètre existe [depuis
2011](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=c319b4d76b9e583a5d88d6bf190e079c4e43213d),
ce n'est que [depuis 2020](https://github.com/systemd/systemd/pull/13141) que les distributions comme Fedora et Ubuntu
se mettent à fournir par défaut une configuration qui permette de se passer de
*capabilities* pour lancer `ping`.\
Si vous vous rendez compte que votre binaire `ping` est dans ce cas, testez
depuis un conteneur, par exemple :
Nous allons par la suite travailler avec le binaire `ping` pour appréhender les
*capabilities*. Si vous vous rendez compte que votre binaire `ping` est dans le
cas figure décrit juste avant (avec une distribution ayant mis en œuvre
l'option `net.ipv4.ping_group_range`), vous pouvez retrouver le comportement
historique en désactivant l'option :
<div lang="en-US">
```
42sh$ docker run -it --rm alpine
(ctnr)# apk add --no-cache acl iputils
(1/4) Installing libacl (2.2.53-r0)
(2/4) Installing acl (2.2.53-r0)
(3/4) Installing libcap (2.50-r0)
(4/4) Installing iputils (20210202-r0)
(ctnr)# su -s/bin/ash daemon
(ctnr)$ _
42sh# sysctl net.ipv4.ping_group_range="1 0"
```
</div>
Dans le conteneur le binaire `ping` est *setuid root*, vous pouvez faire des
tests en retirant le *setuid* :
Pas d'inquiétudes, le paramètre est changé de manière temporaire seulement, au
prochain redémarrage il reprendra sa valeur définie par la distribution.
<div lang="en-US">
```
(ctnr)# chmod u-s /bin/ping
(ctnr)$ ping epita.fr
ping: socket: Operation not permitted
```
</div>
Puis en ajoutant la *capability* :
<div lang="en-US">
```
(ctnr)# setcap cap_net_raw+p /bin/ping
(ctnr)$ ping epita.fr
PING epita.fr (172.67.156.141) 56(84) bytes of data.
```
</div>
Vous vous retrouverez dans le scénario attendu, tout en pouvant agir sur le
binaire `ping` sans avoir peur de casser votre distribution.
:::::
### Les ensembles de *capabilities*
Tout d'abord, il faut noter que chaque *thread* dispose de 5 ensembles de
*capabilities*. Au cours de son exécution, il peut changer ses ensembles de
*capabilities*. Ceux-ci sont utilisés de la façon suivante :
- ***bounding*** (B) : c'est l'ensemble limitant des *capabilities* qui pourront
faire l'objet d'un calcul lors des prochaines exécutions. Quelques soit les
*capabilities* que peut nous faire gagner une exécution, si la *capability*
ne se trouve pas dans le *bounding set*, elle ne sera pas considérée et il
sera impossible de l'obtenir. L'option `--cap-drop` de Docker modifie cet
ensemble pour restreindre les *capabilities* disponibles dans un conteneur ;
- ***effective*** (E) : il s'agit des *capabilities* actuellement actives, qui
seront vérifiées lors des tests de privilèges ;
- ***permitted*** (P) : ce sont les *capabilities* que la tâche peut placer dans
l'ensemble *effective* via `capset(2)`. L'ensemble *effective* ne peut pas
avoir de *capabilities* qui ne sont pas dans l'ensemble *permitted* ;
- ***inheritable*** (I) : est utilisé au moment de la résolution des *capabilities*
lors de l'exécution d'un nouveau processus. Il s'agit des *capabilities* qui
seront transmises au processus fil. À moins d'avoir la *capability*
`CAP_SETPCAP`, cet ensemble ne peut pas avoir plus de *capability* que celles
présentent dans l'ensemble *permitted* ;
- ***ambient*** (A) : existe depuis Linux 4.3 pour permettre aux utilisateurs
non-`root` de conserver des *capabilities* d'une exécution à l'autre (sans
que l'ensemble des binaires soient marqués avec toutes les *capabilities*
dans leur ensemble **inheritable**)[^AMBIENT_PB]. L'ensemble évolue
automatiquement à la baisse si une *capability* n'est plus *permitted*.
[^AMBIENT_PB]: Le problème ayant donné naissance aux *ambient capabilities* est
décrit dans cet échange : <https://lwn.net/Articles/636533/>
### Les attributs de fichier étendus
Une grosse majorité des systèmes de fichiers (ext[234], XFS, btrfs, ...)
permet d'enregistrer, pour chaque fichier, des attributs (dits attributs
*étendus*, par opposition aux attributs *réguliers* qui sont réservés à l'usage
du système de fichiers).
du système de fichiers, tels que le propriétaire, le groupe, les modes du
fichier, ...).
Sous Linux, les attributs sont regroupés dans des espaces de noms :
@ -140,9 +202,14 @@ user.foo="bar"
```
</div>
En tant que simple utilisateur, vous ne pouvez pas modifier des attributs en
dehors de l'espace *user*. Par contre, en *root*, vous pouvez définir et
changer les ACL POSIX :
Lorsque l'on est propriétaire du fichier, on peut modifier les attributs des
espaces *security*, *system* et *user*. Évidemment, *root* peut aussi
intervenir et par exemple les ACL POSIX[^ACLPOSIX] (espace *system*) :
[^ACLPOSIX]: Les ACL POSIX sont des permissions supplémentaires qui viennent
s'ajouter aux modes standards du fichier (propriétaire, groupe, reste du
monde). Avec les ACL POSIX, on peut doonner des droits à un ou plusieurs
utilisateurs ou groupe, de manière spécifique.
<div lang="en-US">
```bash
@ -155,8 +222,8 @@ Hello World!
```
</div>
Bien que les droits UNIX traditionnels ne vous donnent pas accès au fichier,
les ACL POSIX vous autorisent à le lire.
Dans cet exemple, bien que les droits UNIX traditionnels ne vous donnent pas
accès au fichier, les ACL POSIX vous autorisent à le lire.
Vous pouvez voir ces attributs bruts avec la commande :
@ -168,11 +235,50 @@ system.posix_acl_access=0sgAAEAD/////AgAEOgDAEAA/////xAABAD////8=
```
</div>
Il s'agit d'une représentation d'une structure du noyau, pas forcément très
lisible en l'état. On utilisera `getfacl` pour la version lisible.
Il s'agit d'une structure du noyau encodée en base64 (la valeur débute par
`0s`, de la même manière que l'on a l'habitude de reconnaître l'hexadécimal
avec `0x`, voir `getfattr(1)`), pas forcément très lisible en l'état. On
utilisera plutôt `getfacl` pour la version lisible.
::::: {.code}
Les structures utilisées pour stocker les ACL POSIX dans les attributs étendus
sont définies dans `linux/posix_acl_xattr.h` :\
D'abord un en-tête, composé de la version avec laquelle la suite des octets a
été enregistrée (actuellement 2) :
<div lang="en-US">
```c
struct posix_acl_xattr_header {
uint32_t a_version;
};
```
</div>
Puis on trouve directement la liste d'entrée(s) :
<div lang="en-US">
```c
struct posix_acl_xattr_entry {
uint16_t e_tag;
uint16_t e_perm;
uint32_t e_id;
};
```
</div>
Le *tag* identifie de quel type d'entrée il s'agit (propriétaire, utilisateur,
groupe, masque, reste du monde, ...). Les *perm*issions sont un champ de bits
pour la lecture, écriture et exécution. Enfin l'*id*entifiant renseigne sur le
numéro d'utilisateur ou de groupe à qui s'applique l'entrée.\
Les constantes utilisées sont définies dans `linux/posix_acl.h`.
:::::
#### `ping`
### *Capabilities* et attributs étendus pour `ping`
De la même manière que l'on peut définir de façon plus fine les droits d'accès
par utilisateur, un attribut de l'espace de nom *security* peut être défini
@ -190,8 +296,12 @@ security.capability=0sAQAAAgAgAAAAAAAAAAAAAAAAAAA=
```
</div>
Suivant votre distribution, d'autres programmes sont susceptibles de profiter
des attributs étendus pour se passer du *setuid root*, par exemple : `chvt`,
`beep`, `iftop`, `mii-tool`, `mtr`, `nethogs`, ...
Comme pour les ACL POSIX, une structure du noyau est enregistrée comme attribut
du fichier ; et on peut l'afficher dans sa version plus lisible :
du fichier ; et on peut l'afficher dans sa version plus lisible avec `getcap` :
<div lang="en-US">
```bash
@ -200,9 +310,265 @@ du fichier ; et on peut l'afficher dans sa version plus lisible :
```
</div>
::::: {.code}
La structure utilisée pour stocker les *capabilities* dans les attributs étendus
est définie dans `linux/capability.h` :
<div lang="en-US">
```c
struct vfs_cap_data {
uint32_t magic_etc;
struct {
uint32_t permitted;
uint32_t inheritable;
} data[VFS_CAP_U32];
};
```
</div>
La valeur `magic` contient la version sur 1 octet, puis 3 octets sont réservés
pour des flags. Actuellement un seul flag existe, il s'agit de
`VFS_CAP_FLAGS_EFFECTIVE` qui détermine si la liste effective de *capabilities*
du programme doit être remplie avec les *capabilities* *permitted* si elle doit
rester vide (auquel cas ce sera au programme de s'ajouter les *capabilities* au
cours de l'exécution).\
Deux entiers de 32 bits représentent ensuite les *capabilities* de 0 à 31 sous
forme de champ de bits, un entier pour la liste des *capabilities* permises, un
autre pour les *capabilities* héritables. Comme il y a une quarantaine de
*capabilities*, celles de 32 à 40 se retrouvent à la suite, dans deux nouveaux
entiers de 32 bits. C'est pour cela que `data` est un tableau, avec
`VFS_CAP_U32` valant 2, car on a deux fois nos 2 entiers de 32 bits à la
suite.\
Il s'agit de la version 2 de la structure. La version 1 était utilisée
lorsqu'il n'y avait encore que moins de 33 *capabilities* : il n'y avait alors
pas de tableau, seulement les deux entiers de 32 bits. On remarque que les deux
versions sont facilement compatibles entre-elles, la seconde version étendant
simplement la première.\
Une version 3 existe, la structure obtient un champ supplémentaire `rootid` :
<div lang="en-US">
```c
struct vfs_ns_cap_data {
uint32_t magic_etc;
struct {
uint32_t permitted;
uint32_t inheritable;
} data[VFS_CAP_U32];
uint32_t rootid;
};
```
</div>
Ce nouveau champ `rootid` est utilisé pour stocker un identifiant d'utilisateur
`root` au sein d'un *namespace* User. C'est utile pour pouvoir jouer avec les
*capabilities* au sein d'un conteneur non-privilégié. S'il vaut autre chose que
0 et que l'on ne se trouve pas dans un *namespace* User, les *capabilities* ne
seront pas appliquées.
:::::
### Gagner des *capabilities*
C'est à l'exécution (`execve(2)`) que sont calculés les nouveaux ensembles de
*capabilities* : c'est donc uniquement à ce moment que l'on peut en gagner de
nouvelles. Voici comment les différents ensembles du nouveau processus sont
calculés :
$$p_A = (\textsf{file caps or setuid or setgid}\: ?\: ∅\: :\: p_A)$$
$$p_P = (p_I\: \&\: f_I)\ |\ (f_P\: \&\: p_B)\ |\ p_A$$
$$p_E = f_E\: ?\: p_P\: :\: p_A$$
$$p_I = p_I$$
$$p_B = p_B$$
Avec $p$ le processus avant l'exécution, $f$ le fichier exécutable donnant
naissance au nouveau processus et $p$ le nouveau processus.\
On remarque que sans les *ambients capabilities*, on perd systématiquement les
*capabilities* dont on disposait avant l'`execve(2)`, car $f_I$ n'est que très
rarement défini sur un exécutable. Dans le cas général, on récupère donc soit
$f_P$, soit $p_A$ (les deux étant exclusif : si $f_P$ est défini, $p_A$ est
vide).
Bien entendu, lorsque l'on se trouve dans le contexte d'exécution de `root` (ou
que l'on exécute un binaire *setuid root*), ces calculs sont biaisés, car le
super-utilisateur a normalement toutes les *capabilities* (mais toujours
limitées par l'ensemble *bounding*) : $f_P$ et $f_I$ contiennent alors toutes
les *capabilities*, indifféremment du fichier considéré. Les calculs peuvent
alors être simplifiés en :
$$p_P = (p_I\: \&\: p_B)$$
$$p_E = p_P$$
Il y a cependant une exception, lorsque l'utilisateur réel n'est pas `root`
(comme par exemple face à un binaire *setuid root*, seul l'utilisateur effectif
change) : dans ce cas, si le fichier expose des *capabilities*, seulement
celles-ci sont gagnées.
::::: {.question}
#### Peut-on placer des *capabilities* sur un script ? {-}
De la même manière qu'il n'est pas possible d'avoir de script *setuid root*
sous Linux[^NO_SETUID_SCRIPT], ajouter des *capabilities* à un script ne
permettra pas d'en gagner. Le calcul s'effectue en effet sur les *capabilities*
de l'exécutable de l'interpréteur et non sur celles du script.
[^NO_SETUID_SCRIPT]: <https://unix.stackexchange.com/a/2910>
:::::
### Gérer ses *capabilities*
Un *thread* peut agir comme il le souhaite sur les ensembles *effective*,
*permitted* et *inheritable*. À condition bien sûr de ne jamais dépasser les
*capabilities* déjà contenues dans l'ensemble *permitted*, sauf si l'on dispose
de la *capability* `CAP_SETPCAP` : dans ce cas, on se retrouve limité seulement
par l'ensemble *bounding*.
::::: {.code}
On utilise les appels système `capget(2)` et `capset(2)` pour respectivement
connaître les *capabilities* actuelles de notre fil d'exécution et pour les
écraser. Ces appels systèmes utilisent une structure d'en-tête et une structure
de données, qui sont définies dans `linux/capability.h` :
<div lang="en-US">
```c
#define _LINUX_CAPABILITY_VERSION_1 0x19980330
#define _LINUX_CAPABILITY_U32S_1 1
/* V2 added in Linux 2.6.25; deprecated */
#define _LINUX_CAPABILITY_VERSION_2 0x20071026
#define _LINUX_CAPABILITY_U32S_2 2
/* V3 added in Linux 2.6.26 */
#define _LINUX_CAPABILITY_VERSION_3 0x20080522
#define _LINUX_CAPABILITY_U32S_3 2
typedef struct __user_cap_header_struct {
uint32_t version;
int pid;
} *cap_user_header_t;
typedef struct __user_cap_data_struct {
uint32_t effective;
uint32_t permitted;
uint32_t inheritable;
} *cap_user_data_t;
```
</div>
La structure d'en-tête permet de renseigner sur la version de la structure de
données que l'on souhaite utiliser ou recevoir. Comme pour les *capabilities*
dans les attributs étendus, la première version était utilisée lorsqu'il y
avait moins de 33 *capabilities*, ce qui permettait de tout stocker sur un seul
entier de 32 bits non signé. Les versions 2 et 3 sont identiques et permettent
de récupérer les *capabilities* au moyen d'un tableau de 2 structures.\
::::: {.warning}
Les versions 2 et 3 ici ne sont pas comparables aux versions 2 et 3 de nos
structures `vfs_cap_data` et `vfs_ns_cap_data` : il n'y a pas de notion de
*namespace* ici.
Il y a eu un couac dans les en-têtes distribués avec Linux 2.6.25, causant des
*buffers overflow* dans les applications qui n'avaient pas prévues de gérer les
versions. Cela a été corrigé dans la version 2.6.26 : un avertissement est
consigné dans les journaux systèmes en cas d'utilisation malheureuse de la
version 2.
:::::
Dans la pratique, on préférera utiliser `cap_get_proc(3)` et `cap_set_proc(3)`
fournis par la `libcap`.
:::::
Pour agir sur l'ensemble *bounding*, il faut disposer de la *capability*
`CAP_SETPCAP`[^WHY_BOUNDING_SETPCAP]. Lorsque l'on retire une *capability* de
cet ensemble, elle n'est pas retirée des autres ensembles et on peut donc
continuer de bénéficier des privilèges qu'elle accorde.
Il faut bien penser, lorsque l'on retire une *capability* de l'ensemble
*bounding*, à également la retirer de l'ensemble *inheritable*, sans quoi si le
programme exécuté a la *capability* en question dans ses attributs, celle-ci
sera préservée (revoir la formule pour $p_P$, l'ensemble *bounding* n'est pas
considéré avec l'ensemble *inheritable*).
[^WHY_BOUNDING_SETPCAP]: En effet, avant Linux 2.6.25, cet ensemble était
utilisé par tout le système, pas seulement pas le *thread* courant et ses
fils.
::::: {.code}
La consultation et la modification de l'ensemble *bounding* se fait au moyen de
`prctl(2)`, en utilisant les paramètres `PR_CAPBSET_READ` ou `PR_CAPBSET_DROP`.
Le second paramètre attendu est l'une des constantes représentant une
*capability*.\
Avec `PR_CAPBSET_READ`, `prctl(2)` retournera 0 si la *capability* ne fait pas
parti de l'ensemble *bounding*, ou 1 si elle est bien présente.
Avec `PR_CAPBSET_DROP`, `prctl(2)` retirera la *capability* passée en argument
de l'ensemble *bounding*. Une fois cette action effectuée, il est impossible de
revenir en arrière.\
Dans la pratique, on préférera utiliser `cap_get_bound(3)` et
`cap_drop_bound(3)` fournis par la `libcap`.
:::::
Enfin, le *thread* peut aussi modifier à sa guise l'ensemble *ambient*, à
condition que les *capabilities* ajoutées soient dans les ensemble *permitted*
et *inheritable*.
::::: {.code}
On consulte et modifie l'ensemble *ambient* avec `prctl(2)` à qui l'on passe
comme premier paramètre `PR_CAP_AMBIENT`. Le second paramètre permet de
préciser l'action que l'on veut réaliser :\
- `PR_CAP_AMBIENT_RAISE` : ajoute la *capability* précisée comme troisième
paramètre ;
- `PR_CAP_AMBIENT_LOWER` : retire la *capability* précisée comme troisième
paramètre ;
- `PR_CAP_AMBIENT_IS_SET` : retourne 1 si la *capability* précisée comme
troisième paramètre fait parti de l'ensemble *ambient*, sinon retourne 0 ;
- `PR_CAP_AMBIENT_CLEAR_ALL` : vide l'ensemble *ambient*.
:::::
### Explorer les *capabilities* avec un shell
La `libcap`, au travers des paquets `libcap2-bin` (Debian et ses dérivées) ou
`libcap` (Alpine, Archlinux, Gentoo et leurs dérivées), apporte le programme
`capsh(1)`, très utile pour appréhender les *capabilities*.
<div lang="en-US">
```
42sh# capsh --drop=cap_net_raw -- -c "/bin/ping nemunai.re"
/bin/bash: line 1: /bin/ping: Operation not permitted
```
</div>
Une autre commande intéressante est `pscap(1)`, de la `libcap-ng`. Parmi tous
les programmes en cours d'exécution, cet utilitaire va afficher tous les
programmes s'exécutant actuellement avec des *capabilities*, en indiquant pour
chacun lesquelles sont actives et disponibles.
::::: {.exercice}
### Visualisateur de capabilities d'un processus {-}
### Visualisateur de *capabilities* d'un processus {-}
Écrivons maintenant un programme permettant de voir les *capabilities*
d'un processus :
@ -237,10 +603,78 @@ inheritable: 0x0
```
</div>
Appelé sans argument, `view_caps` affichera les capabilities du processus
courant.
Appelé sans argument, `view_caps` affichera les *capabilities* du processus
courant, avec en plus les informations sur son ensemble *ambient* et
*bounding* :
Astuces : `capget(2)`, X-macros, ...
<div lang="en-US">
```
42sh# ./view_caps
cap_user_header_t
-----------------
Version: 20080522
PID: 42
cap_user_data_t
---------------
effective: 0x3fffffffff
CAP_AUDIT_CONTROL
CAP_AUDIT_READ
[...]
CAP_SYSLOG
CAP_WAKE_ALARM
permitted: 0x3fffffffff
CAP_AUDIT_CONTROL
CAP_AUDIT_READ
[...]
CAP_SYSLOG
CAP_WAKE_ALARM
inheritable: 0x0
ambient:
CAP_AUDIT_CONTROL
CAP_AUDIT_READ
[...]
CAP_SYSLOG
CAP_WAKE_ALARM
bounding:
CAP_AUDIT_CONTROL
CAP_AUDIT_READ
[...]
CAP_SYSLOG
CAP_WAKE_ALARM
```
</div>
<div lang="en-US">
```
42sh$ ./view_caps
cap_user_header_t
-----------------
Version: 20080522
PID: 42
cap_user_data_t
---------------
effective: 0x0
permitted: 0x0
inheritable: 0x0
ambient:
bounding:
CAP_AUDIT_CONTROL
CAP_AUDIT_READ
[...]
CAP_SYSLOG
CAP_WAKE_ALARM
```
</div>
Il peut être intéressant de lancer `view_caps` au sein d'un conteneur Docker
pour voir évoluer l'ensemble *bounding*, ou bien d'utiliser `capsh(1)` en
amont.
:::::
@ -263,7 +697,5 @@ Et de ces quelques articles :
<https://forums.grsecurity.net/viewtopic.php?f=7&t=2522#p10271>
* [Linux Capabilities on HackTricks](https://book.hacktricks.xyz/linux-unix/privilege-escalation/linux-capabilities) :\
<https://book.hacktricks.xyz/linux-unix/privilege-escalation/linux-capabilities>
Pour revenir à Docker, un certain nombre de *capabilities* sont désactivées par
défaut ; vous pouvez en ajouter et en retirer via les arguments `--cap-add` et
`--cap-drop` du `docker container run`.
- [POSIZ Access Control Lists on Linux](https://www.usenix.org/legacy/publications/library/proceedings/usenix03/tech/freenix03/full_papers/gruenbacher/gruenbacher_html/main.html) :\
<https://www.usenix.org/legacy/publications/library/proceedings/usenix03/tech/freenix03/full_papers/gruenbacher/gruenbacher_html/main.html>