Autoriser setuid sur les scripts shell

185

Le setuidbit d'autorisation indique à Linux d'exécuter un programme avec l'ID utilisateur effectif du propriétaire à la place 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'ils sont appliqués à des scripts shell exécutables.

En supposant que je sois prêt à accepter ces risques, y a-t-il un moyen de dire à Linux de traiter le bit setuid de la même manière pour les scripts shell que pour les exécutables?

Si ce n'est pas le cas, existe-t-il une solution de contournement commune à ce problème? Ma solution actuelle consiste à ajouter une sudoersentrée pour permettre ALLd'exécuter un script donné sous le nom de l'utilisateur avec lequel je souhaite l'exécuter, NOPASSWDafin d'éviter l'invite du mot de passe. Le principal inconvénient à cela est la nécessité d’une sudoersentrée à chaque fois que je veux le faire, et la nécessité pour l’appelant sudo some-scriptdesome-script

Michael Mrozek
la source

Réponses:

203

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 FAQ comp.unix.questions explique les problèmes de sécurité liés aux scripts du shell setuid. Ces problèmes sont de deux types: liés au shebang et liés au shell; Je vais 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. À partir des noyaux 3.x, je pense que vous devez ajouter un appel à install_exec_credsdans la load_scriptfonction, avant l'appel à open_exec, mais je n'ai pas encore testé.


Setuid shebang

Il existe une condition de concurrence inhérente à la manière dont shebang ( #!) est généralement implémentée:

  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.
  3. Le noyau insère le chemin du script dans la liste d'arguments (as 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 prévoyant de modifier le lien après que le noyau a exécuté l'étape 1 et avant que l'interpréteur ne le ouvrant son premier argument. Pour cette raison, la plupart des utilisateurs ignorent le bit setuid lorsqu'ils détectent un shebang.

Un moyen 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 la suppression de la liaison ou le remplacement du fichier, mais également le changement de nom du répertoire dans le 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 tous nécessitent un réglage du noyau à activer) mettre en œuvre le tralala setuid sécurisé à l' aide d' une fonction supplémentaire: le chemin fait référence au fichier déjà ouvert le descripteur de fichier N (si l' ouverture est à peu près équivalent à ). Beaucoup de systèmes Unix (y compris Linux) ont des scripts setuid mais pas encore./dev/fd/N/dev/fd/Ndup(N)/dev/fd

  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/3la liste d'arguments (as argv[1]) et exécute l'interpréteur.

La page shebang de Sven Mascheck contient de nombreuses informations sur les shebang à travers les unités, y compris le support setuid .


Interprètes Setuid

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 setuid shebang, soit parce que vous avez utilisé un wrapper binaire natif (tel que sudo). Avez-vous ouvert un trou de sécurité? Peut - être . Le problème ici ne concerne pas les programmes interprétés vs compilés. Le problème est de savoir si votre système d'exécution se comporte de manière sécurisée 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 ordinateurs, vous pouvez configurer le chemin de recherche pour les bibliothèques dynamiques via l'environnement ( LD_LIBRARY_PATHnom commun de la variable d'environnement) et même charger des bibliothèques supplémentaires dans tous les fichiers binaires exécutés ( LD_PRELOAD). Le invocateur du programme peut exécuter du code arbitraire dans le contexte de ce programme en plaçant un spécialement conçu libc.sodans $LD_LIBRARY_PATH(entre autres tactiques). Tous les systèmes sains ignorent les LD_*variables des exécutables setuid.

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

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

  • Perl est une exception notable. Il supporte explicitement 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 dans les scripts. En effet, perl est livré avec un assistant setuid root 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 . Il fut un temps que les scripts Perl setuid nécessaires au #!/usr/bin/suidperl -wTlieu de #!/usr/bin/perl -wT, mais sur la plupart des systèmes modernes, #!/usr/bin/perl -wTest suffisante.

Notez que l'utilisation d'un wrapper binaire natif ne fait rien pour empêcher ces problèmes . En fait, il peut rendre la situation pire , car il pourrait empêcher votre environnement d'exécution de détecter qu'il est invoqué avec les privilèges et sans passer par son configurabilité d'exécution.

Un wrapper binaire natif peut sécuriser un script shell s'il désinfecte l'environnement . Le script doit veiller à ne pas faire trop d'hypothèses (par exemple sur le répertoire en cours), mais cela va. Vous pouvez utiliser sudo à cet effet, à condition qu'il soit configuré pour assainir l'environnement. La mise en liste noire des variables est sujette aux erreurs, il faut donc toujours la liste blanche. Sudo, assurez - vous que l' env_resetoption est activée, setenvc'est hors tension, et que env_fileet env_keepuniquement contenir des variables anodines.


TL, DR:

  • Setuid Shebang n'est pas sécurisé mais est généralement ignoré.
  • Si vous exécutez un programme avec des privilèges (via sudo ou setuid), écrivez du code natif ou perl, ou démarrez le programme avec un wrapper qui assainit l'environnement (tel que sudo avec l' env_resetoption).

¹ Cette discussion s’applique également si vous remplacez «setuid» par «setgid»; ils sont tous deux ignorés par le noyau Linux dans les scripts

Gilles
la source
2
@Josh: Les scripts shell setuid sécurisés sont possibles, mais uniquement si l'implémenteur du shell et le rédacteur de script font très attention. Plutôt que du code natif, je recommande Perl, où les développeurs ont veillé à ce que les scripts setuid soient sécurisés sans trop d'effort de la part du scénariste.
Gilles
3
apparemment, le suidperlmatériel est déconseillé et doit être retiré depuis des années (mais il persiste non-moins)
Jmtd,
7
En fait, suidperla été supprimé à partir de perl 5.11 (version stable 5.12): perl5110delta:> "suidperl" a été supprimé. Auparavant, il fournissait un mécanisme pour émuler les bits d'autorisation setuid sur des systèmes qui ne le supportaient pas correctement. perl5120delta:> "suidperl" ne fait plus partie de Perl. Auparavant, il fournissait un mécanisme pour émuler les bits d'autorisation setuid sur des systèmes qui ne le supportaient pas correctement.
Randy Stauner
5
Notez également cette ligne de la documentation Perl 5.6.1 (il y a près de 10 ans) ... perl561delta:> Notez que suidperl n'est ni construit, ni installé par défaut dans une version récente de perl. L'utilisation de suidperl est fortement déconseillée . Si vous pensez en avoir besoin, essayez d’abord des alternatives telles que sudo. Voir courtesan.com/sudo .
Randy Stauner
3
Je ne comprends pas: cela semble être une explication des raisons des problèmes, mais existe-t-il une réponse réelle à la question du PO ici? Existe-t-il un moyen de dire à mon système d'exploitation d'exécuter des scripts shell setuid?
Tom
55

Un moyen de résoudre ce problème consiste à appeler le script shell à partir d'un programme pouvant utiliser le bit setuid.
c'est quelque chose comme sudo. Par exemple, voici comment procéder 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 le nom setuid-test2.c.
compiler
Maintenant, faites le setuid sur ce programme binaire:

su - nobody   
[enter password]  
chown nobody:nobody a.out  
chmod 4755 a.out  

Maintenant, vous devriez être capable de l'exécuter et vous verrez que votre script est exécuté avec aucune permission.
Mais ici aussi, vous devez soit coder en dur le chemin du script, soit le transmettre comme argument de ligne de commande à un fichier exe.

Hemant
la source
25
Je déconseillerais la suggestion d'autoriser le passage du script en tant qu'argument de ligne de commande, car cela donne essentiellement à quiconque peut exécuter le programme la possibilité d'exécuter n'importe quel script en tant qu'utilisateur défini.
dsp
40
Notez que This is INSECURE même si le chemin d'accès complet au script est codé en dur. Le shell héritera des variables de l'environnement et nombre d'entre elles permettent à l'appelant d'injecter du code arbitraire. PATHet LD_LIBRARY_PATHsont des vecteurs évidents. Quelques coquilles exécutent $ENVou $BASHENVou ~/.zshenvmême avant de commencer l' exécution correcte, le script de sorte que vous ne pouvez pas protéger de ces tout dans le script. Le seul moyen sûr d'appeler un script shell avec des privilèges est de nettoyer l'environnement . Sudo sait comment le faire en toute sécurité. Alors n'écrivez pas votre propre wrapper, utilisez sudo .
Gilles
28
Je me sens mal qu'il se soit soudainement vu refuser cela - j'ai dit spécifiquement que je voulais aussi entendre les versions non sécurisées, et j'imaginais un exécutable qui prenait un argument de script shell quand je le disais. De toute évidence, il y a énormément d'insécurité, mais je voulais savoir quelles possibilités existent
Michael Mrozek
8
@Gilles: Pour votre information, Linux se désarme LD_LIBRARY_PATHentre autres lorsqu’il rencontre le bit setuid.
Grawity
3
Au lieu d'utiliser system, vous pouvez trouver qu'il est plus simple (et plus efficace) d'utiliser l'un des membres de la execfamille, probablement execve. Ainsi, vous ne créez pas de nouveau processus ni démarrez un shell, vous pouvez également transférer des arguments (en supposant que votre script privilégié puisse gérer les arguments en toute sécurité).
Toby Speight le
23

Je préfixe quelques scripts qui sont dans ce bateau ainsi:

#!/bin/sh
[ "root" != "$USER" ] && exec sudo $0 "$@"

Notez que ceci n'utilise pas setuidmais exécute simplement le fichier actuel avec sudo.

Crowley
la source
5
Cela n'utilise pas setuid mais vous donne juste une sudoinvite. (Pour moi, le but de setuid est de permettre aux choses de fonctionner en tant que root sans avoir besoin de sudo.)
Luc
12

Si vous voulez éviter d'appeler, sudo some_scriptvous pouvez simplement faire:

  #!/ust/bin/env sh

  sudo /usr/local/scripts/your_script

Les programmes SETUID doivent être conçus avec un soin extrême car ils s'exécutent avec les privilèges root et que les utilisateurs ont un grand contrôle sur eux. Ils doivent tout contrôler. Vous ne pouvez pas le faire avec des scripts car:

  • Les shells sont de gros logiciels qui interagissent fortement avec l'utilisateur. Il est presque impossible de tout vérifier - surtout que la plupart du code n'est pas conçu pour être exécuté dans un tel mode.
  • Les scripts sont une solution généralement rapide et ne sont généralement pas préparés avec le soin qu'ils permettraient à setuid. Ils ont de nombreuses caractéristiques potentiellement dangereuses.
  • Ils dépendent fortement des autres programmes. Il ne suffit pas que le shell ait été vérifié. sed, awketc. devraient également être vérifiés

S'il vous plaît noter que sudofournit une certaine vérification de la santé mentale, mais cela ne suffit pas - vérifiez chaque ligne dans votre propre code.

Dernière remarque: envisagez d'utiliser les capacités. Ils vous permettent d'accorder à un processus exécuté en tant qu'utilisateur des privilèges spéciaux nécessitant normalement des privilèges root. Cependant, par exemple, même s'il pingdoit manipuler le réseau, il n'a pas besoin d'avoir accès aux fichiers. Je ne suis pas sûr cependant si elles sont héritées.

Maciej Piechotka
la source
2
Je présume /ust/bin/envdevrait être /usr/bin/env.
Bob
4

Vous pouvez créer un alias pour sudo + le nom du script. Bien sûr, cela demande encore plus de travail, car vous devez également configurer un alias, mais vous évitez de taper sudo.

Mais si vous ne craignez pas d’horribles risques pour la sécurité, utilisez un shell setuid comme interprète du script shell. Je ne sais pas si cela fonctionnera pour vous, mais j'imagine que ça pourrait l'être.

Permettez-moi de dire que je déconseille de faire cela, cependant. Je le mentionne simplement à des fins éducatives ;-)

wzzrd
la source
5
Ça va marcher. Horriblement comme tu l'as dit. Le bit SETUID permet une exécution avec le droit de propriété. Le shell Setuid (sauf s’il a été conçu pour fonctionner avec setuid) s’exécutera en tant que root pour tout utilisateur. C'est-à-dire que n'importe qui peut exécuter rm -rf /(et d'autres commandes de la série NE LE FAITES PAS À LA MAISON ).
Maciej Piechotka
9
@ MaciejPiechotka par NE PAS FAIRE À LA MAISON, vous voulez dire Ne vous sentez pas obligé de le faire au travail? :)
peterph
4

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, commande par commande, avant l'exécution de la commande. Il s'agit d'une alternative sécurisée à la création de scripts setuid root. Super permet également aux utilisateurs ordinaires de fournir des commandes à exécuter par d’autres utilisateurs; ceux-ci s'exécutent avec les uid, 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. (La racine est autorisée à exécuter par défaut, mais peut toujours être refusée si une règle exclut la racine. Les utilisateurs ordinaires sont interdits à l'exécution par défaut.)

Si commande est un lien symbolique (ou un lien physique) avec le super programme, taper% command args équivaut à taper% super commande args (la commande ne doit pas être super, sinon 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/fr/man1/super.1.html

Nizam Mohamed
la source
Bonjour et bienvenue sur le site! Nous attendons des réponses plus détaillées ici. Pourriez-vous modifier votre réponse et expliquer en quoi consiste ce programme et comment il pourrait aider à résoudre le problème du PO?
terdon
Merci, Nizam, pendant des heures à essayer de trouver quelque chose comme super pour que des comptes puissent être exécutés par un autre utilisateur en tant qu'utilisateur du fichier.
Tchad
2

Si pour une raison quelconque sudon'est pas disponible, vous pouvez écrire un script wrapper en C:

#include <unistd.h>
int main() {
    setuid(0);
    execle("/bin/bash","bash","/full/path/to/script",(char*) NULL,(char*) NULL);
}

Et une fois que vous compilez le définir comme setuidavec chmod 4511 wrapper_script.

Cela ressemble à une autre réponse publiée, mais le script est exécuté avec un environnement propre et utilise explicitement à la /bin/bashplace du shell appelé par system(), ce qui ferme certaines failles de sécurité potentielles.

Notez que ceci élimine complètement l'environnement. Si vous souhaitez utiliser certaines variables d'environnement sans ouvrir de vulnérabilités, vous devez simplement les utiliser sudo.

De toute évidence, vous voulez vous assurer que le script lui-même ne peut être écrit que par root.

Chris
la source
-3

J'ai d'abord trouvé cette question, non convaincue par toutes les réponses, en voici une bien meilleure, qui vous permet également de masquer complètement votre script bash, si vous le souhaitez!

Cela devrait être explicite.

#include <string>
#include <unistd.h>

template <typename T, typename U>
T &replace (
          T &str, 
    const U &from, 
    const U &to)
{
    size_t pos;
    size_t offset = 0;
    const size_t increment = to.size();

    while ((pos = str.find(from, offset)) != T::npos)
    {
        str.replace(pos, from.size(), to);
        offset = pos + increment;
    }

    return str;
}

int main(int argc, char* argv[])
{
    // Set UUID to root
    setuid(0);

    std::string script = 
R"(#!/bin/bash
whoami
echo $1
)";

    // Escape single quotes.
    replace(script, std::string("'"), std::string("'\"'\"'"));

    std::string command;
    command = command + "bash -c '" + script + "'"; 

    // Append the command line arguments.
    for (int a = 0; a < argc; ++a)
    {
        command = command + " " + argv[a];
    }

    return system(command.c_str());
}

Alors tu cours

g++ embedded.cpp -o embedded
sudo chown root embedded
sudo chmod u+s embedded
Theodore R. Smith
la source
Pourquoi les votes négatifs ???
Theodore R. Smith
2
Probablement parce que c'est une solution trop compliquée qui manipule des chaînes de caractères et qui intègre un code shell. Soit vous faites un simple lanceur, soit vous utilisez sudo. C'est la pire de toutes les options.
siride