Le bit d'autorisation setuid
indique à Linux d'exécuter un programme avec l'ID utilisateur effectif du propriétaire au lieu de l'exécuteur:
> cat setuid-test.c
#include <stdio.h>
#include <unistd.h>
int main(int argc, char** argv) {
printf("%d", geteuid());
return 0;
}
> gcc -o setuid-test setuid-test.c
> ./setuid-test
1000
> Sudo chown nobody ./setuid-test; Sudo chmod +s ./setuid-test
> ./setuid-test
65534
Cependant, cela ne s'applique qu'aux exécutables; Les scripts shell ignorent le bit setuid:
> cat setuid-test2
#!/bin/bash
id -u
> ./setuid-test2
1000
> Sudo chown nobody ./setuid-test2; Sudo chmod +s ./setuid-test2
> ./setuid-test2
1000
Wikipedia dit :
En raison de la probabilité accrue de failles de sécurité, de nombreux systèmes d'exploitation ignorent l'attribut setuid lorsqu'il est appliqué à des scripts Shell exécutables.
En supposant que je suis prêt à accepter ces risques, existe-t-il un moyen de dire à Linux de traiter le bit setuid de la même manière sur les scripts Shell que sur les exécutables?
Sinon, existe-t-il une solution de contournement commune à ce problème? Ma solution actuelle consiste à ajouter une entrée sudoers
pour permettre à ALL
d'exécuter un script donné en tant qu'utilisateur sous lequel je souhaite l'exécuter, avec NOPASSWD
pour éviter l'invite de mot de passe. Le principal inconvénient de cela est la nécessité d'une entrée sudoers
chaque fois que je veux le faire, et la nécessité pour l'appelant de Sudo some-script
au lieu de simplement some-script
Linux ignore le bit setuid¹ sur tous les exécutables interprétés (c'est-à-dire les exécutables commençant par une ligne #!
). La comp.unix.questions FAQ explique les problèmes de sécurité avec les scripts setuid Shell. Ces problèmes sont de deux types: liés à Shebang et liés à Shell; J'entre dans plus de détails ci-dessous.
Si vous ne vous souciez pas de la sécurité et souhaitez autoriser les scripts setuid, sous Linux, vous devrez patcher le noyau. Depuis les noyaux 3.x, je pense que vous devez ajouter un appel à install_exec_creds
dans la fonction load_script
, avant l'appel à open_exec
, Mais je n'ai pas testé.
Il existe une condition de concurrence inhérente à la façon dont Shebang (#!
) Est généralement implémenté:
#!
.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, la plupart des unités 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 (principalement OpenBSD, NetBSD et Mac OS X, qui nécessitent tous un paramètre du noyau pour être activé) implémentent setuid sécurisé Shebang en utilisant un fonctionnalité: 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)
). De nombreux systèmes Unix (y compris Linux) ont des scripts /dev/fd
Mais pas setuid.
#!
. Disons que le descripteur de fichier pour l'exécutable est 3./dev/fd/3
La liste d'arguments (comme argv[1]
) Et exécute l'interpréteur.La page Shebang de Sven Mascheck contient beaucoup d'informations sur Shebang à travers les unités, y compris support setuid .
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 est pas à propos des 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 rendre la situation pire, 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.
TL, DR:
env_reset
).¹ Cette discussion s'applique également si vous remplacez "setgid" par "setuid"; ils sont tous deux ignorés par le noyau Linux sur les scripts
Une façon de résoudre ce problème consiste à appeler le script Shell à partir d'un programme qui peut utiliser le bit setuid.
c'est quelque chose comme Sudo. Par exemple, voici comment vous pouvez accomplir cela dans un programme C:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
setuid( 0 ); // you can set it at run time also
system( "/home/pubuntu/setuid-test2.sh" );
return 0;
}
Enregistrez-le sous setuid-test2.c.
compiler
Faites maintenant le setuid sur ce programme binaire:
su - nobody
[enter password]
chown nobody:nobody a.out
chmod 4755 a.out
Maintenant, vous devriez pouvoir l'exécuter et vous verrez que votre script sera exécuté sans autorisation de personne.
Mais ici aussi, vous devez coder en dur le chemin du script ou le passer comme argument de ligne de commande à l'exe ci-dessus.
Je préfixe ainsi quelques scripts qui sont dans ce bateau:
#!/bin/sh
[ "root" != "$USER" ] && exec Sudo $0 "$@"
Notez que cela n'utilise pas setuid
mais exécute simplement le fichier courant avec Sudo
.
Si vous voulez éviter d'appeler Sudo some_script
vous pouvez simplement faire:
#!/ust/bin/env sh
Sudo /usr/local/scripts/your_script
Les programmes SETUID doivent être conçus avec une extrême prudence car ils s'exécutent avec les privilèges root et les utilisateurs ont un contrôle important sur eux. Ils doivent tout vérifier. Vous ne pouvez pas le faire avec des scripts car:
sed
, awk
, etc. devraient également être vérifiésVeuillez noter que Sudo
fournit un certain contrôle d'intégrité mais ce n'est pas suffisant - vérifiez chaque ligne dans votre propre code.
Enfin, pensez à utiliser les capacités. Ils vous permettent d'accorder à un processus exécuté en tant qu'utilisateur des privilèges spéciaux qui nécessiteraient normalement des privilèges root. Cependant, par exemple, bien que ping
doive manipuler le réseau, il n'a pas besoin d'avoir accès aux fichiers. Je ne sais pas cependant s'ils sont hérités.
super [-r reqpath], commande [args]
Super permet aux utilisateurs spécifiés d'exécuter des scripts (ou d'autres commandes) comme s'ils étaient root; ou il peut définir les groupes uid, gid et/ou supplémentaires sur une base par commande avant d'exécuter la commande. Il est destiné à être une alternative sécurisée à la création de scripts setuid root. Super permet également aux utilisateurs ordinaires de fournir des commandes pour exécution par d'autres; ceux-ci s'exécutent avec l'uid, le gid et les groupes de l'utilisateur offrant la commande.
Super consulte un fichier `` super.tab '' pour voir si l'utilisateur est autorisé à exécuter la commande demandée. Si l'autorisation est accordée, super exécutera pgm [args], où pgm est le programme associé à cette commande. (L'exécution de root est autorisée par défaut, mais peut toujours être refusée si une règle exclut root. Les utilisateurs ordinaires ne sont pas autorisés à exécuter par défaut.)
Si la commande est un lien symbolique (ou un lien dur aussi) vers le super programme, alors taper% command args équivaut à taper% super command args (La commande ne doit pas être super, ou super ne reconnaîtra pas qu'elle est invoquée via un lien.)
http://www.ucolick.org/~will/RUE/super/README
http://manpages.ubuntu.com/manpages/utopic/en/man1/super.1.html
Vous pouvez créer un alias pour Sudo + le nom du script. Bien sûr, c'est encore plus de travail à configurer, car vous devez également configurer un alias, mais cela vous évite d'avoir à taper Sudo.
Mais si cela ne vous dérange pas d'horribles risques de sécurité, utilisez un shell setuid comme interpréteur du script Shell. Je ne sais pas si cela fonctionnera pour vous, mais je suppose que oui.
Permettez-moi de dire que je déconseille de le faire, cependant. Je ne fais que le mentionner à des fins éducatives ;-)
Si pour une raison quelconque Sudo
n'est pas disponible, vous pouvez écrire un script de wrapper fin en C:
#include <unistd.h>
int main() {
setuid(0);
execle("/bin/bash","bash","/full/path/to/script",(char*) NULL,(char*) NULL);
}
Et une fois compilé, définissez-le comme setuid
avec chmod 4511 wrapper_script
.
Ceci est similaire à une autre réponse publiée, mais exécute le script avec un environnement propre et utilise explicitement /bin/bash
Au lieu du shell appelé par system()
, et ferme ainsi certains trous de sécurité potentiels.
Notez que cela supprime complètement l'environnement. Si vous souhaitez utiliser certaines variables d'environnement sans ouvrir de vulnérabilités, il vous suffit vraiment d'utiliser Sudo
.
Évidemment, vous voulez vous assurer que le script lui-même n'est accessible en écriture que par root.