4 points par GN⁺ 2024-06-24 | 1 commentaires | Partager sur WhatsApp
  • sudo et doas reposent sur des binaires setuid et sur l’élévation de privilèges ; il s’agit ici d’une expérimentation qui confie l’exécution de commandes root à un sshd local lié à une socket de domaine Unix
  • L’objectif est de n’autoriser que les utilisateurs approuvés à exécuter des commandes root, sans laisser à toute leur session la capacité d’élever ses privilèges
  • L’implémentation se compose d’un fichier de clé SSH réservé à root, de /run/sshd/sshd.sock avec accès restreint, et d’une instance séparée de sshd avec les options AuthorizedKeysFile et PermitRootLogin=yes
  • Comme ssh n’a pas d’option pour recevoir directement une socket existante, l’approche a d’abord utilisé ProxyCommand et socat, puis ProxyUseFdpass avec un court script Python pour transmettre le descripteur de fichier de la socket
  • Cette méthode fonctionne et s’appuie surtout sur OpenSSH pour le traitement de la sécurité, mais pour un usage quotidien il vaut mieux transformer passfd.py en petit exécutable et encapsuler la commande ssh entière dans un wrapper

Les contraintes structurelles de sudo et doas

  • sudo et doas s’appuient sur des binaires setuid et sur l’élévation de privilèges pour exécuter des commandes en root
  • Cette conception présente plusieurs limites
    • toute la session utilisateur doit conserver la capacité d’effectuer une élévation de privilèges
    • elle ne fonctionne pas quand toute la session utilisateur s’exécute dans un espace de noms utilisateur restreint
    • les binaires setuid imposent des contraintes à la configuration de sécurité de l’ensemble du système
  • s6-sudod est une alternative qui sépare le programme entre un serveur privilégié et un client non privilégié

Objectif de l’expérimentation

  • Utiliser ssh en local pour remplir un rôle similaire à sudo, sans exposer l’instance sshd concernée au réseau
  • Deux conditions sont au cœur de l’approche
    • seuls les utilisateurs autorisés peuvent exécuter des commandes en root
    • ne pas utiliser d’élévation de privilèges

Configuration de sshd pour une connexion root locale

  • Créer une clé SSH dédiée, utilisée uniquement pour l’authentification root, et la stocker dans /root/.ssh/local_keys plutôt que dans l’authorized_keys habituel
  • Lier une instance séparée de sshd à une socket de domaine Unix et restreindre les permissions de /run/sshd/ pour empêcher tout utilisateur non autorisé d’accéder à la socket
  • L’exemple d’exécution consiste à créer /run/sshd/sshd.sock avec s6-ipcserver, puis à lancer sshd avec les options suivantes
    • AuthorizedKeysFile=/root/.ssh/local_keys
    • PermitRootLogin=yes
  • Il n’est pas nécessaire de modifier /etc/ssh/sshd_config
    • car on ne veut pas autoriser la connexion root sur le sshd existant lié au réseau
    • la configuration existante conserve donc PermitRootLogin no

Verrouillage du compte root et gestion de la connexion par mot de passe

  • Le compte root était verrouillé de façon à empêcher toute connexion, quel qu’en soit le moyen, en préfixant le hash du mot de passe avec !
  • sshd interprète ce préfixe ! comme un verrouillage du compte et n’autorise donc pas la connexion root
  • La valeur du mot de passe root dans /etc/passwd est modifiée pour remplacer ! par *
    • sshd n’interprète pas * comme une valeur spéciale de verrouillage
    • * ne correspond à aucun hash de mot de passe, donc l’authentification par mot de passe reste de fait désactivée
  • En plus de cela, PasswordAuthentication no est défini dans sshd_config, et il est prudent de désactiver l’authentification par mot de passe dans sshd

Se connecter à une socket Unix avec ssh

  • sshd possède un drapeau -i permettant de lui transmettre une socket existante, mais ssh n’a pas de drapeau équivalent
  • Au départ, l’approche utilisait socat via ProxyCommand pour se connecter à /run/sshd/sshd.sock
  • La commande de connexion se compose des éléments suivants
    • ProxyCommand='socat STDIO UNIX-CONNECT:/run/sshd/sshd.sock'
    • la clé root dédiée est indiquée avec -i .ssh/root-key.pub
    • connexion à root@root
    • déplacement vers le répertoire courant, puis lancement d’un shell de connexion
  • La clé SSH utilisée était une clé liée au matériel, ce qui exigeait de toucher le périphérique physique pour approuver la connexion
  • Un ssh-agent qui ne publie la clé qu’après approbation explicite, comme hissh-agent, peut aussi être une alternative

La surcharge de socat et ProxyUseFdpass

  • socat lit toute l’entrée de ssh avant de l’écrire vers la socket, ce qui duplique en pratique la surcharge de connexion
  • ProxyUseFdpass permet à une commande d’envoyer le descripteur de fichier de la socket à ssh via stdout, puis à ssh d’utiliser cette socket pour la connexion
  • En s’appuyant sur un exemple d’utilisation de ProxyUseFdpass dans OpenSSH datant de 2016, un script Python passfd.py a été écrit
    • connexion à la socket de domaine Unix /run/sshd/sshd.sock
    • transmission du descripteur de fichier avec sendmsg et SCM_RIGHTS
  • La commande de connexion utilise ensuite passfd.py dans ProxyCommand et ajoute ProxyUseFdpass=yes
  • nc -FU /run/sshd/sshd.sock semble aussi envisageable, mais la page de manuel précise que -F ne peut pas être utilisé avec -U

Conclusion et forme adaptée à un usage réel

  • Cette technique fonctionne et confie surtout à OpenSSH les traitements de sécurité sensibles
  • OpenSSH bénéficie d’un bon historique et permet d’utiliser plusieurs méthodes d’authentification, notamment des clés SSH matérielles
  • La mise en place sur un nouvel hôte ne comporte pas d’étapes particulièrement complexes, et la commande ipcserver peut être lancée via le gestionnaire de services du système
  • passfd.py ressemble davantage à un hack rapide pour faire avancer l’expérimentation
  • Pour un usage quotidien, il est plus approprié de créer un petit exécutable remplissant le même rôle et de le placer dans /usr/local/bin, puis d’encapsuler aussi la commande ssh entière dans un petit wrapper

1 commentaires

 
GN⁺ 2024-06-24
Commentaires sur Hacker News
  • La principale objection à cette approche est l’augmentation de la complexité. Au lieu d’un unique binaire suid qui lit un fichier de configuration et appelle exec(), on se retrouve avec un binaire exécuté en root qui écoute sur un socket UNIX, plus un autre binaire qui dialogue avec ce socket, et tous deux doivent en plus gérer de la cryptographie asymétrique
    Si l’argument central contre sudo/doas est qu’« il existe un binaire suid accessible à tous les utilisateurs, et qu’en cas de bug il peut être exploité pour une élévation de privilèges », alors on peut faire ceci
    chgrp wheel /usr/bin/sudo
    chmod o-rwx /usr/bin/sudo
    En mettant les utilisateurs sudo dans le groupe wheel, seuls les utilisateurs autorisés à utiliser sudo peuvent lire les octets du fichier sur le disque et l’exécuter. En matière de contrôle d’accès, la sécurité est presque la même que dans l’approche sshd, où seuls les utilisateurs wheel peuvent accéder au socket UNIX, mais avec une complexité bien moindre
    En outre, l’approche sshd ne permet pas, comme sudo, de limiter l’accès root à des commandes spécifiques ; donc même s’il existe un bug permettant de contourner les restrictions de commandes de sudo, cela n’accorde pas plus de privilèges que l’approche sshd
    Si vous craignez qu’un gestionnaire de paquets casse les permissions de /usr/bin/sudo, vous pouvez les corriger périodiquement via cron, ou supprimer complètement sudo et l’installer manuellement ailleurs à partir des sources. Bien sûr, dans ce cas il faut aussi assurer soi-même la maintenance et les mises à jour

    • Personnellement, j’utilise etckeeper pour suivre toutes les modifications de /etc. Qu’elles viennent d’une installation/mise à niveau logicielle ou d’une intervention humaine, tout est enregistré, et c’est utile lors d’une mise à niveau vers une nouvelle version : on peut générer un patch des modifications locales, l’appliquer à une installation propre, puis vérifier les conflits via une fusion à 3 voies
      https://etckeeper.branchable.com/
    • Mettre les utilisateurs sudo dans le groupe wheel afin que seuls ceux qui peuvent utiliser sudo puissent lire et exécuter le fichier paraît tout à fait raisonnable. Je me demande pourquoi ce n’est pas la configuration par défaut partout
    • Pardonnez mon ignorance. J’aimerais qu’on m’explique ce qu’est le groupe wheel et à quoi il sert. Je sais que cela peut lancer un débat complexe
    • Ne serait-il pas possible de rendre /usr/bin/sudo immutable ? Cela pourrait aider à empêcher qu’un gestionnaire de paquets y touche
    • Sur le point disant qu’on ne peut pas limiter l’accès root à des commandes spécifiques, il existe l’infrastructure ForcedCommand
  • N’est-ce pas exactement ce que fait systemd run0 aujourd’hui ? systemd a un nouvel outil appelé run0, mais en réalité ce n’est pas un outil entièrement nouveau : c’est une façon de faire agir l’ancien systemd-run comme un remplaçant de sudo lorsqu’on l’invoque via un lien symbolique nommé run0
    La différence essentielle est que ce n’est pas réellement du SUID. Il demande au gestionnaire de services d’exécuter une commande ou un shell avec l’UID de l’utilisateur cible, alloue un nouveau PTY, puis transfère les données entre le TTY d’origine et ce PTY
    Autrement dit, la commande cible n’hérite pas du contexte du client ; elle s’exécute dans un contexte isolé nouvellement forké depuis PID 1. $TERM est transmis, mais c’est une exception explicite, donc on est plus proche d’une liste d’autorisations que d’une liste de blocage
    À bien des égards, run0 peut être vu comme plus proche du fonctionnement de ssh que de celui de sudo
    https://mastodon.social/@pid_eins/112353324518585654

    • Pourquoi systemd doit-il tout réinventer ?
  • Est-ce qu’il y a quelque chose qui m’échappe ? Je ne vois pas en quoi se connecter en SSH en root serait plus sûr que d’utiliser sudo. On m’a toujours appris à ne jamais l’autoriser, donc je ne sais même pas vraiment à quel point c’est dangereux en pratique
    Ici, il semble qu’il y ait une réflexion pour bloquer les utilisateurs distants, donc je ne parle pas de cet aspect de sécurité
    Cela a peut-être un lien avec la troisième limite de sudo, mais au moins par rapport au premier point je n’y vois aucun avantage
    Je comprends l’idée de l’expérimentation, mais cela me semble plus vulnérable que sudo, pas moins. Un proxy socket ouvert paraît exposé aux attaques de l’homme du milieu
    Cela dit, j’ai quand même appris quelques techniques faisables avec de vieux outils, et j’ai apprécié qu’on montre quelque chose de nouveau

    • Le binaire sudo est suid root et constitue un binaire privilégié directement exposé à des utilisateurs non fiables. Si quelque chose tourne mal dans sudo, l’ensemble de l’environnement utilisateur devient une surface d’attaque exploitable
      L’approche ssh n’expose pas de binaire suid et s’appuie sur la couche réseau de ssh. Elle n’est donc pas moins sûre qu’un accès à ssh via le réseau, ce qui est généralement considéré comme assez sûr
    • L’un des grands avantages de sudo est justement de pouvoir exécuter des commandes individuelles avec sudo, au lieu de simplement lancer quelque chose comme sudo bash, ce qui améliore l’auditabilité
    • Se connecter en SSH en root signifie ici un serveur SSH supplémentaire qui écoute sur un socket Unix. Le modèle de menace habituel lié à l’exposition d’une connexion root sur Internet ne s’applique donc pas forcément ici
    • Cette approche compare d’un côté des erreurs de configuration théoriques ou des vulnérabilités hypothétiques, et de l’autre une nouvelle surface d’attaque créée par l’exécution d’un nouveau démon
      Ce démon peut lui aussi avoir des erreurs de configuration ou des vulnérabilités, et il réduit plusieurs couches d’autorisation basées sur les utilisateurs à un unique niveau root
      Et pourtant cela semble considéré comme plus sûr. D’un point de vue de sécurité rationnel, on ne peut pas dire que ce soit plus sûr ; c’est simplement une approche différente
    • Je suis sceptique vis-à-vis de l’approche de l’article lié, mais l’idée selon laquelle « se connecter directement en SSH distant en root est dangereux » relève aussi en partie du FUD
      En pratique, se connecter en SSH directement en root est même strictement plus sûr que de se connecter d’abord avec un utilisateur distant puis d’utiliser sudo
      Si user@home se connecte en ssh à root@server, alors root@server n’est compromis que si user@home l’est
      En revanche, si user@home se connecte en ssh à user@server puis devient root@server via sudo, il suffit que user@home ou user@server soit compromis pour que root le soit aussi. En particulier, sur user@server, d’autres logiciels comme des démons ou des tâches cron s’exécutent souvent
      Il ne faut pas offrir une élévation de privilèges root gratuite à quelqu’un qui a réussi à infecter par ce biais, ni faciliter ensuite les mouvements latéraux, fréquents à cause de la réutilisation des mots de passe
      Bien sûr, cela ne s’applique pas si sudo est utilisé uniquement en mode liste blanche de commandes autorisées, et sans mot de passe ni identifiants entièrement accessibles depuis l’hôte distant user@server
  • Que se passe-t-il si ssh ne démarre pas au boot ? Dans une configuration classique, il me semble qu’il démarre en dépendant du réseau. Donc on ne peut pas non plus se connecter depuis une console failsafe
    Je ne vois pas vraiment ce qu’on y gagne par rapport à sudo ou su. Au lieu d’éviter un binaire setuid, on se retrouve à exécuter un service réseau avec les privilèges root, même s’il n’écoute que sur un socket

    • Pour parler en tant que quelqu’un qui utilise une configuration similaire, c’est comme ça que j’ai configuré mon poste principal. Si le pire du pire arrive, tout est sauvegardé, donc je réinstalle le système
      Et si le SSD lâche ? Dans ce cas non plus, on ne peut pas se connecter à la console failsafe
      En 30 ans de Linux, j’ai vu des disques durs mourir bien plus souvent qu’un démon sshd ne pas démarrer ; en proportion, c’est du niveau division par zéro
      Si le démon sshd de l’OS cesse de démarrer aléatoirement, j’y verrais un signe qu’il est temps de passer à un OS plus fiable
      Ce qu’on gagne par rapport à sudo ou su, c’est que les exploits d’élévation de privilèges locale deviennent bien plus difficiles
    • La console Linux, c’est-à-dire le tty affiché sur l’écran local ou via un KVM distant, ainsi que les périphériques ttyS* sur port série et IPMI SoL, n’utilise ni sudo ni su
      Ces consoles utilisent des programmes comme getty ou un gestionnaire de fenêtres, qui sont des programmes non-suid lancés en root
      Il est donc logique de configurer un mot de passe root pour la connexion console
    • Dans mon cas, j’utilise setuid/sudo pour les journaux d’audit. Aujourd’hui, je gère rarement des machines multi-utilisateurs ou multi-services, et la plupart de ce qui est multi-tenant est sur k8s, donc on peut utiliser un endpoint kubectl au lieu de ssh
      Si quelqu’un peut se connecter, je lui laisse la possibilité de faire un setuid root. Sur une machine k8s, ce sera l’équipe d’infrastructure plateforme ; l’accès aux services au-dessus passe via le fournisseur d’autorisations k8s
      Du point de vue de l’équipe d’infrastructure plateforme, si on n’a besoin que de métriques et de logs, ils sont déjà hors de la machine ; et s’il faut déclencher une tâche ou un workflow, on peut utiliser un pipeline
      Malgré tout, si quelqu’un se connecte et effectue des opérations root, je veux garder une trace d’audit
      Je ne vois pas de machine chez moi où quelqu’un ayant le droit de se connecter n’aurait pas aussi tous les privilèges root
      Bien sûr, je comprends les cas où un service fait du setuid, mais pour les services, c’est généralement systemd qui fait le setuid non pas pour élever les privilèges, mais pour les réduire
    • Si on a accès au bootloader, on peut toujours définir systems.unit=emergency.target, init=/bin/bash, rd.break=pre-pivot, ou démarrer sur un environnement live CD. Toutes les options classiques de récupération d’urgence continuent de fonctionner
      Et dans une urgence moins grave, je ne vois pas non plus pourquoi cette instance de sshd devrait être liée au réseau
  • C’est un peu dommage de ne pas inclure tous les inconvénients de cette approche. sudo permet de contrôler quels groupes peuvent exécuter quelles commandes, quels arguments ces commandes peuvent accepter, si elles peuvent lancer des sous-shells, etc.
    Cette méthode perd beaucoup de cette granularité de contrôle et repose sur des clés de confiance plus difficiles à administrer qu’un fichier sudoers
    Si vous voulez voir les capacités impressionnantes de sudo, je recommande vraiment le livre Sudo Mastery

    • SSH peut aussi faire une partie de cela avec ForceCommand, mais je suis d’accord sur le fait que ce n’est ni aussi flexible ni aussi précis
  • C’est une idée proche de run0 de Systemd : https://news.itsfoss.com/systemd-run0/

    • Et run0 n’est pas un bricolage maison. Il a été audité et sera probablement meilleur qu’une solution faite soi-même
  • L’un des problèmes de ssh, c’est que la création de processus ne fait pas partie du protocole. Et comme c’est un protocole distant, on ne peut pas transmettre de ressources locales au processus enfant
    Il est donc impossible de passer un tableau d’arguments délimités par des caractères nuls, des descripteurs de fichiers supplémentaires, ou de spécifier un exécutable
    À la place, on transmet une chaîne unique au shell configuré sur le serveur. Cela impose des échappements shell et suppose aussi de savoir quel shell tourne côté serveur
    Pour faire de SSH un vrai remplaçant de sudo, il faudrait une extension offrant quelque chose de proche de posix_spawn

  • Entièrement d’accord. Je fais quelque chose de similaire moi aussi, et je l’avais déjà expliqué dans un commentaire HN il y a quelque temps
    Ma méthode est un peu différente. J’utilise une machine dédiée comme console SSH physique, sur un LAN privé séparé des autres machines de la maison. Ce n’est pas un switch administrable mais un switch ordinaire, avec des câbles Ethernet et sans trunk
    La connexion se fait uniquement en SSH, et j’y ai branché une Yubikey
    Le PC de bureau a son propre pare-feu et n’autorise le trafic SSH que depuis l’adresse IP/MAC de cette console SSH. C’est uniquement sur leur LAN privé commun ; sur les autres LAN physiques, le bureau peut accéder à Internet
    Le démon sshd n’autorise que la connexion par paire de clés publique/privée et bloque la connexion par mot de passe
    Quand j’ai besoin de root, je démarre la « console SSH ». C’est une machine presque vide, donc elle démarre très vite. Je me connecte, je rappelle la ligne ssh root@... avec la flèche du haut, j’appuie sur Entrée, puis j’active la Yubikey
    Cette console SSH et son clavier sont sur le bureau, toujours à portée de main
    À chacun de décider si iptables/nftables + sshd sur un LAN privé, en plus physiquement séparé d’un autre LAN privé, est plus ou moins sûr qu’un binaire sudo ou su
    Si on me demande « pourquoi », je répondrai : « parce que je peux ». Je l’ai configuré il y a si longtemps que je ne me souviens même plus quand. J’ai probablement commencé à jouer avec cette idée il y a environ deux ans, et je l’utilise depuis. Je n’ai eu absolument aucun problème

    • Même si la configuration n’est pas exactement la même, ça ressemble au concept de bastion host
  • C’est une solution élégante à ce problème. Pas besoin de traiter les utilisateurs comme des enfants, mais il faut quand même éviter les erreurs potentielles avec des valeurs par défaut raisonnables
    Si on a besoin de root, il suffit de se connecter en root sur la console, donc je dirais qu’on n’a même pas besoin de su. Cette méthode se rapproche autant que possible d’une connexion root sur un tty console

    • L’approche consistant à dire « si vous avez besoin de root, connectez-vous en root sur la console » pose deux problèmes
      Premièrement, contrairement à sudo, tous les utilisateurs doivent connaître le mot de passe root
      Deuxièmement, si tout le monde se connecte directement en root, il n’y a aucun moyen d’auditer qui s’est connecté ni ce qu’il a fait ensuite
  • J'avais essayé quelque chose de similaire il y a 10 ans. Il n'y avait pas la partie socket UNIX, et j'avais lancé un sshd séparé n'écoutant que sur localhost, sans avoir besoin de gérer SCM_RIGHTS
    Cela n'a donné ni de très bons ni de très mauvais résultats, et j'ai simplement perdu l'intérêt, donc je n'ai pas reporté la configuration sur la machine suivante