Utiliser correctement le bit setuid


45

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 accomplir cela. 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.)?

Réponses:


53

Le bit setuid peut être défini sur un fichier exécutable afin que, lors de son exécution, le programme dispose des privilèges du propriétaire du fichier à la place de l'utilisateur réel, s'ils sont différents. C'est la différence entre uid effectif (user id) et real uid.

Certains utilitaires courants, tels que passwd, appartiennent à root et sont configurés de cette manière par nécessité ( passwdaccès nécessaire /etc/shadowqui ne peut être lu que par root).

Pour ce faire, la meilleure stratégie consiste à faire immédiatement ce que vous devez faire en tant que superutilisateur, puis à réduire les privilèges, de sorte que les bogues ou les utilisations abusives risquent moins de se produire lors de l'exécution de root. Pour ce faire, vous définissez l’ outil effectif du processus sur son 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 couramment utilisées sur les systèmes POSIX:

  • getuid(): Obtenez le vrai uid.
  • geteuid(): Obtenez le uid efficace .
  • seteuid(): Définit l’ aide efficace .

Vous ne pouvez rien faire avec le dernier qui soit inapproprié pour le véritable uid sauf dans la mesure où le bit setuid a été défini sur l'exécutable . Donc, pour essayer ceci, 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 en ./testuidtant qu'utilisateur normal, le processus par défaut s'exécute avec un uid effectif 0, root.

Qu'en est-il des scripts?

Cela varie d'une plate-forme à 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 script Perl simple 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 intégré des variables spéciales pour uid ( $>) et real uid ( $<). Mais si vous essayez la même chose chownet que vous l' chmodutilisez 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 ceci gcc --std=c99 whatever.c -o perlsuid, alors 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 son 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, ne laissez pas ce genre de choses traîner . Très probablement, vous voulez réellement compiler le nom du script sous forme de chemin absolu , c’est-à-dire remplacer tout le code dans main():

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

Assurez /opt/suid_scripts- vous que tous les éléments sont en lecture seule pour les utilisateurs non root. Sinon, quelqu'un pourrait échanger n'importe quoi contre whatever.pl.

De plus, sachez que de nombreux langages de script permettent aux variables d'environnement de modifier 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 ainsi à l'appelant d'exécuter du code arbitraire en tant que root. Donc, à moins que vous sachiez que l'interpréteur et le script lui-même sont robustes vis-à-vis de toutes les variables d'environnement possibles, NE LE FAITES PAS .

Alors, que devrais-je faire à la place?

Un moyen plus sûr d'autoriser un utilisateur non root à exécuter un script en tant que root consiste à ajouter une règle sudo et à le faire 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? à titre d'exemple.


1
de nombreux shells se comporteront différemment s'ils sont appelés en tant que -privilegedshell et, s'ils sont appelés à partir d'un script responsable, peuvent également être invoqués en toute sécurité suid root. Je crains que la notion de script responsable ne soit un peu un fil conducteur dans le monde réel. Quoi qu'il en soit, il est probablement utile de mentionner combien il est important d'éviter autant que possible les écritures alors que les autorisations sont élevées ...
mikeserv

@ mikeserv Je ne connais pas bien les concepts de " -privilegedshell" et de "script responsable". Voulez-vous faire une autre réponse à propos des coquillages? Je ne peux pas vous promettre le coche, bien sûr;) Il n’ya pas beaucoup de bonnes raisons de le faire - sudoc’est une bien meilleure option si possible - je l’ai écrite comme une réponse générique issue d’une première question sur l’authentification de mot de passe système en php .
goldilocks

1
Je ne veux pas vraiment faire cela - les scripts responsables sont très difficiles - au-delà de moi, probablement. Mais je dois dire que je suis en désaccord avec votre analyse de sudo- je considère sudocomme psychotique en ce sens qu'elle prétend ne pas l'être root. Vous pouvez même mettre des globes dans votre shell sudoers. suest meilleur à mon avis pour des fins de script, mais sudoest bon pour les applications en direct. Mais ni l'un ni l'autre n'est aussi explicite qu'un script avec une ligne de #!/bin/ksh -pdémarcation ou quoi que ce soit suid kshdans $PATH.
Mikeserv

Bien que cette réponse réponde à la question, les scripts setuid sont une recette pour vous ouvrir à une vulnérabilité d’escalade de privilèges locale. Il y a une raison pour laquelle les scripts ne peuvent pas être créés facilement. La réponse correcte ici est sudo.
mdadm

J'ai pris la liberté de modifier votre réponse pour au moins mentionner l'énorme vulnérabilité liée aux variables d'environnement. Je n'aime pas trop cela comme réponse canonique, car cela fait beaucoup de digressions sur une méthode non sécurisée (l'encapsuleur setuid pour les scripts). Il serait préférable de recommander sudo et de laisser la discussion prolongée pour un autre sujet (je l’ai couvert ici , au fait).
Gilles, arrête de faire le mal

11

Oui, vous pouvez, mais c'est probablement une très mauvaise idée . Généralement, vous ne définissez pas le bit SUID directement sur votre exécutable, mais utilisez un sudo (8) ou un su (1) pour l'exécuter (et limiter le nombre de personnes pouvant l'exécuter)

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

Donc, même si vous deviez le faire, ce serait une très bonne idée de commencer par nettoyer TOUTES les entrées du programme que vous voulez 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 de connexions réseau. ça s'ouvre, etc. etc. C'est extrêmement difficile à faire et encore plus difficile à faire correctement.

Par exemple, vous pouvez autoriser les utilisateurs qui ne modifient qu'un fichier avec editor. Mais l’éditeur permet l’exécution de commandes par des assistants externes ou la suspension, donnant ainsi aux utilisateurs un shell racine à leur guise. Vous pouvez également exécuter des scripts shell qui vous paraissent très sécuritaires, mais l’utilisateur peut l’exécuter avec $ IFS modifié ou d’autres variables d’environnement qui changent complètement son comportement. Ou bien il peut s'agir d'un script PHP censé exécuter "ls", mais l'utilisateur l'exécute avec $ PATH modifié et lui fait donc exécuter un shell qu'il nomme "ls". Ou des dizaines d'autres problèmes de sécurité.

Si le programme doit vraiment effectuer une partie de son travail en tant que root, il serait probablement préférable de le modifier pour qu'il fonctionne comme un utilisateur normal, mais exécutez simplement (après avoir désinfecté autant que possible) la partie qui a absolument besoin de root via le programme suid helper.

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


1
détaché. voir l' exemple d'invocation sécurisée dans la série du programmeur POSIX pour man sh- et même si cela échoue command -p env -i .... C'est assez difficile à faire après tout. Néanmoins, si cela est fait correctement, à des fins explicites, il peut être préférable pour suidun interprète compétent que d’utiliser suou sudo- étant donné que le comportement de l’un ou de l’autre sera toujours en dehors du contrôle du script (bien qu’il susoit moins susceptible de lancer des boules de être un peu moins fou) . Regrouper tout le paquet est le seul pari sûr - il vaut mieux que ce soit tout.
Mikeserv
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.