Journal Sandboxer des applications avec bubblewrap (1/3) : un shell basique

Posté par  . Licence CC By‑SA.
Étiquettes :
36
24
déc.
2023

Écrit par moi en anglais, traduit à 95% par ChatGPT.

XKCD #1200: Authorization

Tout le monde sait que permettre à différentes applications d'accéder librement aux données des autres n'est pas exactement optimal d'un point de vue sécurité. Alors que les serveurs bénéficient de conteneurs pour isoler les applications entre elles, nous manquons d'une bonne solution pour le bureau. Ou pas ?

Il y a, évidemment, flatpak. Malheureusement, flatpak se présente comme un "cadre de distribution et de sandboxing d'applications Linux". Ça ne va pas le faire. J'ai déjà une distribution. J'en suis très content. Je veux exécuter les applications de ma distribution de manière isolée.

Heureusement, la partie sandboxing de flatpak est en fait un projet séparé, moins connu : bubblewrap. Essayons de l'utiliser pour sécuriser notre bureau.

Commençons par l'une des choses les plus simples à sandboxer, un shell :

$ bwrap zsh
bwrap: execvp zsh: Aucun fichier ou dossier de ce type

Euh… quoi ?

Revenons à ce que fait bubblewrap : il crée en fait un nouveau espace de noms de système de fichiers, vide. Le mot-clé ici est "vide". Il n'y a pas d'exécutable zsh dedans. Corrigeons cela :

$ bwrap --ro-bind /usr /usr /usr/bin/zsh
bwrap: execvp /usr/bin/zsh: Aucun fichier ou dossier de ce type

Bizarre. Mais la commande suivante vous dira ce qui a échoué :

$ ldd /usr/bin/zsh
    linux-vdso.so.1 (0x00007fff5d189000)
    libcap.so.2 => /usr/lib/libcap.so.2 (0x00007ff55abe2000)
    libncursesw.so.6 => /usr/lib/libncursesw.so.6 (0x00007ff55ab6b000)
    libm.so.6 => /usr/lib/libm.so.6 (0x00007ff55aa7e000)
    libc.so.6 => /usr/lib/libc.so.6 (0x00007ff55a89c000)
    /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007ff55ad24000)

Les bibliothèques partagées. Donc, nous avons besoin de /lib64 également. Pour être sûrs, incluons aussi /bin, /lib et /sbin, bien qu'ils ne soient que des liens symboliques sur mon système et ne devraient pas être nécessaires. Ajoutons également /etc pour des choses comme /etc/profile.d ou /etc/localtime :

$ bwrap --ro-bind /usr /usr --ro-bind /bin /bin --ro-bind /lib /lib --ro-bind /lib64 /lib64 --ro-bind /sbin /sbin --ro-bind /etc /etc /usr/bin/zsh
/usr/share/zsh/scripts/newuser:5: aucun fichier ou dossier de ce type : /dev/null
zsh-newuser-install:23: aucun fichier ou dossier de ce type : /dev/null
zsh-newuser-install:24: aucun fichier ou dossier de ce type : /dev/null
$

Oui, /dev/null est plutôt important. De nombreuses applications en auront besoin. Nous pourrions le lier (en utilisant --dev-bind), mais il y a aussi /dev/zero, /dev/urandom, et probablement d'autres. Nous pourrions lier /dev, mais cela signifierait que les applications sandboxées auraient accès aux périphériques — cela ne semble pas être une bonne idée. Heureusement, bubblewrap a pensé à tout et nous a fourni une option --dev (et une option --proc pour des problèmes similaires). Nous avons aussi --tmpfs pour /tmp. Utilisons-les :

$ bwrap --ro-bind /usr /usr --ro-bind /bin /bin --ro-bind /lib /lib --ro-bind /lib64 /lib64 --ro-bind /sbin /sbin --ro-bind /etc /etc --proc /proc --dev /dev --tmpfs /tmp /usr/bin/zsh
$ ls /
bin  dev  etc  lib  lib64  proc  sbin  tmp  usr

Remarquez l'absence de /home : nous ne l'avons pas lié, donc il n'est pas accessible. Tout programme compromis qui est exécuté dans cette session shell sera incapable d'accéder à nos données personnelles (en l'absence de toute exploitation d'escalade de privilèges donnant un accès root).

Bien ! Nous sommes dans un sandbox ! Nous sommes en sécurité !

Vraiment ?

$ ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0  21592 12736 ?        Ss   10:37   0:01 /sbin/init verbose
root           2  0.0  0.0      0     0 ?        S    10:37   0:00 [kthreadd]
root           3  0.0  0.0      0     0 ?        S    10:37   0:00 [pool_workqueue_release]
root           4  0.0  0.0      0     0 ?        I<   10:37   0:00 [kworker/R-rcu_g]
root           5  0.0  0.0      0     0 ?        I<   10:37   0:00 [kworker/R-rcu_p]
root           6  0.0  0.0      0     0 ?        I<   10:37   0:00 [kworker/R-slub_]
root           7  0.0  0.0      0     0 ?        I<   10:37   0:00 [kworker/R-netns]
root          12  0.0  0.0      0     0 ?        I<   10:37   0:00 [kworker/R-mm_pe]
...
systemd+     426  0.0  0.0  91220  8468 ?        Ssl  10:37   0:00 /usr/lib/systemd/systemd-timesyncd
avahi        438  0.0  0.0   8932  4776 ?        Ss   10:37   0:00 avahi-daemon: running [desk.local]
dbus         439  0.0  0.0   9752  4976 ?        Ss   10:37   0:00 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only
root         441  0.0  0.0  11016  7284 ?        Ss   10:37   0:00 sshd: /usr/bin/sshd -D [listener] 0 of 10-100 startups
...
sloonz       1427  0.0  0.4 513896 70208 ?        Sl   10:38   0:13 /usr/lib/firefox/firefox -contentproc -parentBuildID 20231130105227 -prefsLen 44628 -prefMapSize 241694 -appDir /usr/lib/firefox/browser {5508672c-0163-4fa1-adeb-7f40773b136b} 3 true rdd
...

Oups…

$ env
...
SHELL=/bin/zsh
WORDCHARS=*?_-.[]~=&;!#$%^(){}<>
HISTSIZE=50000
I3SOCK=/run/user/1000/sway-ipc.1000.548.sock
SSH_AUTH_SOCK=/run/user/1000/ssh-agent.socket
CREDENTIALS_DIRECTORY=/run/credentials/getty@tty1.service
MEMORY_PRESSURE_WRITE=c29tZSAyMDAwMDAgMjAwMDAwMAA=
XCURSOR_SIZE=24
...
AWS_SECRET_ACCESS_KEY=[redacted]

Oh non. Renforçons le sandboxing.

$ bwrap --help
...
    --unshare-all                Unshare every namespace we support by default
    --share-net                  Retain the network namespace (can only combine with --unshare-all)
    --unshare-user               Create new user namespace (may be automatically implied if not setuid)
    --unshare-user-try           Create new user namespace if possible else continue by skipping it
    --unshare-ipc                Create new ipc namespace
    --unshare-pid                Create new pid namespace
    --unshare-net                Create new network namespace
    --unshare-uts                Create new uts namespace
    --unshare-cgroup             Create new cgroup namespace
    --unshare-cgroup-try         Create new cgroup namespace if possible else continue by skipping it
...
    --clearenv                   Unset all environment variables
...

Que voulons-nous dissocier ("unshare") ici ?

Dissocier l'espace de noms réseau est une très mauvaise idée, à moins que vous ne vouliez empêcher une application d'accéder à tout réseau (y compris localhost).

Dissocier l'espace de noms des PID (processus) semble être une évidence, tout comme vider l'environnement.

L'espace de noms IPC est probablement, la plupart du temps, sans problème à dissocier (les éléments importants comme les fifo, les pipe et les sockets unix sont dans l'espace de noms du système de fichiers, à l'exception des sockets unix abstraits qui sont dans l'espace de noms réseau), mais il est aussi difficile de voir l'intérêt (le processus compromis devrait trouver un programme exploitable fonctionnant dans l'environnement non sandboxé dont le vecteur d'attaque serait les files d'attente de messages POSIX ou l'IPC SYSV, qui en pratique sont très rarement utilisés par les applications de bureau). Nous verrons plus tard que sandboxer des applications graphiques peut entraîner des complications, et dissocier l'espace de noms IPC pourrait ajouter des bugs vraiment difficiles à traquer en plus de ceux-là. Nous aurons déjà assez à faire lorsque nous essaierons de sandboxer des applications de bureau, donc ne dissociions pas cela.

Je ne vois pas l'intérêt de dissocier l'espace de noms UTS (il s'agit de vider le nom d'hôte), de même pour cgroup (à moins éventuellement que vous ne vouliez appliquer des limites au nouveau cgroup créé plus tard, mais je n'ai jamais essayé). Je ne vois pas non plus de gros problème à les dissocier. Tirez à pile ou face pour décider.

Toute cette histoire de partager ou non l'espace de noms utilisateur est une (petite mais ennuyeuse) boîte de Pandore que je ne tenterai pas de couvrir extensivement ici (probablemet jamais). Pour faire court : cela dépend de la manière dont votre distribution a installé bubblewrap (suid ou non) et aura des effets minimaux (le plus grand étant que dissocier signifie que les fichiers appartenant à root appartiendront à personne dans le sandbox). Contentons-nous du comportement par défaut sur votre système (quel qu'il soit).

Ajoutons donc --clearenv et --unshare-ipc à nos arguments de base pour bubblewrap. Si vous êtes particulièrement paranoïaque, vous pouvez ajouter --unshare-uts, --unshare-user et --unshare-ipc :

$ bwrap --ro-bind /usr /usr --ro-bind /bin /bin --ro-bind /lib /lib --ro-bind /lib64 /lib64 --ro-bind /sbin /sbin --ro-bind /etc /etc --proc /proc --dev /dev --tmpfs /tmp --clearenv --unshare-pid /usr/bin/zsh
$ ls /
bin  dev  etc  lib  lib64  proc  sbin  tmp  usr
$ ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
sloonz          1  0.0  0.0   2720  1152 ?        S    15:39   0:00 bwrap --ro-bind /usr /usr --ro-bind /bin /bin --ro-bind /lib /lib --ro-bind /lib64 /lib64 --ro-bind /sbin /sbin --ro-bind /etc /etc --proc /proc --dev /dev --tmpfs /tmp --clearenv --unshare-pid /usr/bin/zsh
sloonz          3  0.0  0.0   6084  4436 ?        S    15:39   0:00 /usr/bin/zsh
sloonz          6  100  0.0   8024  3988 ?        R+   15:39   0:00 ps aux
$ killall firefox
firefox: no process found
$ env
PWD=/
HOME=/home/sloonz
LOGNAME=sloonz
SHLVL=1
OLDPWD=/
_=/bin/env

Cela semble bon pour une application sans état (par exemple, si vous voulez sandboxer curl https://ipinfo.io pour obtenir votre IP). Que faire si vous voulez conserver des fichiers entre les sessions ? Eh bien, utilisons un répertoire temporaire pour le home :

$ mkdir ~/sandboxes/my-node-project
$ bwrap --ro-bind /usr /usr --ro-bind /bin /bin --ro-bind /lib /lib --ro-bind /lib64 /lib64 --ro-bind /sbin /sbin --ro-bind /etc /etc --proc /proc --dev /dev --tmpfs /tmp --clearenv --unshare-pid --bind ~/sandboxes/my-node-project ~ --chdir ~ /usr/bin/zsh
$ npm install whatever

De cette façon, node_modules sera installé dans ~ (dans votre sandbox) ou ~/sandboxes/my-node-project (dans l'environnement non-sandboxé). Si vous installez par hasard une bibliothèque node compromise, cela ne compromettra pas votre répertoire personnel.

Vous voudrez peut-être lier certains fichiers de configuration courants, comme ~/.zshrc (à moins que vous n'ayez une variable d'environnement AWS_SECRET_ACCESS_KEY dedans) ou ~/.config/nvim. Souvenez-vous de les lier en lecture seule (--ro-bind au lieu de --bind) ; sinon, un processus compromis pourrait y écrire une charge malveillante pour obtenir un accès la prochaine fois que ces fichiers sont lus (et exécutés) dans votre environnement non-sandboxé.

Dans la prochaine partie, nous verrons les bases de l'exécution d'applications graphiques sandboxées comme un IDE ou votre navigateur.

P.S.: à la fois l'article initial en anglais et la traduction quasi-automatiques en français est une expérience nouvelle pour moi (je n’ai jamais réellement écrit d’article, ou tenu de blog). Lancez des tomates pour que ça s'arrête, des fleurs pour que ça continue. La partie 2 est déjà bien avancée et sera probablement publiée quelle que soit la réaction, la partie 3 n’est que partiellement dans ma tête et peut être expulsée à tout instant.

  • # bubblewrap

    Posté par  . Évalué à 4.

    Merci pour la série d'articles à venir, c'est un sujet que j'avais commencé à rtfm mais sans plus.
    Ayant fait un profil bubblerwrap pour Firefox sur une machine j'avais trouvé ça bien plus complexe que Firejail (qui propose des preset).

    • [^] # Re: bubblewrap

      Posté par  . Évalué à 5.

      Oui, parmi les solutions de sandbox évoquées dans les commentaires, firejail est celle qui se rapproche le plus de bubblewrap.

      Les grosses différences :

      • Comme tu le notes, bubblewrap est bien plus brut de décoffrage, où on commence dans une sandbox complètement vide et où l’utilisateur doit tout configurer à la main. Ce que je considère au final comme étant un plus dans une phase de découverte
      • Firejail fonctionne sur une logique de blacklist plutôt que de whitelist. Tu peux le voir en lançant firejail --no-profile : par défaut, tout est partagé. Personnellement, je n’aime pas du tout ce fonctionnement : je préfère oublier de partager quelque chose d’important, que ça plante, et l’ajouter, plutôt que d’oublier de blacklister quelque chose de sensible (et ne m’en rendre compte que trop tard).
      • Firejail a globalement beaucoup plus de fonctionnalités que bubblewrap (support de apparmor/seccomp, possibilité de faire du filtrage réseau, support natif de D-Bus)… ce qui n’est pas nécessairement une bonne chose considérant que firejail est un programme suid
      • Malgré la pléthore de fonctionnalités, impossible de faire des binds arbitraires (comme --bind ~/.config/mozilla ~/.mozilla par exemple)

      Toutes ces raisons font que personnellement je préfère bubblewrap. Mais les deux sont conceptuellement très proches.

      • [^] # Re: bubblewrap

        Posté par  (Mastodon) . Évalué à 4.

        je préfère oublier de partager quelque chose d’important, que ça plante, et l’ajouter, plutôt que d’oublier de blacklister quelque chose

        Bin justement, en lisant ton journal j'ai été étonné de voir que quand même pas mal de chose sont partagées par défaut (le passage : Bien ! Nous sommes dans un sandbox ! Nous sommes en sécurité ! Vraiment ?) donc c'est un peu ce qui m'a dérouté, dans le sens où j'ai du mal à voir pour quoi ça a été conçu à la base.

        On dirait que le comportement par défaut c'est un chroot, mais que via des options on peut se rapprocher du comportement des conteneurs.

        En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.

        • [^] # Re: bubblewrap

          Posté par  . Évalué à 6.

          Tout est dans cette phrase :

          Revenons à ce que fait bubblewrap : il crée en fait un nouveau espace de noms de système de fichiers, vide.

          Il ne sandbox que le système de fichier. Ce qui le rapproche effectivement d'un chroot. C'est plus pratique parce que tu n'utilise pas un dossier existant (à créer au préalable, à bind les dossiers dont tu as besoin, puis à nettoyer ensuite) et tu n'a pas besoin d'être root.

          Après c'est du namespace linux c'est exactement ce que font les conteneurs (docker, systemd-nspawn, lxc, flatpack,…).

          https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

      • [^] # Re: bubblewrap

        Posté par  . Évalué à 2.

        je préfère oublier de partager quelque chose d’important, que ça plante, et l’ajouter, plutôt que d’oublier de blacklister quelque chose de sensible (et ne m’en rendre compte que trop tard).

        Attention à partager tout le /etc donc … Il y a peut-être des mots de passe wifi, ou le /etc/shadow, ou autre données sensible dans les confs.

        Ceci est peut-être un bon début :

        --ro-bind /etc/alternatives /etc/alternatives \
        --ro-bind /etc/shells /etc/shells \
        --ro-bind /etc/services /etc/services \
        --ro-bind /etc/ld.so.cache /etc/ld.so.cache \
        --ro-bind /etc/nsswitch.conf /etc/nsswitch.conf \
        --ro-bind /etc/passwd /etc/passwd \
        --ro-bind /etc/group /etc/group \
        --ro-bind /etc/zshenv /etc/zshenv \
        --ro-bind /etc/zshrc /etc/zshrc \
        --ro-bind /etc/localtime /etc/localtime \
        

        Firejail a globalement beaucoup plus de fonctionnalités que bubblewrap (support de apparmor/seccomp, possibilité de faire du filtrage réseau, support natif de D-Bus)

        BubbleWrap aussi supporte seccomp. Je pense que bubblewrap a beaucoup de fonctionnalités aussi, mais il faut mettre les mains dedans…

        D'ailleurs, je conseillerais d'utiliser --new-session et --cap-drop ALL aussi

        • [^] # Re: bubblewrap

          Posté par  . Évalué à 3.

          Attention à partager tout le /etc donc

          J’avoue que pour le coup c’est un gros coup de flemme, et que c’est une bonne idée d’être plus sélectif /etc. Ceci dit si pour /etc/shadow si tu n’as pas le droit de le lire hors de la sandbox (et normalement tu n’as pas le droit) tu n’as pas non plus le droit de le lire dans la sandbox.

          D'ailleurs, je conseillerais d'utiliser --new-session

          Très bonne remarque, j’étais passé à côté de celui ci

          et --cap-drop ALL aussi

          À moins que j’aie loupé un truc, un utilisateur non-privilégié ne devrait pas avoir de cap activée à la base, non ?

          • [^] # Re: bubblewrap

            Posté par  . Évalué à 1. Dernière modification le 02 janvier 2024 à 00:31.

            Ceci dit si pour /etc/shadow si tu n’as pas le droit de le lire hors de la sandbox (et normalement tu n’as pas le droit) tu n’as pas non plus le droit de le lire dans la sandbox.

            Oui effectivement, mais c'est tout de même plus sûr de ne pas le partager, sur un droit mal mis ou un soucis de config. On sait jamais…

            À moins que j’aie loupé un truc, un utilisateur non-privilégié ne devrait pas avoir de cap activée à la base, non ?

            Oui tu as raison, je l'ajoute un peu automatiquement, mais ça ne concerne que les utilisateurs non-privilégiés.

  • # conteneur? vm?

    Posté par  (site web personnel) . Évalué à 5.

    Quels sont les avantages de bubblewrap par rapport à un moteur de conteneurs (comme podman par exemple)?

    Pour isoler un programme de "pas confiance", est-ce qu'il ne vaut pas mieux une machine virtuelle ?

    Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.

    • [^] # Re: conteneur? vm?

      Posté par  . Évalué à 3.

      Quels sont les avantages de bubblewrap par rapport à un moteur de conteneurs (comme podman par exemple)?

      Bubblewrap est plus bas niveau, il ne gère que l’aspect sandboxing, pas l’aspect gestion d’images. Tu peux probablement réécrire une grosse partie de podman en tant que surcouche à bubblewrap. Il est aussi plus limité dans ses ambitions, en partie parce qu’il est prévu pour tourner en suid (donc utilisable par un simple utilisateur, pas besoin d’être root pour créer une sandbox).

      Ce qui est un avantage, si, comme moi, tu ne veux pas gérer des images, mais réutiliser le système géré par ta distribution.

      Pour isoler un programme de "pas confiance", est-ce qu'il ne vaut pas mieux une machine virtuelle ?

      Ça dépend de "pas confiance" et "vaut mieux".

      Une machine virtuelle est clairement plus sécurisée, oui, et si le but est d’étudier un malware, ça me semble une meilleure idée.

      Mais une application qui tourne sur une VM pourra difficilement s’intégrer dans ton environnement graphique en partageant le socket Wayland et en communiquant sur ton bus de session D-Bus. Et tu retombes sur la problématique que je veux éviter, "gestion d’images".

      • [^] # Re: conteneur? vm?

        Posté par  . Évalué à 1.

        SystemD tout simplement ?

        • [^] # Re: conteneur? vm?

          Posté par  . Évalué à 2.

          Je ne suis pas sur de suivre ? systemd-nspawn nécessite également les droits root, et ne peut pas à ma connaissance faire l’équivalent de --bind ~/sandboxes/app ~.

          • [^] # Re: conteneur? vm?

            Posté par  . Évalué à 1.

            Je pensais à portablectl :

            Portable service images are an efficient way to bundle multiple related services and other units together, and transfer them as a whole between systems. When these images are attached the local system the contained units may run in most ways like regular system-provided units, either with full privileges or inside strict sandboxing, depending on the selected configuration. For more details, see Portable Services Documentation.

            • [^] # Re: conteneur? vm?

              Posté par  . Évalué à 2.

              Je ne connaissais pas du tout. Ceci dit, gestionnaire d'images, c'est à la base ce que je souhaitais éviter.

              • [^] # Re: conteneur? vm?

                Posté par  . Évalué à 1.

                Oui et non car au final tu installes ton appli et ça ajoute les dépendances nécessaires.
                Après si tu veux éviter d'utiliser trop d'espace, tu peux toujours installer une base que tu réutilises à coup d'overlayfs.
                Y a pas grand chose à gérer au final ;)

      • [^] # Re: conteneur? vm?

        Posté par  (site web personnel) . Évalué à 7.

        Podman aussi n'a pas besoin d'être root :-)

        Par contre, effectivement il doit toujours lui falloir une image…

        Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.

    • [^] # Re: conteneur? vm?

      Posté par  . Évalué à 2.

      Subuser est intéressant aussi.

      (super journal, par ailleurs !)

      • [^] # Re: conteneur? vm?

        Posté par  (site web personnel) . Évalué à 4.

        Arf subuser propose une installation via pip et utilise docker sans parler de rootless/daemonless.

        Installer une porte blindée en cassant la fenêtre 😜.

        Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.

      • [^] # Re: conteneur? vm?

        Posté par  . Évalué à 3. Dernière modification le 26 décembre 2023 à 18:20.

        Je ne connaissais pas, merci. Il y a des choses intéressantes dedans, comme la possibilité d'interdire l'accès au clipboard, qu’il faudrait que je regarde comment c’est implémenté.

        Ceci dit dans le principe d’être fondé sur docker je suis très dubitatif, je vois mal comment autoriser un utilisateur à accéder au démon docker sans que ce soit une faile de sécurité en soi (si l’utilisateur peut lancer des commandes docker, il peut lancer, schématiquement, docker -v /:/ -u root bash)

        • [^] # Re: conteneur? vm?

          Posté par  . Évalué à 1.

          il ne s'agit pas de se prémunir dans le cas de subuser d'une attaque locale au système ; l'utilisateur est réputé de confiance ; il s'agit plutot de faire du sandboxing sur un environnement mal maitrisé avec peu de confiance ; par exemple Firefox et de nombreux plugins.

  • # La suite !

    Posté par  (site web personnel) . Évalué à 10.

    Super article. Je lance des fleurs pour avoir la suite !

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.