Il y a une condition de concurrence inhérente à la façon dont le shebang (#!
) est généralement implémenté :
- Le noyau ouvre l'exécutable et constate qu'il commence par
#!
. - Le noyau ferme l'exécutable et ouvre l'interpréteur à la place.
- Le noyau insère le chemin du script dans la liste d'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 s'arrangeant pour modifier le lien après que le noyau a effectué l'étape 1 et avant que l'interpréteur n'arrive à ouvrant son premier argument. Pour cette raison, tous les unix 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éteur l'ait ouvert (notez que cela doit empêcher non seulement de dissocier ou d'écraser le fichier, mais également de renommer tout répertoire dans le chemin). Mais les systèmes Unix ont tendance à éviter les verrous obligatoires, et les liens symboliques rendraient une fonctionnalité de verrouillage correcte particulièrement difficile et envahissante. Je ne pense pas que quiconque le fasse de cette façon.
Quelques systèmes Unix implémentent le shebang setuid sécurisé 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 ouvrir /dev/fd/N
est à peu près équivalent à dup(N)
).
- Le noyau ouvre l'exécutable et trouve qu'il commence par
#!
. Disons que le descripteur de fichier pour l'exécutable est 3. - Le noyau ouvre l'interpréteur.
- Le noyau insère
/dev/fd/3
la liste d'arguments (commeargv[1]
) et exécute l'interpréteur.
Toutes les variantes modernes d'Unix, 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 autre que celui par défaut. Sous Linux, les gens ont écrit des correctifs pour le permettre, mais ces correctifs n'ont jamais été fusionnés. La page shebang de Sven Mascheck contient de nombreuses informations sur le shebang à travers les unices, y compris la prise en charge de setuid.
De plus, 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, à moins que l'interpréteur n'ait é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, en fonction 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 de nettoyer les données avant que quelque chose de mal ne se produise.
Supposons que vous avez réussi à exécuter votre programme en tant que root, soit parce que votre système d'exploitation prend en charge le 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 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 unix, 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 unlibc.so
spécialement conçu en$LD_LIBRARY_PATH
(entre autres tactiques). Tous les systèmes sains ignorent leLD_*
variables 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 du shell. Via des paramètres tels que
PATH
,IFS
, et bien d'autres encore, l'invocateur du script a de nombreuses opportunité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 (qu'ils soient natifs, bytecode ou interprétés) ont des fonctionnalités similaires. Peu de gens prennent des précautions particulières dans les exécutables setuid, bien que ceux qui exécutent du code natif ne fassent 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. Cela est dû au fait que 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 nécessitaient
#!/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 appelé 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 assainit l'environnement. Le script doit veiller à 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 sur liste blanche. Avec sudo, assurez-vous que le env_reset
l'option est activée, que setenv
est éteint, et que env_file
et env_keep
ne contiennent que des variables anodines.
Toutes ces considérations s'appliquent de la même manière pour toute élévation de privilège :setuid, setgid, setcap.
Recyclé depuis https://unix.stackexchange.com/questions/364/allow-setuid-on-shell-scripts/2910#2910
Principalement parce que
De nombreux noyaux souffrent d'une condition de concurrence qui peut vous permettre d'échanger le shellscript contre un autre exécutable de votre choix entre les moments où le processus nouvellement exécuté devient setuid et le moment où l'interpréteur de commandes démarre. Si vous êtes assez persévérant, en théorie, vous pourriez faire en sorte que le noyau exécute n'importe quel programme de votre choix.
ainsi que d'autres raisons trouvées sur ce lien (mais la condition de concurrence 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 cet article du Dr Dobb de 2001 - qui passe par 6 étapes vers l'écriture de scripts shell SUID plus sécurisés, pour atteindre l'étape 7 :
Leçon 7 -- N'utilisez pas de scripts shell SUID.
Même après tout notre travail, il est presque impossible de créer des scripts SUIDshell 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 shellscripts.
Cette histoire parle des variantes qui avaient des correctifs pour la condition de course ; la liste est plus longue que vous ne le pensez... mais les scripts setuid sont encore largement découragés en raison 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 suffisamment important, à l'époque, pour qu'il soit instructif de lire à quel point Perl a approché agressivement sa compensation.