Ho un processo che richiede i privilegi di root quando viene eseguito da un utente normale. Apparentemente posso usare il "bit setuid" per raggiungere questo obiettivo. Qual è il modo corretto di farlo su un sistema POSIX?
Inoltre, come posso farlo con uno script che utilizza un interprete (bash, perl, python, php, ecc.)?
Risposta accettata:
Il bit setuid può essere impostato su un file eseguibile in modo che una volta eseguito, il programma avrà i privilegi del proprietario del file invece che dell'utente reale, se diversi. Questa è la differenza tra efficace uid (id utente) e reale uid.
Alcune utilità comuni, come passwd
, sono di proprietà di root e configurati in questo modo per necessità (passwd
deve accedere a /etc/shadow
che può essere letto solo da root).
La strategia migliore quando si esegue questa operazione è fare immediatamente tutto ciò che è necessario fare come superutente, quindi ridurre i privilegi in modo che sia meno probabile che si verifichino bug o usi impropri durante l'esecuzione di root. Per fare ciò, imposti il processo efficace uid al suo vero uid. In 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: %dn",
real,
geteuid()
);
}
int main (void) {
uid_t real = getuid();
report(real);
seteuid(real);
report(real);
return 0;
}
Le funzioni rilevanti, che dovrebbero avere un equivalente nella maggior parte delle lingue di livello superiore se vengono utilizzate comunemente sui sistemi POSIX:
getuid()
:Ottieni il vero uid.geteuid()
:Ottieni il efficace uid.seteuid()
:imposta l'efficace uid.
Non puoi fare nulla con l'ultimo inappropriato per l'uid reale tranne se il bit setuid è stato impostato sull'eseguibile . Quindi, per provare questo, compila gcc test.c -o testuid
. È quindi necessario, con privilegi:
chown root testuid
chmod u+s testuid
L'ultimo imposta il bit setuid. Se ora esegui ./testuid
come utente normale vedrai che il processo viene eseguito per impostazione predefinita con uid effettivo 0, root.
E gli script?
Questo varia da piattaforma a piattaforma, ma su Linux, le cose che richiedono un interprete, incluso il bytecode, non possono utilizzare il bit setuid a meno che non sia impostato sull'interprete (il che sarebbe molto molto stupido). Ecco un semplice script perl che imita il codice C sopra:
#!/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";
Fedele alle sue *nixy radici, perl ha costruito variabili speciali per un uid efficace ($>
) e uid reale ($<
). Ma se provi lo stesso chown
e chmod
usato con l'eseguibile compilato (da C, esempio precedente), non farà alcuna differenza. Lo script non può ottenere privilegi.
La risposta è usare un binario setuid per eseguire lo 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);
}
Compila questo gcc --std=c99 whatever.c -o perlsuid
, quindi chown root perlsuid && chmod u+s perlsuid
. Ora puoi eseguire qualsiasi perl con un uid effettivo pari a 0, indipendentemente da chi lo possiede.
Una strategia simile funzionerà con php, python, ecc. Ma...
# Think hard, very important:
>_< # Genuine ASCII art "Oh tish!" face
PER FAVORE PER FAVORE NON lasciare questo genere di cose in giro . Molto probabilmente, vuoi effettivamente compilare il nome dello script come un percorso assoluto , ovvero sostituisci tutto il codice in main()
con:
const char *args[] = { "perl", "/opt/suid_scripts/whatever.pl" }
return execv("/usr/bin/perl", (char * const*)args);
Si assicurano /opt/suid_scripts
e tutto ciò che contiene è di sola lettura per utenti non root. Altrimenti, qualcuno potrebbe scambiare qualsiasi cosa con whatever.pl
.
Inoltre, fai attenzione che molti linguaggi di scripting consentono alle variabili di ambiente di cambiare il modo in cui eseguono uno script . Ad esempio, una variabile di ambiente potrebbe causare il caricamento di una libreria fornita dal chiamante, consentendo al chiamante di eseguire codice arbitrario come root. Pertanto, a meno che tu non sappia che sia l'interprete che lo script stesso sono robusti rispetto a tutte le possibili variabili di ambiente, NON FARLO .
Quindi cosa dovrei fare invece?
Un modo più sicuro per consentire a un utente non root di eseguire uno script come root è aggiungere una regola sudo e fare in modo che l'utente esegua sudo /path/to/script
. Sudo elimina la maggior parte delle variabili di ambiente e consente inoltre all'amministratore di selezionare con precisione chi può eseguire il comando e con quali argomenti. Vedi Come eseguire un programma specifico come root senza una richiesta di password? per un esempio.