Consenti setuid su script shell


185

Il setuidbit di autorizzazione indica a Linux di eseguire un programma con l'ID utente effettivo del proprietario anziché dell'esecutore:

> 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

Tuttavia, questo vale solo per gli eseguibili; gli script di shell ignorano il 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 dice :

A causa della maggiore probabilità di difetti di sicurezza, molti sistemi operativi ignorano l'attributo setuid quando applicati a script di shell eseguibili.

Supponendo che io sia disposto ad accettare quei rischi, c'è un modo per dire a Linux di trattare lo stesso bit setuid sugli script shell come sugli eseguibili?

In caso contrario, esiste una soluzione alternativa per questo problema? La mia soluzione attuale è quella di aggiungere una sudoersvoce per consentire ALLl'esecuzione di un determinato script come l'utente con cui lo voglio eseguire, NOPASSWDper evitare la richiesta della password. Il principale svantaggio di ciò è la necessità di una sudoersvoce ogni volta che voglio farlo, e la necessità per il chiamante sudo some-scriptinvece disome-script

Risposte:


203

Linux ignora il bit setuid¹ su tutti gli eseguibili interpretati (ovvero eseguibili che iniziano con una #!linea). Le FAQ di comp.unix.questions spiegano i problemi di sicurezza con gli script di shell setuid. Questi problemi sono di due tipi: correlati a shebang e relativi alla shell; Vado più in dettaglio di seguito.

Se non ti interessa la sicurezza e vuoi consentire gli script setuid, sotto Linux, dovrai patchare il kernel. A partire dai kernel 3.x, penso che sia necessario aggiungere una chiamata a install_exec_credsnella load_scriptfunzione, prima della chiamata a open_exec, ma non ho ancora testato.


Setuid Shebang

Esiste una condizione di competizione inerente al modo in cui shebang ( #!) viene generalmente implementato:

  1. Il kernel apre l'eseguibile e trova che inizia con #!.
  2. Il kernel chiude l'eseguibile e apre invece l'interprete.
  3. Il kernel inserisce il percorso dello script nell'elenco degli argomenti (as argv[1]) ed esegue l'interprete.

Se gli script setuid sono consentiti con questa implementazione, un utente malintenzionato può invocare uno script arbitrario creando un collegamento simbolico a uno script setuid esistente, eseguendolo e organizzando la modifica del collegamento dopo che il kernel ha eseguito il passaggio 1 e prima che l'interprete riesca a aprendo il suo primo argomento. Per questo motivo, la maggior parte dei unices ignorano il bit setuid quando rilevano uno shebang.

Un modo per proteggere questa implementazione sarebbe che il kernel blocchi il file di script fino a quando l'interprete non lo ha aperto (si noti che ciò deve impedire non solo di scollegare o sovrascrivere il file, ma anche di rinominare qualsiasi directory nel percorso). Ma i sistemi unix tendono a evitare i blocchi obbligatori e i collegamenti simbolici renderebbero una funzione di blocco corretta particolarmente difficile e invasiva. Non penso che nessuno lo faccia in questo modo.

Alcuni sistemi unix (principalmente OpenBSD, NetBSD e Mac OS X, che richiedono tutti un'impostazione del kernel per essere abilitati) implementano shebang setuid sicuri usando una funzione aggiuntiva: il percorso si riferisce al file già aperto sul descrittore di file N (quindi l'apertura è approssimativamente equivalente a ). Molti sistemi unix (incluso Linux) hanno ma non script setuid./dev/fd/N/dev/fd/Ndup(N)/dev/fd

  1. Il kernel apre l'eseguibile e trova che inizia con #!. Supponiamo che il descrittore di file per l'eseguibile sia 3.
  2. Il kernel apre l'interprete.
  3. Il kernel inserisce /dev/fd/3la lista degli argomenti (as argv[1]) ed esegue l'interprete.

La pagina shebang di Sven Mascheck contiene molte informazioni su shebang attraverso gli unici, incluso il supporto setuid .


Interpreti setuidi

Supponiamo che tu sia riuscito a far funzionare il tuo programma come root, sia perché il tuo sistema operativo supporta setuid shebang o perché hai usato un wrapper binario nativo (come sudo). Hai aperto una falla di sicurezza? Forse . Il problema qui non riguarda i programmi interpretati o compilati. Il problema è se il tuo sistema di runtime si comporta in modo sicuro se eseguito con privilegi.

  • Qualsiasi eseguibile binario nativo collegato dinamicamente viene in qualche modo interpretato dal caricatore dinamico (ad es. /lib/ld.so), Che carica le librerie dinamiche richieste dal programma. Su molti unices, puoi configurare il percorso di ricerca per le librerie dinamiche attraverso l'ambiente ( LD_LIBRARY_PATHè un nome comune per la variabile d'ambiente) e persino caricare librerie aggiuntive in tutti i binari eseguiti ( LD_PRELOAD). L'invoker del programma può eseguire codice arbitrario nel contesto di quel programma inserendo un appositamente predisposto libc.soin $LD_LIBRARY_PATH(tra le altre tattiche). Tutti i sistemi sani ignorano le LD_*variabili negli eseguibili setuid.

  • In shell come sh, csh e derivati, le variabili di ambiente diventano automaticamente parametri di shell. Attraverso parametri come PATH, IFSe molti altri, l'invocatore dello script ha molte opportunità di eseguire codice arbitrario nel contesto degli script della shell. Alcune shell impostano queste variabili su valori predefiniti sani se rilevano che lo script è stato invocato con privilegi, ma non so che esiste un'implementazione particolare di cui mi fiderei.

  • La maggior parte degli ambienti di runtime (nativi, bytecode o interpretati) hanno funzionalità simili. Pochi prendono precauzioni speciali negli eseguibili setuid, anche se quelli che eseguono codice nativo spesso non fanno nulla di più fantasioso del collegamento dinamico (che prende precauzioni).

  • Perl è un'eccezione notevole. Esso supporta esplicitamente script setuid in modo sicuro. In effetti, il tuo script può eseguire setuid anche se il tuo sistema operativo ha ignorato il bit setuid sugli script. Questo perché perl viene fornito con un helper root setuid che esegue i controlli necessari e reinvoca l'interprete sugli script desiderati con i privilegi desiderati. Questo è spiegato nel manuale perlsec . Ha usato essere che gli script Perl setuid bisogno #!/usr/bin/suidperl -wTinvece di #!/usr/bin/perl -wT, ma sulla maggior parte dei sistemi moderni, #!/usr/bin/perl -wTè sufficiente.

Si noti che l'utilizzo di un wrapper binario nativo non fa nulla in sé per prevenire questi problemi . In effetti, può peggiorare la situazione , perché potrebbe impedire all'ambiente di runtime di rilevare che è invocato con privilegi e di bypassare la sua configurabilità di runtime.

Un wrapper binario nativo può rendere sicuro uno script di shell se il wrapper disinfetta l'ambiente . Lo script deve fare attenzione a non fare troppe ipotesi (ad esempio sulla directory corrente), ma questo va bene. È possibile utilizzare sudo per questo, a condizione che sia impostato per disinfettare l'ambiente. Le variabili nella blacklist sono soggette a errori, quindi sempre nella whitelist. Con sudo, assicurarsi che l' env_resetopzione è attiva, che setenvè spento, e che env_filee env_keepcontengono solo variabili innocui.


TL, DR:

  • Setuid Shebang è insicuro ma di solito ignorato.
  • Se si esegue un programma con privilegi (tramite sudo o setuid), scrivere codice nativo o perl o avviare il programma con un wrapper che disinfetta l'ambiente (come sudo con l' env_resetopzione).

¹ Questa discussione si applica ugualmente se si sostituisce "setgid" con "setuid"; entrambi sono ignorati dal kernel Linux sugli script


2
@Josh: sono possibili script di shell setuid sicuri, ma solo se sia l'implementatore di shell che lo script writer sono molto attenti. Piuttosto che un codice nativo, raccomando Perl, dove gli implementatori si sono preoccupati che gli script setuid debbano essere protetti con un piccolo sforzo da parte dello sceneggiatore.
Gilles,

3
apparentemente il suidperlmateriale è stato deprecato e contrassegnato per la rimozione per anni (ma persiste comunque)
jmtd

7
In realtà suidperlè stato rimosso da perl 5.11 (5.12 stabile): perl5110delta:> "suidperl" è stato rimosso. Utilizzava un meccanismo per emulare bit di autorizzazione setuid su sistemi che non lo supportano correttamente. perl5120delta:> "suidperl" non fa più parte del Perl. Utilizzava un meccanismo per emulare bit di autorizzazione setuid su sistemi che non lo supportano correttamente.
Randy Stauner,

5
Nota anche questa riga dai documenti perl 5.6.1 (quasi un decennio fa) ... perl561delta:> Nota che suidperl non è né costruito né installato di default in nessuna versione recente di perl. L'uso di suidperl è fortemente scoraggiato . Se pensi di averne bisogno, prova prima alternative come sudo. Vedi courtesan.com/sudo .
Randy Stauner,

3
Non capisco: questa sembra essere una spiegazione delle ragioni dei problemi, ma esiste una risposta effettiva alla domanda del PO qui? C'è un modo per dire al mio sistema operativo di eseguire script shell setuid?
Tom,

55

Un modo per risolvere questo problema è chiamare lo script della shell da un programma che può usare il bit setuid.
è qualcosa come sudo. Ad esempio, ecco come lo faresti in un programma 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;
 }

Salvalo come setuid-test2.c.
compila
Ora esegui il setuid su questo programma binario:

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

Ora dovresti essere in grado di eseguirlo e vedrai che lo script viene eseguito senza autorizzazioni nessuno.
Ma anche qui è necessario codificare il percorso dello script o passarlo come arg da riga di comando a exe precedente.


25
Vorrei sconsigliare il suggerimento di consentire il passaggio dello script come argomento della riga di comando, in quanto ciò offre essenzialmente a chiunque sia in grado di eseguire il programma la possibilità di eseguire qualsiasi script come l'utente definito.
dsp,

40
Si noti che QUESTO È INSECURO anche se il percorso completo dello script è hardcoded. La shell erediterà le variabili dall'ambiente e molte di esse consentono all'invocatore di iniettare codice arbitrario. PATHe LD_LIBRARY_PATHsono vettori ovvi. Alcune shell eseguire $ENVo $BASHENVo ~/.zshenvaddirittura prima di iniziare l'esecuzione dello script corretto, quindi non è possibile proteggere da questi a tutti all'interno dello script. L'unico modo sicuro per invocare uno script di shell con privilegi è ripulire l'ambiente . Il Sudo sa come farlo in sicurezza. Quindi non scrivere il tuo wrapper, usa sudo .
Gilles,

28
Mi sento male per il fatto che all'improvviso sta venendo retrocesso per questo - ho detto specificamente che volevo ascoltare anche versioni non sicure, e stavo immaginando un eseguibile che ha preso un argomento script di shell quando l'ho detto. Ovviamente è enormemente insicuro, ma volevo sapere quali possibilità esistono
Michael Mrozek

8
@Gilles: Cordiali saluti, Linux si disinserisce LD_LIBRARY_PATHtra l'altro quando incontra il bit setuid.
gravità

3
Invece di usare system, potresti trovare più semplice (e più efficiente) usare una execfamiglia, molto probabilmente execve. In questo modo, non si crea un nuovo processo né si avvia una shell e si possono inoltrare argomenti (presupponendo che lo script privilegiato sia in grado di gestire argomenti in modo sicuro).
Toby Speight,

23

Prefisso alcuni script che si trovano in questa barca così:

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

Nota che questo non usa setuidma esegue semplicemente il file corrente con sudo.


5
Questo non usa setuid ma ti dà solo un sudoprompt. (Per me, il punto centrale di setuid è consentire alle cose di funzionare come root senza bisogno di sudo.)
Luc

12

Se vuoi evitare di chiamare sudo some_scriptpuoi semplicemente fare:

  #!/ust/bin/env sh

  sudo /usr/local/scripts/your_script

I programmi SETUID devono essere progettati con estrema cura in quanto eseguiti con privilegi di root e gli utenti hanno un ampio controllo su di essi. Hanno bisogno di controllare la sanità mentale tutto. Non puoi farlo con gli script perché:

  • Le conchiglie sono grandi software che interagiscono pesantemente con l'utente. È quasi impossibile controllare il buon senso di tutto, soprattutto perché la maggior parte del codice non è destinata a funzionare in tale modalità.
  • Gli script sono per lo più una soluzione veloce e di solito non sono preparati con tanta cura da consentire setuid. Hanno molte funzionalità potenzialmente pericolose.
  • Dipendono fortemente da altri programmi. Non è sufficiente che la shell sia stata controllata. sed, awkecc. dovrebbero essere controllati

Si noti che sudofornisce alcuni controlli di integrità ma non è sufficiente: controllare ogni riga nel proprio codice.

Come ultima nota: considerare l'utilizzo delle funzionalità. Consentono di assegnare a un processo in esecuzione come utente privilegi speciali che normalmente richiederebbero privilegi di root. Tuttavia, ad esempio, sebbene pingdebba manipolare la rete, non è necessario che abbia accesso ai file. Non sono sicuro comunque se siano ereditati.


2
Presumo che /ust/bin/envdovrebbe essere /usr/bin/env.
Bob,

4

È possibile creare un alias per sudo + il nome dello script. Ovviamente, questo è ancora più lavoro da configurare, dal momento che devi anche impostare un alias, ma ti salva dal dover scrivere sudo.

Ma se non ti dispiace per gli orribili rischi per la sicurezza, usa una shell setuid come interprete per lo script della shell. Non so se funzionerà per te, ma credo che potrebbe.

Lasciatemi dire che sconsiglio di farlo, però. Lo sto solo citando per scopi educativi ;-)


5
Funzionerà. Orribilmente come hai affermato. Il bit SETUID consente l'esecuzione con il diritto proprietario. La shell Setuid (a meno che non sia stata progettata per funzionare con setuid) verrà eseguita come root per qualsiasi utente. Vale a dire chiunque può eseguire rm -rf /(e altri comandi della serie NON FARLO A CASA ).
Maciej Piechotka,

9
@MaciejPiechotka di NON FARLO A CASA intendi Sentirti libero di farlo al lavoro? :)
peterph,

4

comando super [-r reqpath] [args]

Super consente agli utenti specificati di eseguire script (o altri comandi) come se fossero root; oppure può impostare i gruppi uid, gid e / o supplementari in base al comando prima di eseguire il comando. È pensato per essere un'alternativa sicura alla creazione di script setuid root. Super consente inoltre agli utenti ordinari di fornire comandi per l'esecuzione da parte di altri; questi vengono eseguiti con uid, gid e gruppi dell'utente che offre il comando.

Super consulta un file `` super.tab '' per vedere se l'utente è autorizzato ad eseguire il comando richiesto. Se viene concessa l'autorizzazione, super eseguirà pgm [args], dove pgm è il programma associato a questo comando. (L'esecuzione root è consentita per impostazione predefinita, ma può comunque essere negata se una regola esclude root. Agli utenti ordinari è vietata l'esecuzione per impostazione predefinita.)

Se command è un collegamento simbolico (o anche hard link) al super programma, digitare% command args equivale a digitare% super command args (Il comando non deve essere super o super non riconoscerà che è stato invocato tramite un collegamento.)

http://www.ucolick.org/~will/RUE/super/README

http://manpages.ubuntu.com/manpages/utopic/en/man1/super.1.html


Ciao e benvenuto nel sito! Ci aspettiamo che le risposte siano più dettagliate qui. Potresti modificare la tua risposta e spiegare cos'è questo programma e come potrebbe aiutare a risolvere il problema del PO?
terdon

Grazie Nizam, per ore a cercare di trovare qualcosa come super per consentire agli account di essere eseguiti da un altro utente come utente del file.
Chad,

2

Se per qualche motivo sudonon è disponibile, è possibile scrivere uno script wrapper sottile in C:

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

E una volta compilato, impostalo come setuidcon chmod 4511 wrapper_script.

Questo è simile a un'altra risposta postata, ma esegue lo script con un ambiente pulito e usa esplicitamente /bin/bashinvece la shell chiamata da system(), quindi chiude alcune potenziali falle di sicurezza.

Si noti che ciò elimina completamente l'ambiente. Se si desidera utilizzare alcune variabili ambientali senza aprire le vulnerabilità, è sufficiente utilizzarle sudo.

Ovviamente, vuoi assicurarti che lo script stesso sia scrivibile solo da root.


-3

Ho trovato prima questa domanda, non convinta da tutte le risposte, eccone una molto migliore, che ti permette anche di offuscare completamente il tuo script bash, se sei così propenso!

Dovrebbe essere autoesplicativo.

#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());
}

Quindi corri

g++ embedded.cpp -o embedded
sudo chown root embedded
sudo chmod u+s embedded

Perché i voti negativi ???
Theodore R. Smith,

2
Probabilmente perché è una soluzione eccessivamente complicata che manipola le stringhe e ha un codice shell incorporato. O fai un semplice lanciatore o usi sudo. Questa è la peggiore di tutte le opzioni.
siride,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.