Pourquoi devrais-je chrooter pour le sandboxing pour la sécurité si mon application peut depuis le début s'exécuter à un niveau inférieur?

14

J'écris un démon de serveur HTTP en C (il y a des raisons), je le gère avec le fichier d'unité systemd.

Je réécris une application conçue il y a 20 ans, vers 1995. Et le système qu'ils utilisent est qu'ils chrootent puis setuid, et la procédure standard.

Maintenant, dans mon travail précédent, la politique habituelle était de ne jamais exécuter de processus en tant que root. Vous créez un utilisateur / groupe pour lui et exécutez à partir de là. Bien sûr, le système exécutait certaines choses en tant que root, mais nous pouvions réaliser tous les traitements de logique métier sans être root.

Maintenant, pour le démon HTTP, je peux l'exécuter sans root si je ne chroote pas dans l'application. N'est-il donc pas plus sûr que l'application ne s'exécute jamais en tant que root?

N'est-il pas plus sûr de l'exécuter en tant qu'utilisateur mydaemon depuis le début? Au lieu de le démarrer avec root, chrooter, puis setuid à mydaemon-user?

mur
la source
3
vous devez être exécuté en tant que root pour utiliser le port 80 ou 443. sinon, vous pouvez faire ce que tomcat et d'autres logiciels webapp / serveur Web font et exécuter sur un port plus élevé (par exemple, 8080, 9090, etc.), puis utiliser soit apache / nginx pour proxy la connexion à votre logiciel de serveur Web, ou utilisez le pare-feu du système pour NAT / transférer le trafic vers votre serveur Web à partir du port 80. Si vous n'avez pas besoin du port 80 ou 443, ou pouvez proxy ou transférer la connexion, alors vous n'avez pas besoin d'exécuter en tant que root, dans un chroot ou autre.
SnakeDoc
3
@SnakeDoc sur Linux n'est plus vrai. Merci à capabilities(7).
0xC0000022L
@SnakeDoc vous pouvez utiliser authbind ainsi
Abdul Ahad

Réponses:

27

Il semble que d'autres aient manqué votre argument, ce qui n'était pas une raison pour utiliser des racines modifiées, que vous connaissez bien sûr déjà clairement, ni ce que vous pouvez faire d'autre pour limiter les dæmons, alors que vous savez également clairement comment courir sous l'égide de comptes d'utilisateurs non privilégiés; mais pourquoi faire ce genre de choses dans l'application . Il y a en fait un exemple assez juste de pourquoi.

Considérez la conception du httpdprogramme dæmon dans le dossier public de Daniel J. Bernstein. La première chose qu'il fait est de remplacer root par le répertoire racine qu'il a été invité à utiliser avec un argument de commande, puis de supprimer les privilèges de l'ID utilisateur et de l'ID de groupe non privilégiés transmis dans deux variables d'environnement.

Les jeux d'outils de gestion Dæmon ont des outils dédiés pour des choses telles que la modification du répertoire racine et le passage aux ID utilisateur et groupe non privilégiés. Le runit de Gerrit Pape l'a fait chpst. Mon ensemble d'outils Nash a chrootet setuidgid-fromenv. Le s6 de Laurent Bercot a s6-chrootet s6-setuidgid. Perp de Wayne Marshall a runtoolet runuid. Et ainsi de suite. En effet, ils ont tous la propre boîte à outils daemontools de M. Bernstein avec setuidgidcomme antécédent.

On pourrait penser que l'on pourrait extraire la fonctionnalité httpdet utiliser de tels outils dédiés. Ensuite, comme vous l'imaginez, aucune partie du programme serveur ne s'exécute avec les privilèges de superutilisateur.

Le problème est que l'une des conséquences directes doit faire beaucoup plus de travail pour configurer la racine modifiée, ce qui expose de nouveaux problèmes.

Avec Bernstein httpdtel qu'il est, les seuls fichiers et répertoires qui se trouvent dans l'arborescence des répertoires racine sont ceux qui doivent être publiés dans le monde. Il n'y a rien d'autre dans l'arbre. De plus, il n'y a aucune raison pour qu'un fichier image de programme exécutable existe dans cette arborescence.

Mais déplacer le changement de répertoire racine sur un programme chargement en chaîne (ou systemd), et tout à coup le fichier image du programme httpd, toutes les bibliothèques partagées qu'il charge, et tous les fichiers spéciaux /etc, /runet /devque le chargeur de programme ou runtime C accès à la bibliothèque lors de l'initialisation du programme (ce qui peut vous surprendre si vous truss/ straceun programme C ou C ++), devez également être présent dans la racine modifiée. Sinon, httpdne peut pas être enchaîné à et ne se chargera / ne fonctionnera pas.

N'oubliez pas qu'il s'agit d'un serveur de contenu HTTP (S). Il peut potentiellement servir n'importe quel fichier (lisible par le monde) dans la racine modifiée. Cela inclut maintenant des éléments tels que vos bibliothèques partagées, votre chargeur de programme et des copies de divers fichiers de configuration du chargeur / CRTL pour votre système d'exploitation. Et si, par certains moyens (accidentels), le serveur de contenu a accès à des éléments d' écriture , un serveur compromis peut éventuellement obtenir un accès en écriture à l'image du programme pour httpdlui-même, ou même au chargeur de programme de votre système. (Rappelez - vous que vous avez maintenant deux séries parallèles /usr, /lib, /etc, /runet /devrépertoires pour garder en sécurité.)

Rien de tout cela n'est le cas où la httpdracine change et supprime elle-même les privilèges.

Vous avez donc échangé avec une petite quantité de code privilégié, qui est assez facile à auditer et qui s'exécute dès le début du httpdprogramme, avec des privilèges de superutilisateur; pour avoir une surface d'attaque considérablement étendue de fichiers et de répertoires dans la racine modifiée.

C'est pourquoi ce n'est pas aussi simple que de tout faire à l'extérieur du programme de service.

Notez qu'il s'agit néanmoins d'un strict minimum de fonctionnalités en httpdsoi. Tout le code qui fait des choses comme regarder dans la base de données des comptes du système d'exploitation pour l'ID utilisateur et l'ID de groupe à mettre dans ces variables d'environnement en premier lieu est externe au httpdprogramme, dans de simples commandes auditables autonomes telles que envuidgid. (Et bien sûr , il est un outil de ucspi, il ne contient aucune du code à écouter sur le port TCP concerné (s) ou d'accepter les connexions, ceux -ci étant le domaine des commandes telles que tcpserver, tcp-socket-listen, tcp-socket-accept, s6-tcpserver4-socketbinder, s6-tcpserver4d, et ainsi de suite.)

Lectures complémentaires

JdeBP
la source
+1, coupable comme accusé. J'ai trouvé le titre et le dernier paragraphe ambigus, et si vous avez raison, j'ai raté le point. Cette réponse donne une interprétation très pratique. Personnellement, je noterais explicitement que devoir créer l'environnement chroot comme celui-ci est un effort supplémentaire, que la plupart des gens voudraient éviter. Mais les 2 points de sécurité ici sont déjà bien faits.
sourcejedi
Un autre point à retenir est que si le serveur baisse les privilèges avant de traiter le trafic réseau, le code privilégié n'est exposé à aucun exploit à distance.
kasperd
5

Je pense que de nombreux détails de votre question pourraient s'appliquer également avahi-daemon, que j'ai examinés récemment. (J'ai peut-être manqué un autre détail qui diffère cependant). L'exécution d'avahi-daemon dans un chroot présente de nombreux avantages, au cas où avahi-daemon serait compromis. Ceux-ci inclus:

  1. il ne peut pas lire le répertoire personnel des utilisateurs et exfiltrer des informations privées.
  2. il ne peut pas exploiter les bogues dans d'autres programmes en écrivant dans / tmp. Il existe au moins une catégorie entière de ces bogues. Par exemple https://www.google.co.uk/search?q=tmp+race+security+bug
  3. il ne peut ouvrir aucun fichier socket Unix en dehors du chroot, sur lequel d'autres démons peuvent écouter et lire des messages.

Le point 3 pourrait être particulièrement agréable lorsque vous n'utilisez pas dbus ou similaire ... Je pense qu'avahi-daemon utilise dbus, donc il s'assure de garder l'accès au système dbus même depuis l'intérieur du chroot. Si vous n'avez pas besoin d'envoyer des messages sur le système dbus, le fait de refuser cette capacité pourrait être une fonctionnalité de sécurité assez intéressante.

le gérer avec le fichier d'unité systemd

Notez que si avahi-daemon a été réécrit, il pourrait potentiellement choisir de s'appuyer sur systemd pour la sécurité, et utiliser par exemple ProtectHome. J'ai proposé un changement à avahi-daemon pour ajouter ces protections en tant que couche supplémentaire, ainsi que des protections supplémentaires qui ne sont pas garanties par chroot. Vous pouvez voir la liste complète des options que j'ai proposées ici:

https://github.com/lathiat/avahi/pull/181/commits/67a7b10049c58d6afeebdc64ffd2023c5a93d49a

Il semble qu'il y ait plus de restrictions que j'aurais pu utiliser si avahi-daemon n'a pas utilisé chroot lui-même, dont certaines sont mentionnées dans le message de validation. Je ne sais pas combien cela s'applique cependant.

Remarque, les protections que j'ai utilisées n'auraient pas empêché le démon d'ouvrir des fichiers socket Unix (point 3 ci-dessus).

Une autre approche serait d'utiliser SELinux. Cependant, vous seriez en quelque sorte en train de lier votre application à ce sous-ensemble de distributions Linux. La raison pour laquelle j'ai pensé positivement à SELinux ici, c'est que SELinux restreint l'accès aux processus sur dbus, de manière fine. Par exemple, je pense que vous pourriez souvent vous attendre à ce que systemdce ne soit pas dans la liste des noms de bus dont vous avez besoin pour pouvoir envoyer des messages à :-).

"Je me demandais si l'utilisation du sandboxing systemd était plus sûre que chroot / setuid / umask / ..."

Résumé: pourquoi pas les deux? Décodons un peu ce qui précède :-).

Si vous pensez au point 3, l'utilisation de chroot offre plus de confinement. ProtectHome = et ses amis n'essaient même pas d'être aussi restrictifs que chroot. (Par exemple, aucune des listes noires d'options systemd nommées /run, où nous avons tendance à placer des fichiers socket Unix).

chroot montre que restreindre l'accès au système de fichiers peut être très puissant, mais tout n'est pas sous Linux un fichier :-). Il existe des options systemd qui peuvent restreindre d'autres choses, qui ne sont pas des fichiers. Ceci est utile si le programme est compromis, vous pouvez réduire les fonctionnalités du noyau qui lui sont disponibles, dans lesquelles il pourrait essayer d'exploiter une vulnérabilité. Par exemple, avahi-daemon n'a pas besoin de prises bluetooth et je suppose que votre serveur web non plus :-). Ne lui donnez donc pas accès à la famille d'adresses AF_BLUETOOTH. Ajoutez simplement la liste blanche AF_INET, AF_INET6, et peut-être AF_UNIX, en utilisant l' RestrictAddressFamilies=option.

Veuillez lire la documentation de chaque option que vous utilisez. Certaines options sont plus efficaces en combinaison avec d'autres, et certaines ne sont pas disponibles sur toutes les architectures CPU. (Pas parce que le CPU est mauvais, mais parce que le port Linux pour ce CPU n'était pas aussi bien conçu. Je pense).

(Il y a un principe général ici. C'est plus sûr si vous pouvez écrire des listes de ce que vous voulez autoriser, pas de ce que vous voulez refuser. Comme définir un chroot vous donne une liste de fichiers auxquels vous êtes autorisé à accéder, et ce plus robuste que de dire que vous voulez bloquer /home).

En principe, vous pouvez appliquer vous-même les mêmes restrictions avant setuid (). C'est tout simplement du code que vous pouvez copier depuis systemd. Cependant, les options d'unité systemd devraient être beaucoup plus faciles à écrire, et comme elles sont dans un format standard, elles devraient être plus faciles à lire et à examiner.

Je peux donc fortement recommander de lire la section sandboxing de man systemd.execvotre plate-forme cible. Mais si vous voulez la conception la plus sécurisée possible, je n'aurais pas peur d'essayer chroot(puis de supprimer les rootprivilèges) dans votre programme également . Il y a un compromis ici. L'utilisation chrootimpose certaines contraintes à votre conception globale. Si vous avez déjà un design qui utilise chroot et qu'il semble faire ce dont vous avez besoin, cela sonne plutôt bien.

sourcejedi
la source
+1 spécialement pour les suggestions de systemd.
mattdm
j'ai beaucoup appris de la réponse ur, si le débordement de pile permettait une réponse multiple, j'accepterais aussi ur. Je me demandais si l'utilisation de sandboxing systemd était plus sécurisée que chroot / setuid / umask / ...
mur
@mur heureux que vous ayez aimé :). C'est une réponse très naturelle à ma réponse. Je l'ai donc mis à jour à nouveau, pour essayer de répondre à votre question.
sourcejedi
1

Si vous pouvez compter sur systemd, il est en effet plus sûr (et plus simple!) De laisser le sandboxing à systemd. (Bien sûr, l'application peut également détecter si elle a été lancée en sandbox par systemd ou non, et sandbox elle-même si elle est toujours root.) L'équivalent du service que vous décrivez serait:

[Service]
ExecStart=/usr/local/bin/mydaemon
User=mydaemon-user
RootDirectory=...

Mais nous ne devons pas nous arrêter là. systemd peut également faire beaucoup d'autres sandboxing pour vous - voici quelques exemples:

[Service]
# allocate separate /tmp and /var/tmp for the service
PrivateTmp=yes
# mount / (except for some subdirectories) read-only
ProtectSystem=strict
# empty /home, /root
ProtectHome=yes
# disable setuid and other privilege escalation mechanisms
NoNewPrivileges=yes
# separate network namespace with only loopback device
PrivateNetwork=yes
# only unix domain sockets (no inet, inet6, netlink, …)
RestrictAddressFamilies=AF_UNIX

Voir man 5 systemd.execpour beaucoup plus de directives et des descriptions plus détaillées. Si vous rendez votre démon activable par socket ( man 5 systemd.socket), vous pouvez même utiliser les options liées au réseau: le seul lien du service vers le monde extérieur sera le socket réseau qu'il a reçu de systemd, il ne pourra pas se connecter à autre chose. S'il s'agit d'un simple serveur qui n'écoute que sur certains ports et n'a pas besoin de se connecter à d'autres serveurs, cela peut être utile. (Les options liées au système de fichiers peuvent également rendre RootDirectoryobsolètes, à mon avis, donc vous n'avez peut-être plus besoin de vous soucier de configurer un nouveau répertoire racine avec tous les binaires et bibliothèques requis.)

Les versions plus récentes de systemd (depuis la v232) prennent également en charge DynamicUser=yes, où systemd allouera automatiquement l'utilisateur du service pour vous uniquement pour l'exécution du service. Cela signifie que vous ne devez pas vous enregistrer un utilisateur permanent pour le service, et fonctionne très bien tant que le service ne pas écrire à tous les emplacements du système de fichiers autres que son StateDirectory, LogsDirectoryet CacheDirectory(que vous pouvez également déclarer dans le fichier de l' unité - voir man 5 systemd.exec, encore une fois - et quel systemd va ensuite gérer, en prenant soin de les affecter correctement à l'utilisateur dynamique).

Lucas Werkmeister
la source