web-dev-qa-db-fra.com

Utiliser correctement le bit setuid

J'ai un processus qui nécessite des privilèges root lorsqu'il est exécuté par un utilisateur normal. Apparemment, je peux utiliser le "bit setuid" pour y parvenir. Quelle est la bonne façon de procéder sur un système POSIX?

Aussi, comment puis-je faire cela avec un script qui utilise un interpréteur (bash, Perl, python, php, etc.)?

45
goldilocks

Le bit setuid peut être défini sur un fichier exécutable de sorte que lors de son exécution, le programme aura les privilèges du propriétaire du fichier au lieu de l'utilisateur réel, s'ils sont différents. C'est la différence entre efficace uid (id utilisateur) et réel uid.

Certains utilitaires courants, tels que passwd, appartiennent à root et sont configurés de cette façon par nécessité (passwd doit accéder à /etc/shadow Qui ne peut être lu que par root).

La meilleure stratégie lorsque vous faites cela est de faire tout ce que vous devez faire en tant que superutilisateur immédiatement, puis de réduire les privilèges afin que les bogues ou les abus soient moins susceptibles de se produire lors de l'exécution de root. Pour ce faire, vous définissez le effective uid du processus sur son uid réel. Dans POSIX C:

#define _POSIX_C_SOURCE 200112L // Needed with glibc (e.g., linux).
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

void report (uid_t real) {
    printf (
        "Real UID: %d Effective UID: %d\n",
        real,
        geteuid()
    );
}

int main (void) {
    uid_t real = getuid();
    report(real);
    seteuid(real);
    report(real);
    return 0;
}

Les fonctions pertinentes, qui devraient avoir un équivalent dans la plupart des langages de niveau supérieur si elles sont utilisées couramment sur les systèmes POSIX:

  • getuid(): Récupère le uid réel .
  • geteuid(): Récupère le uid effectif .
  • seteuid(): Définissez le uid effectif .

Vous ne pouvez rien faire avec le dernier inapproprié au vrai uid sauf dans la mesure où le bit setuid a été défini sur l'exécutable. Donc, pour essayer cela, compilez gcc test.c -o testuid. Vous devez ensuite, avec des privilèges:

chown root testuid
chmod u+s testuid

Le dernier définit le bit setuid. Si vous exécutez maintenant ./testuid En tant qu'utilisateur normal, vous verrez que le processus s'exécute par défaut avec uid 0 effectif, root.

Et les scripts?

Cela varie d'une plateforme à l'autre , mais sous Linux, les choses qui nécessitent un interpréteur, y compris le bytecode, ne peuvent pas utiliser le bit setuid à moins qu'il ne soit défini sur l'interpréteur (ce qui serait très très stupide ). Voici un simple script Perl qui imite le code C ci-dessus:

#!/usr/bin/Perl
use strict;
use warnings FATAL => qw(all);

print "Real UID: $< Effective UID: $>\n";
$> = $<; # Not an ASCII art greedy face, but genuine Perl...
print "Real UID: $< Effective UID: $>\n"; 

Fidèle à ses racines * nixy, Perl a construit des variables spéciales pour uid efficace ($>) Et uid réel ($<). Mais si vous essayez les mêmes chown et chmod utilisés avec l'exécutable compilé (à partir de C, exemple précédent), cela ne fera aucune différence. Le script ne peut pas obtenir de privilèges.

La réponse à cela est d'utiliser un binaire setuid pour exécuter le script:

#include <stdio.h>
#include <unistd.h> 

int main (int argc, char *argv[]) {
    if (argc < 2) {
        puts("Path to Perl script required.");
        return 1;
    }
    const char *Perl = "Perl";
    argv[0] = (char*)Perl;
    return execv("/usr/bin/Perl", argv);
}

Compilez ce gcc --std=c99 whatever.c -o perlsuid, Puis chown root perlsuid && chmod u+s perlsuid. Vous pouvez maintenant exécuter n'importe quel script Perl avec un uid effectif de 0, quel que soit le propriétaire.

Une stratégie similaire fonctionnera avec php, python, etc. Mais ...

# Think hard, very important:
>_< # Genuine ASCII art "Oh tish!" face

S'IL VOUS PLAÎT S'IL VOUS PLAÎT S'IL VOUS PLAÎT NE LAISSEZ PAS ce genre de chose traîner. Très probablement, vous voulez réellement compiler le nom du script comme un chemin absolu , c'est-à-dire remplacer tout le code de main() par:

    const char *args[] = { "Perl", "/opt/suid_scripts/whatever.pl" }
    return execv("/usr/bin/Perl", (char * const*)args);

Assurez-vous que /opt/suid_scripts Et tout ce qu'il contient sont en lecture seule pour les utilisateurs non root. Sinon, quelqu'un pourrait échanger quoi que ce soit contre whatever.pl.

De plus, sachez que de nombreux langages de script permettent aux variables d'environnement de changer la façon dont elles exécutent un script . Par exemple, une variable d'environnement peut entraîner le chargement d'une bibliothèque fournie par l'appelant, permettant à l'appelant d'exécuter du code arbitraire en tant que root. Ainsi, à moins que vous ne sachiez que l'interpréteur et le script lui-même sont robustes contre toutes les variables d'environnement possibles, NE FAITES PAS CELA .

Alors, que dois-je faire à la place?

Un moyen plus sûr d'autoriser un utilisateur non root à exécuter un script en tant que root est d'ajouter une règle Sudo et de demander à l'utilisateur d'exécuter Sudo /path/to/script. Sudo supprime la plupart des variables d'environnement et permet également à l'administrateur de sélectionner avec précision qui peut exécuter la commande et avec quels arguments. Voir Comment exécuter un programme spécifique en tant que root sans invite de mot de passe? pour un exemple.

54
goldilocks

Oui, mais c'est probablement une très mauvaise idée . Habituellement, vous ne définissez pas le bit SUID directement sur votre exécutable, mais utilisez un Sudo (8) ou su (1) pour l'exécuter (et limiter qui peut l'exécuter)

Notez cependant qu'il existe de nombreux problèmes de sécurité pour permettre aux utilisateurs réguliers d'exécuter des programmes (et en particulier des scripts!) En tant que root. La plupart d'entre eux doivent le faire à moins que le programme ne soit spécifiquement et très soigneusement écrit pour gérer cela, des utilisateurs malveillants pourraient l'utiliser pour obtenir facilement le shell racine.

Donc, même si vous le faisiez, ce serait une très bonne idée de d'abord nettoyer toutes les entrées du programme que vous souhaitez exécuter en tant que root, y compris son environnement, les paramètres de ligne de commande, STDIN et tous les fichiers qu'il traite, les données provenant des connexions réseau il s'ouvre, etc. etc. C'est extrêmement difficile à faire, et encore plus difficile à faire correctement.

Par exemple, vous pouvez autoriser les utilisateurs à modifier uniquement certains fichiers avec l'éditeur. Mais l'éditeur permet l'exécution de commandes d'aides externes ou la suspension, ce qui permet aux utilisateurs root de Shell de faire ce qu'ils souhaitent. Ou vous exécutez peut-être des scripts Shell qui vous semblent très sécuritaires, mais l'utilisateur peut l'exécuter avec $ IFS modifié ou d'autres variables d'environnement changeant complètement son comportement. Ou cela peut être PHP script qui est censé exécuter "ls", mais l'utilisateur l'exécute avec $ PATH modifié et lui fait ainsi exécuter un Shell qu'il nomme "ls". Ou des dizaines d'autres sécurité problèmes.

Si le programme doit vraiment faire une partie de son travail en tant que root, il serait probablement préférable de le modifier pour qu'il fonctionne en tant qu'utilisateurs normaux, mais il suffit d'exécuter (après avoir assaini autant que possible) la partie qui a absolument besoin de root via le programme d'aide suid.

Notez que même si vous faites entièrement confiance à tous vos utilisateurs locaux (comme, vous leur donnez le mot de passe root), c'est une mauvaise idée, comme toute supervision de sécurité involontaire par N'IMPORTE QUEL d'entre eux (comme, avoir un PHP = script en ligne, ou mot de passe faible, ou autre) permet soudainement à un attaquant distant de compromettre complètement la machine entière.

11
Matija Nalis

J'ai ce genre de solution.

  1. Fabriquer un wrapper C, destiné à être utilisé dans la ligne Shebang. Définissez-le root.
  2. Juste après le démarrage, le wrapper vérifiera si le script donné en argument est setuid/setgid.
    • si non, supprimez le privilège racine.
    • si oui - changez l'uid/gid effectif pour refléter les bits setuid/setgid.
  3. Vérifiez si le script donné est un fichier normal - quittez sinon.
  4. exécute Perl via "Script Perl -x".

Une version plus stricte/limitée pourrait également faire cela (et/ou):

  • interdire d'exécuter des scripts qui sont setuid/setgid root,
  • exécutez Perl en mode corrompu ("script Perl -x -T").

Une version encore plus avancée pourrait démarrer Perl via Sudo ("script Sudo ... Perl -x").

0
user404221