703 lines
25 KiB
Markdown
703 lines
25 KiB
Markdown
Les *capabilities*
|
||
------------------
|
||
|
||
Historiquement, dans la tradition UNIX, on distinguait deux catégories de
|
||
processus :
|
||
|
||
* les processus *privilégiés* : dont l'identifiant numérique de son utilisateur
|
||
est 0 ;
|
||
* les processus *non-privilégiés* : dont l'identifiant numérique de son
|
||
utilisateur n'est pas 0.
|
||
|
||
Lors des différents tests de permission faits 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...
|
||
|
||
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*.
|
||
|
||
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 de 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ées 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 ;
|
||
* `CAP_KILL` : permet de tuer n'importe quel processus ;
|
||
* `CAP_SYS_BOOT` : permet d'arrêter ou de redémarrer la machine ;
|
||
* `CAP_SYS_MODULE` : permet de charger et décharger des modules ;
|
||
* et beaucoup d'autres, il y en a environ 41 en tout (ça dépend de la
|
||
version du noyau) !
|
||
|
||
::::: {.more}
|
||
|
||
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 après le 17\textsuperscript{ème} *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*, ...
|
||
|
||
:::::
|
||
|
||
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.
|
||
|
||
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.
|
||
|
||
::::: {.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}
|
||
|
||
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# sysctl net.ipv4.ping_group_range="1 0"
|
||
```
|
||
</div>
|
||
|
||
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.
|
||
|
||
:::::
|
||
|
||
|
||
### 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 soient 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, tels que le propriétaire, le groupe, les modes du
|
||
fichier, ...).
|
||
|
||
Sous Linux, les attributs sont regroupés dans des espaces de noms :
|
||
|
||
* *security* : espace utilisé par les modules de sécurité du noyau, tel que
|
||
SELinux, ... ;
|
||
* *system* : espace utilisé par le noyau pour stocker des objets système, tels
|
||
que les ACL POSIX ;
|
||
* *trusted*: espace dont la lecture et l'écriture est limité au
|
||
super-utilisateur ;
|
||
* *user* : modifiable sans restriction, à partir du moment où l'on est le
|
||
propriétaire du fichier.
|
||
|
||
Par exemple, on peut définir un attribut sur un fichier comme cela :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
42sh$ echo 'Hello World!' > toto
|
||
42sh$ setfattr -n user.foo -v bar toto
|
||
42sh$ getfattr -d toto
|
||
# file: toto
|
||
user.foo="bar"
|
||
```
|
||
</div>
|
||
|
||
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
|
||
42sh$ sudo chown root:root toto && sudo chmod o-r toto
|
||
42sh$ cat toto
|
||
cat: toto: Permission denied
|
||
42sh$ sudo setfacl -m u:$USER:r toto
|
||
42sh$ cat toto
|
||
Hello World!
|
||
```
|
||
</div>
|
||
|
||
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 :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
42sh$ getfattr -d -m "^system" toto
|
||
# file: toto
|
||
system.posix_acl_access=0sgAAEAD/////AgAEOgDAEAA/////xAABAD////8=
|
||
```
|
||
</div>
|
||
|
||
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`.
|
||
|
||
:::::
|
||
|
||
|
||
### *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
|
||
pour accroître les *capabilities* d'un processus lorsqu'il est lancé par un
|
||
utilisateur non-privilégié. On peut voir le *setuid root* comme l'utilisation
|
||
de cet attribut, qui accroîtrait l'ensemble des *capabilities*.
|
||
|
||
Si votre distribution profite de ces attributs étendus, vous devriez obtenir :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
42sh$ getfattr -d -m "^security" $(which ping)
|
||
# file: bin/ping
|
||
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 avec `getcap` :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
42sh$ getcap $(which ping)
|
||
/bin/ping = cap_net_raw+ep
|
||
```
|
||
</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 exclusifs : 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ème 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évu de gérer les
|
||
versions. Cela a été corrigé dans la version 2.6.26 : un avertissement est
|
||
consigné dans les journaux système 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
|
||
partie 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 ensembles *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 partie 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 {-}
|
||
|
||
Écrivons maintenant un programme permettant de voir les *capabilities*
|
||
d'un processus :
|
||
|
||
<div lang="en-US">
|
||
```
|
||
42sh$ ./view_caps 1
|
||
cap_user_header_t
|
||
-----------------
|
||
Version: 20080522
|
||
PID: 1
|
||
|
||
cap_user_data_t
|
||
---------------
|
||
effective: 0x3fffffffff
|
||
CAP_AUDIT_CONTROL
|
||
CAP_AUDIT_READ
|
||
[...]
|
||
CAP_SYS_TIME
|
||
CAP_SYS_TTY_CONFIG
|
||
CAP_SYSLOG
|
||
CAP_WAKE_ALARM
|
||
permitted: 0x3fffffffff
|
||
CAP_AUDIT_CONTROL
|
||
CAP_AUDIT_READ
|
||
[...]
|
||
CAP_SYS_TIME
|
||
CAP_SYS_TTY_CONFIG
|
||
CAP_SYSLOG
|
||
CAP_WAKE_ALARM
|
||
inheritable: 0x0
|
||
```
|
||
</div>
|
||
|
||
Appelé sans argument, `view_caps` affichera les *capabilities* du processus
|
||
courant, avec en plus les informations sur son ensemble *ambient* et
|
||
*bounding* :
|
||
|
||
<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.
|
||
|
||
:::::
|
||
|
||
### Pour aller plus loin {-}
|
||
|
||
Je vous recommande la lecture des *man* suivants :
|
||
|
||
* `capabilities(7)` : énumérant tous les *capabilities*, leur utilisation, etc. ;
|
||
* `xattrs(7)` : à propos des attributs étendus.
|
||
|
||
Et de ces quelques articles :
|
||
|
||
* [Linux Capabilities: Why They Exist and How They Work](https://blog.container-solutions.com/linux-capabilities-why-they-exist-and-how-they-work) :\
|
||
<https://blog.container-solutions.com/linux-capabilities-why-they-exist-and-how-they-work>
|
||
* [Guidelines for extended attributes](https://www.freedesktop.org/wiki/CommonExtendedAttributes/) :\
|
||
<https://www.freedesktop.org/wiki/CommonExtendedAttributes/>
|
||
* [File-based capabilities](https://lwn.net/Articles/211883/) : <https://lwn.net/Articles/211883/>
|
||
* [A bid to resurrect Linux capabilities](https://lwn.net/Articles/199004/) : <https://lwn.net/Articles/199004/>
|
||
* [False Boundaries and Arbitrary Code Execution](https://forums.grsecurity.net/viewtopic.php?f=7&t=2522#p10271) :\
|
||
<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>
|
||
- [POSIX 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>
|