web-dev-qa-db-fra.com

Pourquoi SUID est-il désactivé pour les scripts Shell mais pas pour les binaires?

Bien que je comprenne l'idée de SUID est de laisser un utilisateur non privilégié exécuter un programme en tant qu'utilisateur privilégié, j'ai trouvé que SUID ne fonctionne généralement pas sur un script Shell sans quelques solutions de contournement. Ma question est, je ne comprends pas vraiment la dichotomie entre un script Shell et un programme binaire. Il semble que tout ce que vous pouvez faire avec un script Shell, vous pouvez également le faire avec C et le compiler dans un binaire. Si SUID n'est pas sécurisé pour un script Shell, il n'est pas non plus sécurisé pour les binaires. Alors pourquoi les scripts Shell mais pas les binaires ne seraient-ils pas autorisés à utiliser SUID?

36
Cyker

Il existe une condition de concurrence inhérente à la façon dont Shebang (#!) Est généralement implémenté:

  1. Le noyau ouvre l'exécutable et constate qu'il commence par #!.
  2. Le noyau ferme l'exécutable et ouvre l'interpréteur à la place.
  3. Le noyau insère le chemin du script dans la liste des arguments (comme argv[1]), Et exécute l'interpréteur.

Si les scripts setuid sont autorisés avec cette implémentation, un attaquant peut invoquer un script arbitraire en créant un lien symbolique vers un script setuid existant, en l'exécutant et en prenant des dispositions pour modifier le lien après que le noyau a effectué l'étape 1 et avant que l'interprète ne se déplace vers ouvrant son premier argument. Pour cette raison, tous les unités modernes ignorent le bit setuid lorsqu'ils détectent un Shebang.

Une façon de sécuriser cette implémentation serait que le noyau verrouille le fichier de script jusqu'à ce que l'interprète l'ouvre (notez que cela doit empêcher non seulement de dissocier ou d'écraser le fichier, mais aussi de renommer n'importe quel répertoire du chemin). Mais les systèmes Unix ont tendance à éviter les verrous obligatoires, et les liens symboliques rendraient une fonction de verrouillage correcte particulièrement difficile et invasive. Je pense que personne ne le fait de cette façon.

Quelques systèmes Unix implémentent un setuid sécurisé Shebang en utilisant une fonctionnalité supplémentaire: le chemin /dev/fd/N Fait référence au fichier déjà ouvert sur le descripteur de fichier [~ # ~] n [~ # ~] (donc l'ouverture de /dev/fd/N équivaut à peu près à dup(N)).

  1. Le noyau ouvre l'exécutable et constate qu'il commence par #!. Disons que le descripteur de fichier pour l'exécutable est 3.
  2. Le noyau ouvre l'interpréteur.
  3. Le noyau insère /dev/fd/3 La liste des arguments (comme argv[1]), Et exécute l'interpréteur.

Toutes les variantes Unix modernes, y compris Linux, implémentent /dev/fd, Mais la plupart n'autorisent pas les scripts setuid. OpenBSD, NetBSD et Mac OS X le prennent en charge si vous activez un paramètre de noyau non par défaut. Sous Linux, les gens ont écrit des correctifs pour le permettre, mais ces correctifs n'ont jamais été fusionnés. page Shebang de Sven Mascheck a beaucoup d'informations sur Shebang à travers les unités, y compris support setuid .


En outre, les programmes exécutés avec des privilèges élevés présentent des risques inhérents qui sont généralement plus difficiles à contrôler dans les langages de programmation de niveau supérieur, sauf si l'interpréteur a été spécialement conçu pour cela. La raison en est que le code d'initialisation du runtime du langage de programmation peut effectuer des actions avec des privilèges élevés, sur la base des données héritées de l'appelant à privilèges inférieurs, avant que le propre code du programme n'ait eu la possibilité de nettoyer ces données. Le runtime C fait très peu pour le programmeur, donc les programmes C ont une meilleure opportunité de prendre le contrôle et d'assainir les données avant que quelque chose de mauvais ne puisse arriver.

Supposons que vous ayez réussi à exécuter votre programme en tant que root, soit parce que votre système d'exploitation prend en charge Setuid Shebang, soit parce que vous avez utilisé un wrapper binaire natif (tel que Sudo). Avez-vous ouvert une faille de sécurité? Peut être. Le problème ici n'est pas pas sur les programmes interprétés vs compilés. Le problème est de savoir si votre système d'exécution se comporte en toute sécurité s'il est exécuté avec des privilèges.

  • Tout exécutable binaire natif lié dynamiquement est en quelque sorte interprété par le chargeur dynamique (par exemple /lib/ld.so), Qui charge les bibliothèques dynamiques requises par le programme. Sur de nombreux appareils, vous pouvez configurer le chemin de recherche des bibliothèques dynamiques via l'environnement (LD_LIBRARY_PATH Est un nom commun pour la variable d'environnement), et même charger des bibliothèques supplémentaires dans tous les binaires exécutés (LD_PRELOAD) . L'invocateur du programme peut exécuter du code arbitraire dans le contexte de ce programme en plaçant un libc.so Spécialement conçu dans $LD_LIBRARY_PATH (Entre autres tactiques). Tous les systèmes sensés ignorent les variables LD_* Dans les exécutables setuid.

  • Dans les shells tels que sh, csh et dérivés, les variables d'environnement deviennent automatiquement des paramètres Shell. Grâce à des paramètres tels que PATH, IFS, et bien d'autres, l'invocateur du script a de nombreuses possibilités d'exécuter du code arbitraire dans le contexte des scripts Shell. Certains shells définissent ces variables sur des valeurs par défaut saines s'ils détectent que le script a été invoqué avec des privilèges, mais je ne sais pas s'il existe une implémentation particulière à laquelle je ferais confiance.

  • La plupart des environnements d'exécution (natifs, bytecode ou interprétés) ont des fonctionnalités similaires. Peu prennent des précautions particulières dans les exécutables setuid, bien que ceux qui exécutent du code natif ne font souvent rien de plus sophistiqué que la liaison dynamique (qui prend des précautions).

  • Perl est une exception notable. Il prend explicitement en charge les scripts setuid de manière sécurisée. En fait, votre script peut exécuter setuid même si votre système d'exploitation a ignoré le bit setuid sur les scripts. En effet, Perl est livré avec un assistant racine setuid qui effectue les vérifications nécessaires et réinvoque l'interpréteur sur les scripts souhaités avec les privilèges souhaités. Ceci est expliqué dans le manuel perlsec . Auparavant, les scripts Perl setuid avaient besoin de #!/usr/bin/suidperl -wT Au lieu de #!/usr/bin/Perl -wT, Mais sur la plupart des systèmes modernes, #!/usr/bin/Perl -wT Est suffisant.

Notez que l'utilisation d'un wrapper binaire natif ne fait rien en soi pour éviter ces problèmes. En fait, cela peut aggraver la situation, car cela pourrait empêcher votre environnement d'exécution de détecter qu'il est invoqué avec des privilèges et de contourner sa configurabilité d'exécution.

Un wrapper binaire natif peut sécuriser un script Shell si le wrapper désinfecte l'environnement. Le script doit prendre soin de ne pas faire trop d'hypothèses (par exemple sur le répertoire courant) mais cela va. Vous pouvez utiliser Sudo pour cela à condition qu'il soit configuré pour assainir l'environnement. La mise sur liste noire des variables est sujette aux erreurs, donc toujours la liste blanche. Avec Sudo, assurez-vous que l'option env_reset Est activée, que setenv est désactivée et que env_file Et env_keep Ne contiennent que des variables inoffensives.

Toutes ces considérations s'appliquent également à toute élévation de privilèges: setuid, setgid, setcap.

Recyclé de https://unix.stackexchange.com/questions/364/allow-setuid-on-Shell-scripts/2910#291

principalement parce que

De nombreux noyaux souffrent d'une condition de concurrence critique qui peut vous permettre d'échanger le shellscript contre un autre exécutable de votre choix entre le moment où le processus nouvellement exec () ed devient setuid et le moment où l'interpréteur de commandes démarre. Si vous êtes assez persistant, en théorie, vous pourriez faire en sorte que le noyau exécute le programme de votre choix.

ainsi que d'autres raisons trouvées sur ce lien (mais la condition de concurrence critique du noyau étant la plus pernicieuse). Les scripts, bien sûr, se chargent différemment des programmes binaires, c'est là que la faute se glisse.

Vous pourriez être amusé de lire ceci article de 2001 du Dr Dobb - qui passe par 6 étapes vers l'écriture de scripts Shell SUID plus sécurisés, pour atteindre l'étape 7:

Septième leçon - N'utilisez pas de scripts Shell SUID.

Même après tout notre travail, il est presque impossible de créer des scripts Shell SUID sûrs. (C'est impossible sur la plupart des systèmes.) En raison de ces problèmes, certains systèmes (par exemple, Linux) n'honoreront pas SUID sur les scripts Shell.

Cet historique parle des variantes qui avaient des correctifs pour la condition de concurrence; la liste est plus longue que vous ne le pensez ... mais les scripts setuid sont toujours largement découragés à cause d'autres problèmes ou parce qu'il est plus facile de les décourager que de se rappeler si vous utilisez une variante sûre ou non.

C'était un problème assez important, à l'époque, qu'il est instructif de lire sur la façon dont agressivement Perl a abordé la compensation.

15
gowenfawr